From bcb66585d4f828b2cf182585975c9456f9a5c306 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 17 Apr 2024 23:35:44 -0700 Subject: [PATCH] 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. --- funding/manager.go | 64 +++++++++++++++++----- lnwallet/reservation.go | 96 ++++++++++++++++++++++++++++++--- lnwallet/test/test_interface.go | 2 + lnwallet/wallet.go | 38 ++++++++++++- 4 files changed, 181 insertions(+), 19 deletions(-) diff --git a/funding/manager.go b/funding/manager.go index 3d3ed4c6f..cb1ec04b9 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -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 } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 1f0000e8e..7f1a89c22 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -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, diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index bcb396caf..4a02f0324 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -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") diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 554f21db9..d72a09da1 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -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)