Merge pull request #4840 from halseth/anchors-zero-fee-secondlevel

[anchors] zero-fee HTLC secondlevel transactions
This commit is contained in:
Olaoluwa Osuntokun 2020-12-15 10:52:03 -08:00 committed by GitHub
commit d289a6ff78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 135 additions and 52 deletions

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

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

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

@ -43,7 +43,7 @@ var defaultSetDesc = setDesc{
SetNodeAnn: {}, // N
SetInvoice: {}, // 9
},
lnwire.AnchorsOptional: {
lnwire.AnchorsZeroFeeHtlcTxOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
},

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

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

@ -1,3 +1,5 @@
// +build !rpctest
package lncfg
// ProtocolOptions is a struct that we use to be able to test backwards
@ -17,6 +19,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 +30,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
}

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

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

38
lncfg/protocol_rpctest.go Normal file

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

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

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

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

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

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