diff --git a/feature/default_sets.go b/feature/default_sets.go index 8054894e..64e6f9bd 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -47,4 +47,8 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, + lnwire.WumboChannelsOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, } diff --git a/feature/manager.go b/feature/manager.go index 159540c2..7e20541e 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -20,6 +20,9 @@ type Config struct { // NoAnchors unsets any bits signaling support for anchor outputs. NoAnchors bool + + // NoWumbo unsets any bits signalling support for wumbo channels. + NoWumbo bool } // Manager is responsible for generating feature vectors for different requested @@ -36,7 +39,7 @@ func NewManager(cfg Config) (*Manager, error) { return newManager(cfg, defaultSetDesc) } -// newManager creates a new feeature Manager, applying any custom modifications +// newManager creates a new feature Manager, applying any custom modifications // to its feature sets before returning. This method accepts the setDesc as its // own parameter so that it can be unit tested. func newManager(cfg Config, desc setDesc) (*Manager, error) { @@ -83,6 +86,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.AnchorsOptional) raw.Unset(lnwire.AnchorsRequired) } + if cfg.NoWumbo { + raw.Unset(lnwire.WumboChannelsOptional) + raw.Unset(lnwire.WumboChannelsRequired) + } // Ensure that all of our feature sets properly set any // dependent features. diff --git a/fundingmanager.go b/fundingmanager.go index ebad44b6..aa3887ed 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -227,6 +227,10 @@ func newSerializedKey(pubKey *btcec.PublicKey) serializedPubKey { // within the configuration MUST be non-nil for the FundingManager to carry out // its duties. type fundingConfig struct { + // NoWumboChans indicates if we're to reject all incoming wumbo channel + // requests, and also reject all outgoing wumbo channel requests. + NoWumboChans bool + // IDKey is the PublicKey that is used to identify this node within the // Lightning Network. IDKey *btcec.PublicKey @@ -1236,8 +1240,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { } // We'll reject any request to create a channel that's above the - // current soft-limit for channel size. - if msg.FundingAmount > MaxFundingAmount { + // current soft-limit for channel size, but only if we're rejecting all + // wumbo channel initiations. + if f.cfg.NoWumboChans && msg.FundingAmount > MaxFundingAmount { f.failFundingFlow( fmsg.peer, fmsg.msg.PendingChannelID, lnwire.ErrChanTooLarge, diff --git a/fundingmanager_test.go b/fundingmanager_test.go index 4ea95fa5..9896563d 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -3176,3 +3176,84 @@ func TestGetUpfrontShutdownScript(t *testing.T) { }) } } + +func expectOpenChannelMsg(t *testing.T, msgChan chan lnwire.Message) *lnwire.OpenChannel { + var msg lnwire.Message + select { + case msg = <-msgChan: + case <-time.After(time.Second * 5): + t.Fatalf("node did not send OpenChannel message") + } + + openChannelReq, ok := msg.(*lnwire.OpenChannel) + if !ok { + errorMsg, gotError := msg.(*lnwire.Error) + if gotError { + t.Fatalf("expected OpenChannel to be sent "+ + "from bob, instead got error: %v", + errorMsg.Error()) + } + t.Fatalf("expected OpenChannel to be sent, instead got %T", + msg) + } + + return openChannelReq +} + +// TestWumboChannelConfig tests that the funding manager will respect the wumbo +// channel config param when creating or accepting new channels. +func TestWumboChannelConfig(t *testing.T) { + t.Parallel() + + // First we'll create a set of funding managers that will reject wumbo + // channels. + alice, bob := setupFundingManagers(t, func(cfg *fundingConfig) { + cfg.NoWumboChans = true + }) + + // If we attempt to initiate a new funding open request to Alice, + // that's below the wumbo channel mark, we should be able to start the + // funding process w/o issue. + updateChan := make(chan *lnrpc.OpenStatusUpdate) + errChan := make(chan error, 1) + initReq := &openChanReq{ + targetPubkey: bob.privKey.PubKey(), + chainHash: *activeNetParams.GenesisHash, + localFundingAmt: MaxFundingAmount, + pushAmt: lnwire.NewMSatFromSatoshis(0), + private: false, + updates: updateChan, + err: errChan, + } + + // We expect Bob to respond with an Accept channel message. + alice.fundingMgr.initFundingWorkflow(bob, initReq) + openChanMsg := expectOpenChannelMsg(t, alice.msgChan) + bob.fundingMgr.processFundingOpen(openChanMsg, alice) + assertFundingMsgSent(t, bob.msgChan, "AcceptChannel") + + // We'll now attempt to create a channel above the wumbo mark, which + // should be rejected. + initReq.localFundingAmt = btcutil.SatoshiPerBitcoin + + // After processing the funding open message, bob should respond with + // an error rejecting the channel. + alice.fundingMgr.initFundingWorkflow(bob, initReq) + openChanMsg = expectOpenChannelMsg(t, alice.msgChan) + bob.fundingMgr.processFundingOpen(openChanMsg, alice) + assertErrorSent(t, bob.msgChan) + + // Next, we'll re-create the funding managers, but this time allowing + // wumbo channels explicitly. + tearDownFundingManagers(t, alice, bob) + alice, bob = setupFundingManagers(t, func(cfg *fundingConfig) { + cfg.NoWumboChans = false + }) + + // We should now be able to initiate a wumbo channel funding w/o any + // issues. + alice.fundingMgr.initFundingWorkflow(bob, initReq) + openChanMsg = expectOpenChannelMsg(t, alice.msgChan) + bob.fundingMgr.processFundingOpen(openChanMsg, alice) + assertFundingMsgSent(t, bob.msgChan, "AcceptChannel") +} diff --git a/lncfg/protocol.go b/lncfg/protocol.go new file mode 100644 index 00000000..5e1be5e6 --- /dev/null +++ b/lncfg/protocol.go @@ -0,0 +1,26 @@ +package lncfg + +// ProtocolOptions is a struct that we use to be able to test backwards +// compatibility of protocol additions, while defaulting to the latest within +// lnd, or to enable experimental protocol changes. +type ProtocolOptions struct { + // LegacyProtocol is a sub-config that houses all the legacy protocol + // options. These are mostly used for integration tests as most modern + // nodes shuld always run with them on by default. + LegacyProtocol `group:"legacy" namespace:"legacy"` + + // ExperimentalProtocol is a sub-config that houses any experimental + // protocol features that also require a build-tag to activate. + ExperimentalProtocol + + // WumboChans should be set if we want to enable support for wumbo + // (channels larger than 0.16 BTC) channels, which is the opposite of + // mini. + WumboChans bool `long:"wumbo-channels" description:"if set, then lnd will create and accept requests for channels larger chan 0.16 BTC"` +} + +// Wumbo returns true if lnd should permit the creation and acceptance of wumbo +// channels. +func (l *ProtocolOptions) Wumbo() bool { + return l.WumboChans +} diff --git a/lncfg/protocol_experimental_off.go b/lncfg/protocol_experimental_off.go new file mode 100644 index 00000000..20d1ce48 --- /dev/null +++ b/lncfg/protocol_experimental_off.go @@ -0,0 +1,14 @@ +// +build !dev + +package lncfg + +// ExperimentalProtocol is a sub-config that houses any experimental protocol +// features that also require a build-tag to activate. +type ExperimentalProtocol struct { +} + +// AnchorCommitments returns true if support for the anchor commitment type +// should be signaled. +func (l *ExperimentalProtocol) AnchorCommitments() bool { + return false +} diff --git a/lncfg/protocol_experimental_on.go b/lncfg/protocol_experimental_on.go new file mode 100644 index 00000000..dac8cfea --- /dev/null +++ b/lncfg/protocol_experimental_on.go @@ -0,0 +1,17 @@ +// +build dev + +package lncfg + +// ExperimentalProtocol is a sub-config that houses any experimental protocol +// features that also require a build-tag to activate. +type ExperimentalProtocol struct { + // Anchors should be set if we want to support opening or accepting + // channels having the anchor commitment type. + Anchors bool `long:"anchors" description:"EXPERIMENTAL: enable experimental support for anchor commitments, won't work with watchtowers"` +} + +// AnchorCommitments returns true if support for the anchor commitment type +// should be signaled. +func (l *ExperimentalProtocol) AnchorCommitments() bool { + return l.Anchors +} diff --git a/lncfg/protocol_legacy_off.go b/lncfg/protocol_legacy_off.go index bd589c24..060569d8 100644 --- a/lncfg/protocol_legacy_off.go +++ b/lncfg/protocol_legacy_off.go @@ -2,27 +2,21 @@ package lncfg -// ProtocolOptions is a struct that we use to be able to test backwards -// compatibility of protocol additions, while defaulting to the latest within -// lnd, or to enable experimental protocol changes. -type ProtocolOptions struct { +// Legacy is a sub-config that houses all the legacy protocol options. These +// are mostly used for integration tests as most modern nodes shuld always run +// with them on by default. +type LegacyProtocol struct { } // LegacyOnion returns true if the old legacy onion format should be used when // we're an intermediate or final hop. This controls if we set the // TLVOnionPayloadOptional bit or not. -func (l *ProtocolOptions) LegacyOnion() bool { +func (l *LegacyProtocol) LegacyOnion() bool { return false } // NoStaticRemoteKey returns true if the old commitment format with a tweaked // remote key should be used for new funded channels. -func (l *ProtocolOptions) NoStaticRemoteKey() bool { - return false -} - -// AnchorCommitments returns true if support for the the anchor commitment type -// should be signaled. -func (l *ProtocolOptions) AnchorCommitments() bool { +func (l *LegacyProtocol) NoStaticRemoteKey() bool { return false } diff --git a/lncfg/protocol_legacy_on.go b/lncfg/protocol_legacy_on.go index ac388f02..712d5fed 100644 --- a/lncfg/protocol_legacy_on.go +++ b/lncfg/protocol_legacy_on.go @@ -2,41 +2,31 @@ package lncfg -// ProtocolOptions is a struct that we use to be able to test backwards -// compatibility of protocol additions, while defaulting to the latest within -// lnd, or to enable experimental protocol changes. -type ProtocolOptions struct { +// Legacy is a sub-config that houses all the legacy protocol options. These +// are mostly used for integration tests as most modern nodes shuld always run +// with them on by default. +type LegacyProtocol struct { // LegacyOnionFormat if set to true, then we won't signal // TLVOnionPayloadOptional. As a result, nodes that include us in the // route won't use the new modern onion framing. - LegacyOnionFormat bool `long:"legacyonion" description:"force node to not advertise the new modern TLV onion format"` + LegacyOnionFormat bool `long:"onion" description:"force node to not advertise the new modern TLV onion format"` // CommitmentTweak guards if we should use the old legacy commitment // protocol, or the newer variant that doesn't have a tweak for the // remote party's output in the commitment. If set to true, then we // won't signal StaticRemoteKeyOptional. CommitmentTweak bool `long:"committweak" description:"force node to not advertise the new commitment format"` - - // Anchors should be set if we want to support opening or accepting - // channels having the anchor commitment type. - Anchors bool `long:"anchors" description:"EXPERIMENTAL: enable experimental support for anchor commitments, won't work with watchtowers"` } // LegacyOnion returns true if the old legacy onion format should be used when // we're an intermediate or final hop. This controls if we set the // TLVOnionPayloadOptional bit or not. -func (l *ProtocolOptions) LegacyOnion() bool { +func (l *LegacyProtocol) LegacyOnion() bool { return l.LegacyOnionFormat } // NoStaticRemoteKey returns true if the old commitment format with a tweaked // remote key should be used for new funded channels. -func (l *ProtocolOptions) NoStaticRemoteKey() bool { +func (l *LegacyProtocol) NoStaticRemoteKey() bool { return l.CommitmentTweak } - -// AnchorCommitments returns true if support for the anchor commitment type -// should be signaled. -func (l *ProtocolOptions) AnchorCommitments() bool { - return l.Anchors -} diff --git a/lntest/itest/lnd_multi-hop-payments.go b/lntest/itest/lnd_multi-hop-payments.go index fb9e79cb..a7dfee5a 100644 --- a/lntest/itest/lnd_multi-hop-payments.go +++ b/lntest/itest/lnd_multi-hop-payments.go @@ -48,7 +48,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) { // // First, we'll create Dave and establish a channel to Alice. Dave will // be running an older node that requires the legacy onion payload. - daveArgs := []string{"--protocol.legacyonion"} + daveArgs := []string{"--protocol.legacy.onion"} dave, err := net.NewNode("Dave", daveArgs) if err != nil { t.Fatalf("unable to create new nodes: %v", err) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 0cf06fd1..687483ab 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -1178,7 +1178,7 @@ func (c commitType) String() string { func (c commitType) Args() []string { switch c { case commitTypeLegacy: - return []string{"--protocol.committweak"} + return []string{"--protocol.legacy.committweak"} case commitTypeTweakless: return []string{} case commitTypeAnchors: @@ -1457,9 +1457,14 @@ test: // Check that the signalled type matches what we // expect. switch { - case expType == commitTypeAnchors && chansCommitType == lnrpc.CommitmentType_ANCHORS: - case expType == commitTypeTweakless && chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY: - case expType == commitTypeLegacy && chansCommitType == lnrpc.CommitmentType_LEGACY: + case expType == commitTypeAnchors && + chansCommitType == lnrpc.CommitmentType_ANCHORS: + + case expType == commitTypeTweakless && + chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY: + + case expType == commitTypeLegacy && + chansCommitType == lnrpc.CommitmentType_LEGACY: default: t.Fatalf("expected nodes to signal "+ @@ -15195,6 +15200,10 @@ var testsCases = []*testCase{ name: "intercept forwarded htlc packets", test: testForwardInterceptor, }, + { + name: "wumbo channels", + test: testWumboChannels, + }, } // TestLightningNetworkDaemon performs a series of integration tests amongst a diff --git a/lntest/itest/lnd_wumbo_channels_test.go b/lntest/itest/lnd_wumbo_channels_test.go new file mode 100644 index 00000000..31f379b8 --- /dev/null +++ b/lntest/itest/lnd_wumbo_channels_test.go @@ -0,0 +1,92 @@ +// +build rpctest + +package itest + +import ( + "context" + "strings" + + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/lntest" +) + +// testWumboChannels tests that only a node that signals wumbo channel +// acceptances will allow a wumbo channel to be created. Additionally, if a +// node is running with mini channels only enabled, then they should reject any +// inbound wumbo channel requests. +func testWumboChannels(net *lntest.NetworkHarness, t *harnessTest) { + // With all the channel types exercised, we'll now make sure the wumbo + // signalling support works properly. + // + // We'll make two new nodes, with one of them signalling support for + // wumbo channels while the other doesn't. + wumboNode, err := net.NewNode( + "wumbo", []string{"--protocol.wumbo-channels"}, + ) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, wumboNode) + miniNode, err := net.NewNode("mini", nil) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, miniNode) + + // We'll send coins to the wumbo node, as it'll be the one imitating + // the channel funding. + ctxb := context.Background() + err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, wumboNode) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + + // Next we'll connect both nodes, then attempt to make a wumbo channel + // funding request to the mini node we created above. The wumbo request + // should fail as the node isn't advertising wumbo channels. + err = net.EnsureConnected(ctxb, wumboNode, miniNode) + if err != nil { + t.Fatalf("unable to connect peers: %v", err) + } + + chanAmt := lnd.MaxBtcFundingAmount + 1 + _, err = net.OpenChannel( + ctxb, wumboNode, miniNode, lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + if err == nil { + t.Fatalf("expected wumbo channel funding to fail") + } + + // The test should indicate a failure due to the channel being too + // large. + if !strings.Contains(err.Error(), "channel too large") { + t.Fatalf("channel should be rejected due to size, instead "+ + "error was: %v", err) + } + + // We'll now make another wumbo node to accept our wumbo channel + // funding. + wumboNode2, err := net.NewNode( + "wumbo2", []string{"--protocol.wumbo-channels"}, + ) + if err != nil { + t.Fatalf("unable to create new node: %v", err) + } + defer shutdownAndAssert(net, t, wumboNode2) + + // Creating a wumbo channel between these two nodes should succeed. + err = net.EnsureConnected(ctxb, wumboNode, wumboNode2) + if err != nil { + t.Fatalf("unable to connect peers: %v", err) + } + chanPoint := openChannelAndAssert( + ctxb, t, net, wumboNode, wumboNode2, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + closeChannelAndAssert(ctxb, t, net, wumboNode, chanPoint, false) +} diff --git a/lntest/itest/log_error_whitelist.txt b/lntest/itest/log_error_whitelist.txt index 0185f6b4..59f1bdc3 100644 --- a/lntest/itest/log_error_whitelist.txt +++ b/lntest/itest/log_error_whitelist.txt @@ -191,4 +191,7 @@