From abefa9306522ebf0f0c81bf03c1cb8622433712b Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 7 Dec 2020 14:12:45 +0100 Subject: [PATCH 1/7] lnwire+feature: define AnchorsZeroFeeHtlcTx feature --- feature/default_sets.go | 2 +- feature/manager.go | 4 ++-- lnwire/features.go | 12 ++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/feature/default_sets.go b/feature/default_sets.go index 65e6607b400..50b503f8ca1 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -43,7 +43,7 @@ var defaultSetDesc = setDesc{ SetNodeAnn: {}, // N SetInvoice: {}, // 9 }, - lnwire.AnchorsOptional: { + lnwire.AnchorsZeroFeeHtlcTxOptional: { SetInit: {}, // I SetNodeAnn: {}, // N }, diff --git a/feature/manager.go b/feature/manager.go index 7e20541ec75..cbaf8f57f10 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -83,8 +83,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.StaticRemoteKeyRequired) } if cfg.NoAnchors { - raw.Unset(lnwire.AnchorsOptional) - raw.Unset(lnwire.AnchorsRequired) + raw.Unset(lnwire.AnchorsZeroFeeHtlcTxOptional) + raw.Unset(lnwire.AnchorsZeroFeeHtlcTxRequired) } if cfg.NoWumbo { raw.Unset(lnwire.WumboChannelsOptional) diff --git a/lnwire/features.go b/lnwire/features.go index 45ef6eeb8af..279e72c9e42 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -119,6 +119,16 @@ const ( // outputs. AnchorsOptional FeatureBit = 21 + // AnchorsZeroFeeHtlcTxRequired is a required feature bit that signals + // that the node requires channels having zero-fee second-level HTLC + // transactions, which also imply anchor commitments. + AnchorsZeroFeeHtlcTxRequired FeatureBit = 22 + + // AnchorsZeroFeeHtlcTxRequired is an optional feature bit that signals + // that the node supports channels having zero-fee second-level HTLC + // transactions, which also imply anchor commitments. + AnchorsZeroFeeHtlcTxOptional FeatureBit = 23 + // maxAllowedSize is a maximum allowed size of feature vector. // // NOTE: Within the protocol, the maximum allowed message size is 65535 @@ -158,6 +168,8 @@ var Features = map[FeatureBit]string{ MPPRequired: "multi-path-payments", AnchorsRequired: "anchor-commitments", AnchorsOptional: "anchor-commitments", + AnchorsZeroFeeHtlcTxRequired: "anchors-zero-fee-htlc-tx", + AnchorsZeroFeeHtlcTxOptional: "anchors-zero-fee-htlc-tx", WumboChannelsRequired: "wumbo-channels", WumboChannelsOptional: "wumbo-channels", } From d5cd9861d2e09c54deb197fcb0e02c6245cc410c Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 7 Dec 2020 14:14:20 +0100 Subject: [PATCH 2/7] channeldb+lnwallet: define zero-fee channel type --- channeldb/channel.go | 10 ++++++++++ lnwallet/commitment.go | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/channeldb/channel.go b/channeldb/channel.go index 41f513374a9..14a0c62e499 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -244,6 +244,10 @@ const ( // that only the responder can decide to cooperatively close the // channel. FrozenBit ChannelType = 1 << 4 + + // ZeroHtlcTxFeeBit indicates that the channel should use zero-fee + // second-level HTLC transactions. + ZeroHtlcTxFeeBit ChannelType = 1 << 5 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -275,6 +279,12 @@ func (c ChannelType) HasAnchors() bool { return c&AnchorOutputsBit == AnchorOutputsBit } +// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC +// transactions signed with zero-fee. +func (c ChannelType) ZeroHtlcTxFee() bool { + return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit +} + // IsFrozen returns true if the channel is considered to be "frozen". A frozen // channel means that only the responder can initiate a cooperative channel // closure. diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index f5eaf861c42..9dbf19b1b6e 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -278,6 +278,12 @@ func CommitWeight(chanType channeldb.ChannelType) int64 { func HtlcTimeoutFee(chanType channeldb.ChannelType, feePerKw chainfee.SatPerKWeight) btcutil.Amount { + // For zero-fee HTLC channels, this will always be zero, regardless of + // feerate. + if chanType.ZeroHtlcTxFee() { + return 0 + } + if chanType.HasAnchors() { return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed) } @@ -290,6 +296,12 @@ func HtlcTimeoutFee(chanType channeldb.ChannelType, func HtlcSuccessFee(chanType channeldb.ChannelType, feePerKw chainfee.SatPerKWeight) btcutil.Amount { + // For zero-fee HTLC channels, this will always be zero, regardless of + // feerate. + if chanType.ZeroHtlcTxFee() { + return 0 + } + if chanType.HasAnchors() { return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed) } From 0b9bec78048bcc4ce7e49fce9d1a038a5aa32c35 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 7 Dec 2020 14:21:29 +0100 Subject: [PATCH 3/7] chanrestore: define ZeroHtlcTxFeeCommitVersion --- chanbackup/single.go | 9 +++++++++ chanrestore.go | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/chanbackup/single.go b/chanbackup/single.go index 490657b90dd..ab8bdcdc622 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -36,6 +36,10 @@ const ( // implicitly denotes that this channel uses the new anchor commitment // format. AnchorsCommitVersion = 2 + + // AnchorsZeroFeeHtlcTxCommitVersion is a version that denotes this + // channel is using the zero-fee second-level anchor commitment format. + AnchorsZeroFeeHtlcTxCommitVersion = 3 ) // Single is a static description of an existing channel that can be used for @@ -163,6 +167,9 @@ func NewSingle(channel *channeldb.OpenChannel, } switch { + case channel.ChanType.ZeroHtlcTxFee(): + single.Version = AnchorsZeroFeeHtlcTxCommitVersion + case channel.ChanType.HasAnchors(): single.Version = AnchorsCommitVersion @@ -185,6 +192,7 @@ func (s *Single) Serialize(w io.Writer) error { case DefaultSingleVersion: case TweaklessCommitVersion: case AnchorsCommitVersion: + case AnchorsZeroFeeHtlcTxCommitVersion: default: return fmt.Errorf("unable to serialize w/ unknown "+ "version: %v", s.Version) @@ -344,6 +352,7 @@ func (s *Single) Deserialize(r io.Reader) error { case DefaultSingleVersion: case TweaklessCommitVersion: case AnchorsCommitVersion: + case AnchorsZeroFeeHtlcTxCommitVersion: default: return fmt.Errorf("unable to de-serialize w/ unknown "+ "version: %v", s.Version) diff --git a/chanrestore.go b/chanrestore.go index 60071ae389b..a309866c524 100644 --- a/chanrestore.go +++ b/chanrestore.go @@ -110,6 +110,11 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( chanType = channeldb.AnchorOutputsBit chanType |= channeldb.SingleFunderTweaklessBit + case chanbackup.AnchorsZeroFeeHtlcTxCommitVersion: + chanType = channeldb.ZeroHtlcTxFeeBit + chanType |= channeldb.AnchorOutputsBit + chanType |= channeldb.SingleFunderTweaklessBit + default: return nil, fmt.Errorf("unknown Single version: %v", err) } From 1923d40843e0e6ac918f7f59f2b9bc5dccdc5222 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 7 Dec 2020 14:22:07 +0100 Subject: [PATCH 4/7] lnwallet+funding: enable AnchorsZeroFeeHtlcTx commit type if both nodes support We assume the legacy anchor type is no longer advertised by us. --- fundingmanager.go | 36 +++++++++++++++++------------------- lnwallet/reservation.go | 26 ++++++++++++++------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index 46338af39ca..9d1e234cae7 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1137,20 +1137,21 @@ func (f *fundingManager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) func commitmentType(localFeatures, remoteFeatures *lnwire.FeatureVector) lnwallet.CommitmentType { - // If both peers are signalling support for anchor commitments, this - // implicitly mean we'll create the channel of this type. Note that - // this also enables tweakless commitments, as anchor commitments are - // always tweakless. - localAnchors := localFeatures.HasFeature( - lnwire.AnchorsOptional, + // If both peers are signalling support for anchor commitments with + // zero-fee HTLC transactions, we'll use this type. + localZeroFee := localFeatures.HasFeature( + lnwire.AnchorsZeroFeeHtlcTxOptional, ) - remoteAnchors := remoteFeatures.HasFeature( - lnwire.AnchorsOptional, + remoteZeroFee := remoteFeatures.HasFeature( + lnwire.AnchorsZeroFeeHtlcTxOptional, ) - if localAnchors && remoteAnchors { - return lnwallet.CommitmentTypeAnchors + if localZeroFee && remoteZeroFee { + return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx } + // Since we don't want to support the "legacy" anchor type, we will + // fall back to static remote key if the nodes don't support the zero + // fee HTLC tx anchor type. localTweakless := localFeatures.HasFeature( lnwire.StaticRemoteKeyOptional, ) @@ -1306,10 +1307,9 @@ func (f *fundingManager) handleFundingOpen(peer lnpeer.Peer, // responding side of a single funder workflow, we don't commit any // funds to the channel ourselves. // - // Before we init the channel, we'll also check to see if we've - // negotiated the new tweakless commitment format. This is only the - // case if *both* us and the remote peer are signaling the proper - // feature bit. + // Before we init the channel, we'll also check to see what commitment + // format we can use with this peer. This is dependent on *both* us and + // the remote peer are signaling the proper feature bit. commitType := commitmentType( peer.LocalFeatures(), peer.RemoteFeatures(), ) @@ -3116,7 +3116,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { case chainreg.LitecoinChain: ourDustLimit = chainreg.DefaultLitecoinDustLimit } - fndgLog.Infof("Initiating fundingRequest(local_amt=%v "+ "(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+ "dust_limit=%v, min_confs=%v)", localAmt, msg.subtractFees, @@ -3185,10 +3184,9 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // wallet doesn't have enough funds to commit to this channel, then the // request will fail, and be aborted. // - // Before we init the channel, we'll also check to see if we've - // negotiated the new tweakless commitment format. This is only the - // case if *both* us and the remote peer are signaling the proper - // feature bit. + // Before we init the channel, we'll also check to see what commitment + // format we can use with this peer. This is dependent on *both* us and + // the remote peer are signaling the proper feature bit. commitType := commitmentType( msg.peer.LocalFeatures(), msg.peer.RemoteFeatures(), ) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 45f5ebce03e..a5aca0ffb78 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -28,10 +28,11 @@ const ( // to_remote key is static. CommitmentTypeTweakless - // CommitmentTypeAnchors is a commitment type that is tweakless, and - // has extra anchor ouputs in order to bump the fee of the commitment - // transaction. - CommitmentTypeAnchors + // CommitmentTypeAnchorsZeroFeeHtlcTx is a commitment type that is an + // extension of the outdated CommitmentTypeAnchors, which in addition + // requires second-level HTLC transactions to be signed using a + // zero-fee. + CommitmentTypeAnchorsZeroFeeHtlcTx ) // String returns the name of the CommitmentType. @@ -41,8 +42,8 @@ func (c CommitmentType) String() string { return "legacy" case CommitmentTypeTweakless: return "tweakless" - case CommitmentTypeAnchors: - return "anchors" + case CommitmentTypeAnchorsZeroFeeHtlcTx: + return "anchors-zero-fee-second-level" default: return "invalid" } @@ -182,7 +183,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // Based on the channel type, we determine the initial commit weight // and fee. commitWeight := int64(input.CommitWeight) - if commitType == CommitmentTypeAnchors { + if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { commitWeight = input.AnchorCommitWeight } commitFee := commitFeePerKw.FeeForWeight(commitWeight) @@ -195,7 +196,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // The total fee paid by the initiator will be the commitment fee in // addition to the two anchor outputs. feeMSat := lnwire.NewMSatFromSatoshis(commitFee) - if commitType == CommitmentTypeAnchors { + if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) } @@ -280,8 +281,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // Both the tweakless type and the anchor type is tweakless, // hence set the bit. if commitType == CommitmentTypeTweakless || - commitType == CommitmentTypeAnchors { - + commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { chanType |= channeldb.SingleFunderTweaklessBit } else { chanType |= channeldb.SingleFunderBit @@ -315,9 +315,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.DualFunderBit } - // We are adding anchor outputs to our commitment. - if commitType == CommitmentTypeAnchors { + // We are adding anchor outputs to our commitment. We only support this + // in combination with zero-fee second-levels HTLCs. + if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { chanType |= channeldb.AnchorOutputsBit + chanType |= channeldb.ZeroHtlcTxFeeBit } // If the channel is meant to be frozen, then we'll set the frozen bit From c92c06400b074397271dd246503796b6ae010d65 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 14 Dec 2020 20:51:35 +0100 Subject: [PATCH 5/7] config+protocol: enable anchor commitments (zero-fee HTLC ) by default --- lncfg/protocol.go | 10 ++++++++++ lncfg/protocol_experimental_off.go | 6 ------ lncfg/protocol_experimental_on.go | 9 --------- server.go | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lncfg/protocol.go b/lncfg/protocol.go index 5e1be5e650a..880ae62cace 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -17,6 +17,10 @@ type ProtocolOptions struct { // (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"` + + // NoAnchors should be set if we don't want to support opening or accepting + // channels having the anchor commitment type. + NoAnchors bool `long:"no-anchors" description:"disable support for anchor commitments"` } // Wumbo returns true if lnd should permit the creation and acceptance of wumbo @@ -24,3 +28,9 @@ type ProtocolOptions struct { func (l *ProtocolOptions) Wumbo() bool { return l.WumboChans } + +// NoAnchorCommitments returns true if we have disabled support for the anchor +// commitment type. +func (l *ProtocolOptions) NoAnchorCommitments() bool { + return l.NoAnchors +} diff --git a/lncfg/protocol_experimental_off.go b/lncfg/protocol_experimental_off.go index 20d1ce48aff..a4f32c035a2 100644 --- a/lncfg/protocol_experimental_off.go +++ b/lncfg/protocol_experimental_off.go @@ -6,9 +6,3 @@ package lncfg // 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 index dac8cfeae77..d12fb9822db 100644 --- a/lncfg/protocol_experimental_on.go +++ b/lncfg/protocol_experimental_on.go @@ -5,13 +5,4 @@ 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/server.go b/server.go index 7b440ec0ebb..5291b566472 100644 --- a/server.go +++ b/server.go @@ -404,7 +404,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, featureMgr, err := feature.NewManager(feature.Config{ NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(), - NoAnchors: !cfg.ProtocolOptions.AnchorCommitments(), + NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), NoWumbo: !cfg.ProtocolOptions.Wumbo(), }) if err != nil { From d69a1a616d2c84874f5fe80c21d92cb07a247a2c Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 14 Dec 2020 21:07:12 +0100 Subject: [PATCH 6/7] sample config: add no-anchors --- sample-lnd.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-lnd.conf b/sample-lnd.conf index a633a495121..fc788a7b21f 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -938,8 +938,8 @@ litecoin.node=ltcd ; BTC ; protocol.wumbo-channels=true -; Set to enable experimental support for anchor commitments, won't work with watchtowers yet. -; protocol.anchors=true +; Set to disable support for anchor commitments +; protocol.no-anchors=true [db] ; The selected database backend. The current default backend is "bolt". lnd From 64659e615684f3179deebf0333df6666ff2d168b Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 14 Dec 2020 21:23:16 +0100 Subject: [PATCH 7/7] protocol+rpctest: disable anchors by default for itests --- lncfg/protocol.go | 2 ++ lncfg/protocol_rpctest.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 lncfg/protocol_rpctest.go diff --git a/lncfg/protocol.go b/lncfg/protocol.go index 880ae62cace..afc129680fc 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -1,3 +1,5 @@ +// +build !rpctest + package lncfg // ProtocolOptions is a struct that we use to be able to test backwards diff --git a/lncfg/protocol_rpctest.go b/lncfg/protocol_rpctest.go new file mode 100644 index 00000000000..037aec7775e --- /dev/null +++ b/lncfg/protocol_rpctest.go @@ -0,0 +1,38 @@ +// +build rpctest + +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"` + + // Anchors enables anchor commitments. + // TODO(halseth): transition itests to anchors instead! + Anchors bool `long:"anchors" description:"enable support for anchor commitments"` +} + +// Wumbo returns true if lnd should permit the creation and acceptance of wumbo +// channels. +func (l *ProtocolOptions) Wumbo() bool { + return l.WumboChans +} + +// NoAnchorCommitments returns true if we have disabled support for the anchor +// commitment type. +func (l *ProtocolOptions) NoAnchorCommitments() bool { + return !l.Anchors +}