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 2603b4443..8a1a8fb9b 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)