From 0764f27b6fc7b1952c5c73fb952fbd610ca8c4d2 Mon Sep 17 00:00:00 2001 From: Yash Bhutwala Date: Tue, 13 Jan 2026 09:37:12 -0500 Subject: [PATCH 1/3] rpcserver: use protocol max for fundMax, not maxChanSize The maxChanSize config option is documented to only apply to incoming channel requests. However, when using fundMax with OpenChannel, the code was incorrectly using maxChanSize as the upper bound for the outgoing channel size. This commit fixes the issue by using the protocol-level maximum (MaxBtcFundingAmount or MaxBtcFundingAmountWumbo depending on wumbo support) as the upper bound for fundMax operations. Fixes #10468. --- rpcserver.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 3d5c4890d..be4d05694 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2152,15 +2152,26 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, "the channel opening") } + // Fetch our own feature set and determine wumbo support early, as it's + // needed for both FundMax and explicit amount validation. + globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn) + wumboEnabled := globalFeatureSet.HasFeature( + lnwire.WumboChannelsOptional, + ) + // If the FundMax flag is set, ensure that the acceptable minimum local // amount adheres to the amount to be pushed to the remote, and to - // current rules, while also respecting the settings for the maximum + // current rules, while also respecting the protocol-level maximum // channel size. var minFundAmt, fundUpToMaxAmt btcutil.Amount if in.FundMax { - // We assume the configured maximum channel size to be the upper - // bound of our "maxed" out funding attempt. - fundUpToMaxAmt = btcutil.Amount(r.cfg.MaxChanSize) + // Use the protocol-level maximum as the upper bound for our + // funding attempt. + if wumboEnabled { + fundUpToMaxAmt = funding.MaxBtcFundingAmountWumbo + } else { + fundUpToMaxAmt = MaxFundingAmount + } // Since the standard non-fundmax flow requires the minimum // funding amount to be at least in the amount of the initial @@ -2186,8 +2197,6 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, maxHtlcs := uint16(in.RemoteMaxHtlcs) remoteChanReserve := btcutil.Amount(in.RemoteChanReserveSat) - globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn) - // Determine if the user provided channel fees // and if so pass them on to the funding workflow. var channelBaseFee, channelFeeRate *uint64 @@ -2212,9 +2221,6 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // in the wallet hence we do not check it here against the maximum // funding amount. Only if the localFundingAmt is specified we can check // if it exceeds the maximum funding amount. - wumboEnabled := globalFeatureSet.HasFeature( - lnwire.WumboChannelsOptional, - ) if !in.FundMax && !wumboEnabled && localFundingAmt > MaxFundingAmount { return nil, fmt.Errorf("funding amount is too large, the max "+ "channel size is: %v", MaxFundingAmount) From 73e7708f15d867b0b10a9d47384d9fa40bb7e09c Mon Sep 17 00:00:00 2001 From: Yash Bhutwala Date: Tue, 13 Jan 2026 09:37:19 -0500 Subject: [PATCH 2/3] itest: add fundMax with maxChanSize test Add integration test to verify that fundMax uses the protocol-level maximum channel size instead of the user-configured maxChanSize. The test uses a table-driven approach to cover both non-wumbo and wumbo scenarios, creating nodes with restrictive maxChanSize (5M sats) and verifying that fundMax still creates channels at the protocol maximum. --- itest/list_on_test.go | 4 ++ itest/lnd_channel_funding_fund_max_test.go | 77 ++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 18732b179..a09c4c1f6 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -567,6 +567,10 @@ var allTestCases = []*lntest.TestCase{ Name: "channel fundmax anchor reserve", TestFunc: testChannelFundMaxAnchorReserve, }, + { + Name: "channel fundmax maxchansize", + TestFunc: testChannelFundMaxMaxChanSize, + }, { Name: "htlc timeout resolver extract preimage remote", TestFunc: testHtlcTimeoutResolverExtractPreimageRemote, diff --git a/itest/lnd_channel_funding_fund_max_test.go b/itest/lnd_channel_funding_fund_max_test.go index f2c73851d..34718623f 100644 --- a/itest/lnd_channel_funding_fund_max_test.go +++ b/itest/lnd_channel_funding_fund_max_test.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -408,3 +409,79 @@ func sweepNodeWalletAndAssert(ht *lntest.HarnessTest, node *node.HarnessNode) { // Ensure that the node's balance is 0 checkChannelBalance(ht, node, 0, 0) } + +// testChannelFundMaxMaxChanSize verifies that fundMax uses the protocol-level +// maximum channel size, not the user-configured maxChanSize. The maxChanSize +// config option is intended only for limiting incoming channel requests, not +// outgoing ones. +func testChannelFundMaxMaxChanSize(ht *lntest.HarnessTest) { + testCases := []struct { + name string + wumbo bool + expectedMax btcutil.Amount + }{ + { + name: "non-wumbo", + wumbo: false, + expectedMax: funding.MaxBtcFundingAmount, + }, + { + name: "wumbo", + wumbo: true, + expectedMax: funding.MaxBtcFundingAmountWumbo, + }, + } + + for _, tc := range testCases { + success := ht.Run(tc.name, func(t *testing.T) { + st := ht.Subtest(t) + + // Configure Alice with a restrictive maxChanSize (5M + // sats), which is below both protocol maximums. + aliceArgs := []string{ + "--maxchansize=5000000", + } + if tc.wumbo { + aliceArgs = append( + aliceArgs, "--protocol.wumbo-channels", + ) + } + + alice := st.NewNode("Alice", aliceArgs) + + // Bob needs wumbo enabled to accept large channels. + var bobArgs []string + if tc.wumbo { + bobArgs = []string{"--protocol.wumbo-channels"} + } + bob := st.NewNode("Bob", bobArgs) + + st.EnsureConnected(alice, bob) + + // Fund Alice with more than the protocol maximum. + fundAmt := tc.expectedMax + btcutil.SatoshiPerBitcoin + st.FundCoins(fundAmt, alice) + + // Open channel with fundMax. This should use the + // protocol maximum, not the configured maxChanSize. + chanPoint := st.OpenChannel( + alice, bob, lntest.OpenChannelParams{ + FundMax: true, + }, + ) + + cType := st.GetChannelCommitType(alice, chanPoint) + + // The expected balance is the protocol maximum minus + // the commitment fee. + expectedBalance := tc.expectedMax - + lntest.CalcStaticFee(cType, 0) + + checkChannelBalance(st, alice, expectedBalance, 0) + checkChannelBalance(st, bob, 0, expectedBalance) + }) + if !success { + break + } + } +} From f48e6ba145fa09ece37930a7bdc17fde88515336 Mon Sep 17 00:00:00 2001 From: Yash Bhutwala Date: Tue, 13 Jan 2026 09:37:23 -0500 Subject: [PATCH 3/3] docs: update release notes for fundMax fix --- docs/release-notes/release-notes-0.21.0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index 93892dbbe..13e048487 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -21,6 +21,12 @@ # Bug Fixes +* [Fixed `OpenChannel` with + `fund_max`](https://github.com/lightningnetwork/lnd/pull/10488) to use the + protocol-level maximum channel size instead of the user-configured + `maxchansize`. The `maxchansize` config option is intended only for limiting + incoming channel requests from peers, not outgoing ones. + - Chain notifier RPCs now [return the gRPC `Unavailable` status](https://github.com/lightningnetwork/lnd/pull/10352) while the sub-server is still starting. This allows clients to reliably detect the