funding+lnwallet: support funding new script enforced leased channels

This commit is contained in:
Wilmer Paulino 2021-07-14 16:29:29 -07:00 committed by Olaoluwa Osuntokun
parent f38c6d6662
commit 564ec0fd9b
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
4 changed files with 128 additions and 14 deletions

View file

@ -50,6 +50,23 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType,
channelFeatures := lnwire.RawFeatureVector(channelType) channelFeatures := lnwire.RawFeatureVector(channelType)
switch { switch {
// Lease script enforcement + anchors zero fee + static remote key
// features only.
case channelFeatures.OnlyContains(
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key features only. // Anchors zero fee + static remote key features only.
case channelFeatures.OnlyContains( case channelFeatures.OnlyContains(
lnwire.AnchorsZeroFeeHtlcTxRequired, lnwire.AnchorsZeroFeeHtlcTxRequired,

View file

@ -1364,6 +1364,28 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
} }
reservation.SetOurUpfrontShutdown(shutdown) reservation.SetOurUpfrontShutdown(shutdown)
// If a script enforced channel lease is being proposed, we'll need to
// validate its custom TLV records.
if commitType == lnwallet.CommitmentTypeScriptEnforcedLease {
if msg.LeaseExpiry == nil {
err := errors.New("missing lease expiry")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
// If we had a shim registered for this channel prior to
// receiving its corresponding OpenChannel message, then we'll
// validate the proposed LeaseExpiry against what was registered
// in our shim.
if reservation.LeaseExpiry() != 0 {
if uint32(*msg.LeaseExpiry) != reservation.LeaseExpiry() {
err := errors.New("lease expiry mismatch")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
}
}
log.Infof("Requiring %v confirmations for pendingChan(%x): "+ log.Infof("Requiring %v confirmations for pendingChan(%x): "+
"amt=%v, push_amt=%v, committype=%v, upfrontShutdown=%x", numConfsReq, "amt=%v, push_amt=%v, committype=%v, upfrontShutdown=%x", numConfsReq,
msg.PendingChannelID, amt, msg.PushAmount, msg.PendingChannelID, amt, msg.PushAmount,
@ -1499,6 +1521,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
UpfrontShutdownScript: ourContribution.UpfrontShutdown, UpfrontShutdownScript: ourContribution.UpfrontShutdown,
ChannelType: msg.ChannelType, ChannelType: msg.ChannelType,
LeaseExpiry: msg.LeaseExpiry,
} }
if err := peer.SendMessage(true, &fundingAccept); err != nil { if err := peer.SendMessage(true, &fundingAccept); err != nil {
@ -1530,11 +1553,12 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
log.Infof("Recv'd fundingResponse for pending_id(%x)", log.Infof("Recv'd fundingResponse for pending_id(%x)",
pendingChanID[:]) pendingChanID[:])
// We'll want to quickly check that ChannelType echoed by the channel // Perform some basic validation of any custom TLV records included.
// request recipient matches what we proposed.
// //
// TODO: Return errors as funding.Error to give context to remote peer? // TODO: Return errors as funding.Error to give context to remote peer?
if resCtx.channelType != nil { if resCtx.channelType != nil {
// We'll want to quickly check that the ChannelType echoed by
// the channel request recipient matches what we proposed.
if msg.ChannelType == nil { if msg.ChannelType == nil {
err := errors.New("explicit channel type not echoed back") err := errors.New("explicit channel type not echoed back")
f.failFundingFlow(peer, msg.PendingChannelID, err) f.failFundingFlow(peer, msg.PendingChannelID, err)
@ -1547,6 +1571,21 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
f.failFundingFlow(peer, msg.PendingChannelID, err) f.failFundingFlow(peer, msg.PendingChannelID, err)
return return
} }
// We'll want to do the same with the LeaseExpiry if one should
// be set.
if resCtx.reservation.LeaseExpiry() != 0 {
if msg.LeaseExpiry == nil {
err := errors.New("lease expiry not echoed back")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
if uint32(*msg.LeaseExpiry) != resCtx.reservation.LeaseExpiry() {
err := errors.New("lease expiry mismatch")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
}
} else if msg.ChannelType != nil { } else if msg.ChannelType != nil {
err := errors.New("received unexpected channel type") err := errors.New("received unexpected channel type")
f.failFundingFlow(peer, msg.PendingChannelID, err) f.failFundingFlow(peer, msg.PendingChannelID, err)
@ -3206,9 +3245,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// For anchor channels cap the initial commit fee rate at our defined // For anchor channels cap the initial commit fee rate at our defined
// maximum. // maximum.
if commitType == lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx && if commitType.HasAnchors() &&
commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate { commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate {
commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate
} }
@ -3311,6 +3349,14 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// remote party. // remote party.
chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit) chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit)
// When opening a script enforced channel lease, include the required
// expiry TLV record in our proposal.
var leaseExpiry *lnwire.LeaseExpiry
if commitType == lnwallet.CommitmentTypeScriptEnforcedLease {
leaseExpiry = new(lnwire.LeaseExpiry)
*leaseExpiry = lnwire.LeaseExpiry(reservation.LeaseExpiry())
}
log.Infof("Starting funding workflow with %v for pending_id(%x), "+ log.Infof("Starting funding workflow with %v for pending_id(%x), "+
"committype=%v", msg.Peer.Address(), chanID, commitType) "committype=%v", msg.Peer.Address(), chanID, commitType)
@ -3335,6 +3381,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
ChannelFlags: channelFlags, ChannelFlags: channelFlags,
UpfrontShutdownScript: shutdown, UpfrontShutdownScript: shutdown,
ChannelType: msg.ChannelType, ChannelType: msg.ChannelType,
LeaseExpiry: leaseExpiry,
} }
if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil {
e := fmt.Errorf("unable to send funding request message: %v", e := fmt.Errorf("unable to send funding request message: %v",

View file

@ -1,6 +1,7 @@
package lnwallet package lnwallet
import ( import (
"errors"
"net" "net"
"sync" "sync"
@ -34,8 +35,40 @@ const (
// requires second-level HTLC transactions to be signed using a // requires second-level HTLC transactions to be signed using a
// zero-fee. // zero-fee.
CommitmentTypeAnchorsZeroFeeHtlcTx CommitmentTypeAnchorsZeroFeeHtlcTx
// CommitmentTypeScriptEnforcedLease is a commitment type that builds
// upon CommitmentTypeTweakless and CommitmentTypeAnchorsZeroFeeHtlcTx,
// which in addition requires a CLTV clause to spend outputs paying to
// the channel initiator. This is intended for use on leased channels to
// guarantee that the channel initiator has no incentives to close a
// leased channel before its maturity date.
CommitmentTypeScriptEnforcedLease
) )
// HasStaticRemoteKey returns whether the commitment type supports remote
// outputs backed by static keys.
func (c CommitmentType) HasStaticRemoteKey() bool {
switch c {
case CommitmentTypeTweakless,
CommitmentTypeAnchorsZeroFeeHtlcTx,
CommitmentTypeScriptEnforcedLease:
return true
default:
return false
}
}
// HasAnchors returns whether the commitment type supports anchor outputs.
func (c CommitmentType) HasAnchors() bool {
switch c {
case CommitmentTypeAnchorsZeroFeeHtlcTx,
CommitmentTypeScriptEnforcedLease:
return true
default:
return false
}
}
// String returns the name of the CommitmentType. // String returns the name of the CommitmentType.
func (c CommitmentType) String() string { func (c CommitmentType) String() string {
switch c { switch c {
@ -45,6 +78,8 @@ func (c CommitmentType) String() string {
return "tweakless" return "tweakless"
case CommitmentTypeAnchorsZeroFeeHtlcTx: case CommitmentTypeAnchorsZeroFeeHtlcTx:
return "anchors-zero-fee-second-level" return "anchors-zero-fee-second-level"
case CommitmentTypeScriptEnforcedLease:
return "script-enforced-lease"
default: default:
return "invalid" return "invalid"
} }
@ -188,8 +223,8 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// Based on the channel type, we determine the initial commit weight // Based on the channel type, we determine the initial commit weight
// and fee. // and fee.
commitWeight := int64(input.CommitWeight) commitWeight := int64(input.CommitWeight)
if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { if commitType.HasAnchors() {
commitWeight = input.AnchorCommitWeight commitWeight = int64(input.AnchorCommitWeight)
} }
commitFee := commitFeePerKw.FeeForWeight(commitWeight) commitFee := commitFeePerKw.FeeForWeight(commitWeight)
@ -201,7 +236,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// The total fee paid by the initiator will be the commitment fee in // The total fee paid by the initiator will be the commitment fee in
// addition to the two anchor outputs. // addition to the two anchor outputs.
feeMSat := lnwire.NewMSatFromSatoshis(commitFee) feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { if commitType.HasAnchors() {
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
} }
@ -288,8 +323,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 { if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
// Both the tweakless type and the anchor type is tweakless, // Both the tweakless type and the anchor type is tweakless,
// hence set the bit. // hence set the bit.
if commitType == CommitmentTypeTweakless || if commitType.HasStaticRemoteKey() {
commitType == CommitmentTypeAnchorsZeroFeeHtlcTx {
chanType |= channeldb.SingleFunderTweaklessBit chanType |= channeldb.SingleFunderTweaklessBit
} else { } else {
chanType |= channeldb.SingleFunderBit chanType |= channeldb.SingleFunderBit
@ -325,14 +359,20 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// We are adding anchor outputs to our commitment. We only support this // We are adding anchor outputs to our commitment. We only support this
// in combination with zero-fee second-levels HTLCs. // in combination with zero-fee second-levels HTLCs.
if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { if commitType.HasAnchors() {
chanType |= channeldb.AnchorOutputsBit chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.ZeroHtlcTxFeeBit chanType |= channeldb.ZeroHtlcTxFeeBit
} }
// If the channel is meant to be frozen, then we'll set the frozen bit // Set the appropriate LeaseExpiration/Frozen bit based on the
// now so once the channel is open, it can be interpreted properly. // reservation parameters.
if thawHeight != 0 { if commitType == CommitmentTypeScriptEnforcedLease {
if thawHeight == 0 {
return nil, errors.New("missing absolute expiration " +
"for script enforced lease commitment type")
}
chanType |= channeldb.LeaseExpirationBit
} else if thawHeight > 0 {
chanType |= channeldb.FrozenBit chanType |= channeldb.FrozenBit
} }
@ -719,6 +759,16 @@ func (r *ChannelReservation) Capacity() btcutil.Amount {
return r.partialState.Capacity return r.partialState.Capacity
} }
// LeaseExpiry returns the absolute expiration height for a leased channel using
// the script enforced commitment type. A zero value is returned when the
// channel is not using a script enforced lease commitment type.
func (r *ChannelReservation) LeaseExpiry() uint32 {
if !r.partialState.ChanType.HasLeaseExpiration() {
return 0
}
return r.partialState.ThawHeight
}
// Cancel abandons this channel reservation. This method should be called in // Cancel abandons this channel reservation. This method should be called in
// the scenario that communications with the counterparty break down. Upon // the scenario that communications with the counterparty break down. Upon
// cancellation, all resources previously reserved for this pending payment // cancellation, all resources previously reserved for this pending payment

View file

@ -818,7 +818,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
// funding tx ready, so this will always pass. We'll do another check // funding tx ready, so this will always pass. We'll do another check
// when the PSBT has been verified. // when the PSBT has been verified.
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0 isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
hasAnchors := req.CommitType == CommitmentTypeAnchorsZeroFeeHtlcTx hasAnchors := req.CommitType.HasAnchors()
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors) err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
if err != nil { if err != nil {
fundingIntent.Cancel() fundingIntent.Cancel()