Merge pull request #4429 from Roasbeef/i-wumbo-you-wumbo-he-she-wumbo

multi: I wumbo, you wumbo, he she wumbo
This commit is contained in:
Conner Fromknecht 2020-07-13 16:06:27 -07:00 committed by GitHub
commit 2f0ccaead1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 313 additions and 62 deletions

@ -47,4 +47,8 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.WumboChannelsOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
},
}

@ -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.

@ -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,

@ -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")
}

26
lncfg/protocol.go Normal file

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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)

@ -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

@ -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)
}

@ -191,4 +191,7 @@
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: error sending channel announcement: gossiper is shutting down
<time> [ERR] PEER: unable to close channel, ChannelID(<hex>) is unknown
<time> [ERR] HSWC: ChannelLink(<chan>): unable to update signals
<time> [ERR] RPCS: [/routerrpc.Router/HtlcInterceptor]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/routerrpc.Router/HtlcInterceptor]: rpc error: code = Canceled desc = context canceled
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=channel too large

@ -101,6 +101,14 @@ const (
// HTLC.
MPPOptional FeatureBit = 17
// WumboChannelsRequired is a required feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsRequired = 18
// WumboChannelsRequired is an optional feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsOptional = 19
// AnchorsRequired is a required feature bit that signals that the node
// requires channels to be made using commitments having anchor
// outputs.
@ -150,6 +158,8 @@ var Features = map[FeatureBit]string{
MPPRequired: "multi-path-payments",
AnchorsRequired: "anchor-commitments",
AnchorsOptional: "anchor-commitments",
WumboChannelsRequired: "wumbo-channels",
WumboChannelsOptional: "wumbo-channels",
}
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A

@ -1713,6 +1713,8 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
remoteCsvDelay := uint16(in.RemoteCsvDelay)
maxValue := lnwire.MilliSatoshi(in.RemoteMaxValueInFlightMsat)
globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn)
// Ensure that the initial balance of the remote party (if pushing
// satoshis) does not exceed the amount the local party has requested
// for funding.
@ -1726,7 +1728,10 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
// Ensure that the user doesn't exceed the current soft-limit for
// channel size. If the funding amount is above the soft-limit, then
// we'll reject the request.
if localFundingAmt > MaxFundingAmount {
wumboEnabled := globalFeatureSet.HasFeature(
lnwire.WumboChannelsOptional,
)
if !wumboEnabled && localFundingAmt > MaxFundingAmount {
return nil, fmt.Errorf("funding amount is too large, the max "+
"channel size is: %v", MaxFundingAmount)
}

@ -356,27 +356,6 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
}
}
globalFeatures := lnwire.NewRawFeatureVector()
// Only if we're not being forced to use the legacy onion format, will
// we signal our knowledge of the new TLV onion format.
if !cfg.ProtocolOptions.LegacyOnion() {
globalFeatures.Set(lnwire.TLVOnionPayloadOptional)
}
// Similarly, we default to supporting the new modern commitment format
// where the remote key is static unless the protocol config is set to
// keep using the older format.
if !cfg.ProtocolOptions.NoStaticRemoteKey() {
globalFeatures.Set(lnwire.StaticRemoteKeyOptional)
}
// We only signal that we support the experimental anchor commitments
// if explicitly enabled in the config.
if cfg.ProtocolOptions.AnchorCommitments() {
globalFeatures.Set(lnwire.AnchorsOptional)
}
var serializedPubKey [33]byte
copy(serializedPubKey[:], nodeKeyECDH.PubKey().SerializeCompressed())
@ -410,6 +389,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
NoAnchors: !cfg.ProtocolOptions.AnchorCommitments(),
NoWumbo: !cfg.ProtocolOptions.Wumbo(),
})
if err != nil {
return nil, err
@ -987,6 +967,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
}
s.fundingMgr, err = newFundingManager(fundingConfig{
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
IDKey: nodeKeyECDH.PubKey(),
Wallet: cc.wallet,
PublishTransaction: cc.wallet.PublishTransaction,
@ -1053,11 +1034,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
return defaultConf
}
minConf := uint64(3)
maxConf := uint64(6)
// If this is a wumbo channel, then we'll require the
// max amount of confirmations.
if chanAmt > MaxFundingAmount {
return uint16(maxConf)
}
// If not we return a value scaled linearly
// between 3 and 6, depending on channel size.
// TODO(halseth): Use 1 as minimum?
minConf := uint64(3)
maxConf := uint64(6)
maxChannelSize := uint64(
lnwire.NewMSatFromSatoshis(MaxFundingAmount))
stake := lnwire.NewMSatFromSatoshis(chanAmt) + pushAmt
@ -1086,6 +1074,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
return defaultDelay
}
// If this is a wumbo channel, then we'll require the
// max value.
if chanAmt > MaxFundingAmount {
return maxRemoteDelay
}
// If not we scale according to channel size.
delay := uint16(btcutil.Amount(maxRemoteDelay) *
chanAmt / MaxFundingAmount)