funding+lnwallet: finish hook up new aux funding flow

For the initiator, once we get the signal that the PSBT has been
finalized, we'll call into the aux funder to get the funding desc. For
the responder, once we receive the funding_created message, we'll do the
same.

We now also have local+remote aux leaves for the commitment transaction.

Some old TODO comments that in retrospect aren't required anymore are
removed as well.
This commit is contained in:
Olaoluwa Osuntokun 2024-04-17 23:35:44 -07:00 committed by Oliver Gugger
parent 7ec48a5054
commit bcb66585d4
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
4 changed files with 181 additions and 19 deletions

View File

@ -99,7 +99,6 @@ const (
// you and limitless channel size (apart from 21 million cap).
MaxBtcFundingAmountWumbo = btcutil.Amount(1000000000)
// TODO(roasbeef): tune.
msgBufferSize = 50
// MaxWaitNumBlocksFundingConf is the maximum number of blocks to wait
@ -1262,8 +1261,8 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
// advancePendingChannelState waits for a pending channel's funding tx to
// confirm, and marks it open in the database when that happens.
func (f *Manager) advancePendingChannelState(
channel *channeldb.OpenChannel, pendingChanID PendingChanID) error {
func (f *Manager) advancePendingChannelState(channel *channeldb.OpenChannel,
pendingChanID PendingChanID) error {
if channel.IsZeroConf() {
// Persist the alias to the alias database.
@ -2285,10 +2284,34 @@ func (f *Manager) waitForPsbt(intent *chanfunding.PsbtIntent,
return
}
// At this point, we'll see if there's an AuxFundingDesc we
// need to deliver so the funding process can continue
// properly.
auxFundingDesc, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxFundingDescResult {
return c.DescFromPendingChanID(
cid.tempChanID,
lnwallet.NewAuxChanState(
resCtx.reservation.ChanState(),
),
resCtx.reservation.CommitmentKeyRings(),
true,
)
},
).Unpack()
if err != nil {
failFlow("error continuing PSBT flow", err)
return
}
// A non-nil error means we can continue the funding flow.
// Notify the wallet so it can prepare everything we need to
// continue.
err = resCtx.reservation.ProcessPsbt()
//
// We'll also pass along the aux funding controller as well,
// which may be used to help process the finalized PSBT.
err = resCtx.reservation.ProcessPsbt(auxFundingDesc)
if err != nil {
failFlow("error continuing PSBT flow", err)
return
@ -2414,7 +2437,6 @@ func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer,
// final funding transaction, as well as a signature for our version of
// the commitment transaction. So at this point, we can validate the
// initiator's commitment transaction, then send our own if it's valid.
// TODO(roasbeef): make case (p vs P) consistent throughout
fundingOut := msg.FundingPoint
log.Infof("completing pending_id(%x) with ChannelPoint(%v)",
pendingChanID[:], fundingOut)
@ -2446,16 +2468,38 @@ func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer,
}
}
// At this point, we'll see if there's an AuxFundingDesc we need to
// deliver so the funding process can continue properly.
auxFundingDesc, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxFundingDescResult {
return c.DescFromPendingChanID(
cid.tempChanID, lnwallet.NewAuxChanState(
resCtx.reservation.ChanState(),
), resCtx.reservation.CommitmentKeyRings(),
true,
)
},
).Unpack()
if err != nil {
log.Errorf("error continuing PSBT flow: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// With all the necessary data available, attempt to advance the
// funding workflow to the next stage. If this succeeds then the
// funding transaction will broadcast after our next message.
// CompleteReservationSingle will also mark the channel as 'IsPending'
// in the database.
//
// We'll also directly pass in the AuxFunding controller as well,
// which may be used by the reservation system to finalize funding our
// side.
completeChan, err := resCtx.reservation.CompleteReservationSingle(
&fundingOut, commitSig,
&fundingOut, commitSig, auxFundingDesc,
)
if err != nil {
// TODO(roasbeef): better error logging: peerID, channelID, etc.
log.Errorf("unable to complete single reservation: %v", err)
f.failFundingFlow(peer, cid, err)
return
@ -2766,9 +2810,6 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer,
// Send an update to the upstream client that the negotiation process
// is over.
//
// TODO(roasbeef): add abstraction over updates to accommodate
// long-polling, or SSE, etc.
upd := &lnrpc.OpenStatusUpdate{
Update: &lnrpc.OpenStatusUpdate_ChanPending{
ChanPending: &lnrpc.PendingUpdate{
@ -3670,7 +3711,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
// waitForZeroConfChannel is called when the state is addedToGraph with
// a zero-conf channel. This will wait for the real confirmation, add the
// confirmed SCID to the graph, and then announce after six confs.
// confirmed SCID to the router graph, and then announce after six confs.
func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error {
// First we'll check whether the channel is confirmed on-chain. If it
// is already confirmed, the chainntnfs subsystem will return with the
@ -4468,7 +4509,6 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
// InitFundingWorkflow sends a message to the funding manager instructing it
// to initiate a single funder workflow with the source peer.
// TODO(roasbeef): re-visit blocking nature..
func (f *Manager) InitFundingWorkflow(msg *InitFundingMsg) {
f.fundingRequests <- msg
}

View File

@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
@ -25,7 +26,7 @@ type CommitmentType int
const (
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
// to_remote key.
CommitmentTypeLegacy = iota
CommitmentTypeLegacy CommitmentType = iota
// CommitmentTypeTweakless is a newer commitment format where the
// to_remote key is static.
@ -100,6 +101,28 @@ func (c CommitmentType) String() string {
}
}
// ReservationState is a type that represents the current state of a channel
// reservation within the funding workflow.
type ReservationState int
const (
// WaitingToSend is the state either the funder/fundee is in after
// creating a reservation, but hasn't sent a message yet.
WaitingToSend ReservationState = iota
// SentOpenChannel is the state the funder is in after sending the
// OpenChannel message.
SentOpenChannel
// SentAcceptChannel is the state the fundee is in after sending the
// AcceptChannel message.
SentAcceptChannel
// SentFundingCreated is the state the funder is in after sending the
// FundingCreated message.
SentFundingCreated
)
// ChannelContribution is the primary constituent of the funding workflow
// within lnwallet. Each side first exchanges their respective contributions
// along with channel specific parameters like the min fee/KB. Once
@ -223,6 +246,8 @@ type ChannelReservation struct {
nextRevocationKeyLoc keychain.KeyLocator
musigSessions *MusigPairSession
state ReservationState
}
// NewChannelReservation creates a new channel reservation. This function is
@ -459,6 +484,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
reservationID: id,
wallet: wallet,
chanFunder: req.ChanFunder,
state: WaitingToSend,
}, nil
}
@ -470,6 +496,22 @@ func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
r.partialState.ShortChannelID = scid
}
// SetState sets the ReservationState.
func (r *ChannelReservation) SetState(state ReservationState) {
r.Lock()
defer r.Unlock()
r.state = state
}
// State returns the current ReservationState.
func (r *ChannelReservation) State() ReservationState {
r.RLock()
defer r.RUnlock()
return r.state
}
// SetNumConfsRequired sets the number of confirmations that are required for
// the ultimate funding transaction before the channel can be considered open.
// This is distinct from the main reservation workflow as it allows
@ -610,12 +652,15 @@ func (r *ChannelReservation) IsCannedShim() bool {
}
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
// construct the funding transaction. This method can be called once the PSBT is
// finalized and the signed transaction is available.
func (r *ChannelReservation) ProcessPsbt() error {
// construct the funding transaction. This method can be called once the PSBT
// is finalized and the signed transaction is available.
func (r *ChannelReservation) ProcessPsbt(
auxFundingDesc fn.Option[AuxFundingDesc]) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &continueContributionMsg{
auxFundingDesc: auxFundingDesc,
pendingFundingID: r.reservationID,
err: errChan,
}
@ -717,8 +762,10 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Sc
// available via the .OurSignatures() method. As this method should only be
// called as a response to a single funder channel, only a commitment signature
// will be populated.
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
commitSig input.Signature) (*channeldb.OpenChannel, error) {
func (r *ChannelReservation) CompleteReservationSingle(
fundingPoint *wire.OutPoint, commitSig input.Signature,
auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel,
error) {
errChan := make(chan error, 1)
completeChan := make(chan *channeldb.OpenChannel, 1)
@ -728,6 +775,7 @@ func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoi
fundingOutpoint: fundingPoint,
theirCommitmentSig: commitSig,
completeChan: completeChan,
auxFundingDesc: auxFundingDesc,
err: errChan,
}
@ -813,6 +861,42 @@ func (r *ChannelReservation) Cancel() error {
return <-errChan
}
// ChanState the current open channel state.
func (r *ChannelReservation) ChanState() *channeldb.OpenChannel {
r.RLock()
defer r.RUnlock()
return r.partialState
}
// CommitmentKeyRings returns the local+remote key ring used for the very first
// commitment transaction both parties.
//
//nolint:lll
func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] {
r.RLock()
defer r.RUnlock()
chanType := r.partialState.ChanType
ourChanCfg := r.ourContribution.ChannelConfig
theirChanCfg := r.theirContribution.ChannelConfig
localKeys := DeriveCommitmentKeys(
r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType,
ourChanCfg, theirChanCfg,
)
remoteKeys := DeriveCommitmentKeys(
r.theirContribution.FirstCommitmentPoint, lntypes.Remote,
chanType, ourChanCfg, theirChanCfg,
)
return lntypes.Dual[CommitmentKeyRing]{
Local: *localKeys,
Remote: *remoteKeys,
}
}
// VerifyConstraints is a helper function that can be used to check the sanity
// of various channel constraints.
func VerifyConstraints(bounds *channeldb.ChannelStateBounds,

View File

@ -34,6 +34,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
@ -940,6 +941,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
fundingPoint := aliceChanReservation.FundingOutpoint()
_, err = bobChanReservation.CompleteReservationSingle(
fundingPoint, aliceCommitSig,
fn.None[lnwallet.AuxFundingDesc](),
)
require.NoError(t, err, "bob unable to consume single reservation")

View File

@ -1844,6 +1844,26 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
return
}
chanState := pendingReservation.partialState
// If we have an aux funding desc, then we can use it to populate some
// of the optional, but opaque TLV blobs we'll carry for the channel.
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomFundingBlob
})(req.auxFundingDesc)
chanState.LocalCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomLocalCommitBlob
},
)(req.auxFundingDesc)
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomRemoteCommitBlob
},
)(req.auxFundingDesc)
ourContribution := pendingReservation.ourContribution
theirContribution := pendingReservation.theirContribution
chanPoint := pendingReservation.partialState.FundingOutpoint
@ -1902,7 +1922,6 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
// Store their current commitment point. We'll need this after the
// first state transition in order to verify the authenticity of the
// revocation.
chanState := pendingReservation.partialState
chanState.RemoteCurrentRevocation = theirContribution.FirstCommitmentPoint
// Create the txin to our commitment transaction; required to construct
@ -2349,6 +2368,23 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
defer pendingReservation.Unlock()
chanState := pendingReservation.partialState
// If we have an aux funding desc, then we can use it to populate some
// of the optional, but opaque TLV blobs we'll carry for the channel.
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomFundingBlob
})(req.auxFundingDesc)
chanState.LocalCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomLocalCommitBlob
},
)(req.auxFundingDesc)
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomRemoteCommitBlob
},
)(req.auxFundingDesc)
chanType := pendingReservation.partialState.ChanType
chanState.FundingOutpoint = *req.fundingOutpoint
fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil)