Skip to content

Commit

Permalink
funding+lnwallet: finish hook up new aux funding flow
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Roasbeef authored and guggero committed May 1, 2024
1 parent 078f756 commit 8a7167a
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 48 deletions.
43 changes: 37 additions & 6 deletions funding/aux_funding.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package funding

import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/protofsm"
Expand All @@ -17,16 +18,46 @@ type AuxFundingController interface {
// handle custom messages specific to the funding type.
protofsm.MsgEndpoint

// DescPendingChanID takes a pending channel ID, that may already be
// DescFromPendingChanID takes a pending channel ID, that may already be
// known due to prior custom channel messages, and maybe returns an aux
// funding desc which can be used to modify how a channel is funded.
//
// TODO(roasbeef): erorr on validation if fail due to invalid root
// match?
DescFromPendingChanID(PendingChanID) fn.Option[lnwallet.AuxFundingDesc]
DescFromPendingChanID(pid PendingChanID,
openChan *channeldb.OpenChannel,
localKeyRing, remoteKeyRing lnwallet.CommitmentKeyRing,
initiator bool) (fn.Option[lnwallet.AuxFundingDesc], error)

// DeriveTapscriptRoot takes a pending channel ID and maybe returns a
// tapscript root that should be used when creating any musig2 sessions
// for a channel.
DeriveTapscriptRoot(PendingChanID) fn.Option[chainhash.Hash]
DeriveTapscriptRoot(PendingChanID) (fn.Option[chainhash.Hash], error)
}

// descFromPendingChanID takes a pending channel ID, that may already be
// known due to prior custom channel messages, and maybe returns an aux
// funding desc which can be used to modify how a channel is funded.
func descFromPendingChanID(controller fn.Option[AuxFundingController],
chanID PendingChanID, openChan *channeldb.OpenChannel,
localKeyRing, remoteKeyRing lnwallet.CommitmentKeyRing,
initiator bool) (fn.Option[lnwallet.AuxFundingDesc], error) {

if controller.IsNone() {
return fn.None[lnwallet.AuxFundingDesc](), nil
}

return controller.UnsafeFromSome().DescFromPendingChanID(
chanID, openChan, localKeyRing, remoteKeyRing, initiator,
)
}

// deriveTapscriptRoot takes a pending channel ID and maybe returns a
// tapscript root that should be used when creating any musig2 sessions
// for a channel.
func deriveTapscriptRoot(controller fn.Option[AuxFundingController],
chanID PendingChanID) (fn.Option[chainhash.Hash], error) {

if controller.IsNone() {
return fn.None[chainhash.Hash](), nil
}

return controller.UnsafeFromSome().DeriveTapscriptRoot(chanID)
}
84 changes: 61 additions & 23 deletions funding/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1622,11 +1621,14 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
// At this point, if we have an AuxFundingController active, we'll
// check to see if we have a special tapscript root to use in our
// musig2 funding output.
tapscriptRoot := fn.MapOption(
func(a AuxFundingController) fn.Option[chainhash.Hash] {
return a.DeriveTapscriptRoot(msg.PendingChannelID)
},
)(f.cfg.AuxFundingController)
tapscriptRoot, err := deriveTapscriptRoot(
f.cfg.AuxFundingController, msg.PendingChannelID,
)
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
log.Error(err)
f.failFundingFlow(peer, cid, err)
}

req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
Expand All @@ -1644,7 +1646,7 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
ZeroConf: zeroConf,
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
TapscriptRoot: fn.FlattenOption(tapscriptRoot),
TapscriptRoot: tapscriptRoot,
}

reservation, err := f.cfg.Wallet.InitChannelReservation(req)
Expand Down Expand Up @@ -2241,10 +2243,27 @@ 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.
chanState := resCtx.reservation.ChanState()
localKeys, remoteKeys := resCtx.reservation.CommitmentKeyRings()
auxFundingDesc, err := descFromPendingChanID(
f.cfg.AuxFundingController, cid.tempChanID, chanState,
*localKeys, *remoteKeys, true,
)
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
Expand All @@ -2253,8 +2272,8 @@ func (f *Manager) waitForPsbt(intent *chanfunding.PsbtIntent,
// We are now ready to continue the funding flow.
f.continueFundingAccept(resCtx, cid)

// Handle a server shutdown as well because the reservation won't
// survive a restart as it's in memory only.
// Handle a server shutdown as well because the reservation won't
// survive a restart as it's in memory only.
case <-f.quit:
log.Errorf("Unable to handle funding accept message "+
"for peer_key=%x, pending_chan_id=%x: funding manager "+
Expand Down Expand Up @@ -2308,6 +2327,10 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx,
// funding flow fails.
cid.setChanID(channelID)

// Now that we're ready to resume the funding flow, we'll call into the
// aux controller with the final funding details so we can obtain the
// funding descs we need.

// Send the FundingCreated msg.
fundingCreated := &lnwire.FundingCreated{
PendingChannelID: cid.tempChanID,
Expand Down Expand Up @@ -2370,7 +2393,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)
Expand Down Expand Up @@ -2402,16 +2424,33 @@ 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.
chanState := resCtx.reservation.ChanState()
localKeys, remoteKeys := resCtx.reservation.CommitmentKeyRings()
auxFundingDesc, err := descFromPendingChanID(
f.cfg.AuxFundingController, cid.tempChanID, chanState,
*localKeys, *remoteKeys, true,
)
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 AuxFundiner 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
Expand Down Expand Up @@ -2722,9 +2761,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{
Expand Down Expand Up @@ -4429,7 +4465,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
}
Expand Down Expand Up @@ -4622,11 +4657,14 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// At this point, if we have an AuxFundingController active, we'll
// check to see if we have a special tapscript root to use in our
// musig2 funding output.
tapscriptRoot := fn.MapOption(
func(a AuxFundingController) fn.Option[chainhash.Hash] {
return a.DeriveTapscriptRoot(chanID)
},
)(f.cfg.AuxFundingController)
tapscriptRoot, err := deriveTapscriptRoot(
f.cfg.AuxFundingController, chanID,
)
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
msg.Err <- err
return
}

req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
Expand All @@ -4651,7 +4689,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
Memo: msg.Memo,
TapscriptRoot: fn.FlattenOption(tapscriptRoot),
TapscriptRoot: tapscriptRoot,
}

reservation, err := f.cfg.Wallet.InitChannelReservation(req)
Expand Down
56 changes: 48 additions & 8 deletions lnwallet/reservation.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,14 @@ type ChannelReservation struct {

fundingIntent chanfunding.Intent

// initAuxLeaves is an optional set of aux commitment leaves that'll
// modify the way we construct the commitment transaction, in
// localInitAuxLeaves is an optional set of aux commitment leaves
// that'll modify the way we construct the commitment transaction, in
// particular the tapscript leaves.
initAuxLeaves fn.Option[CommitAuxLeaves]
localInitAuxLeaves fn.Option[CommitAuxLeaves]

Check failure on line 224 in lnwallet/reservation.go

View workflow job for this annotation

GitHub Actions / lint code

field `localInitAuxLeaves` is unused (unused)

// remoteInitAuxLeaves is an optional set of aux commitment leaves for
// the remote party.
remoteInitAuxLeaves fn.Option[CommitAuxLeaves]

Check failure on line 228 in lnwallet/reservation.go

View workflow job for this annotation

GitHub Actions / lint code

field `remoteInitAuxLeaves` is unused (unused)

// nextRevocationKeyLoc stores the key locator information for this
// channel.
Expand Down Expand Up @@ -608,12 +612,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,
}
Expand Down Expand Up @@ -715,8 +722,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)
Expand All @@ -726,6 +735,7 @@ func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoi
fundingOutpoint: fundingPoint,
theirCommitmentSig: commitSig,
completeChan: completeChan,
auxFundingDesc: auxFundingDesc,
err: errChan,
}

Expand Down Expand Up @@ -811,6 +821,36 @@ 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.
func (r *ChannelReservation) CommitmentKeyRings() (*CommitmentKeyRing, *CommitmentKeyRing) {
r.RLock()
defer r.RUnlock()

chanType := r.partialState.ChanType
ourChanCfg := r.ourContribution.ChannelConfig
theirChanCfg := r.theirContribution.ChannelConfig

localKeys := DeriveCommitmentKeys(
r.ourContribution.FirstCommitmentPoint, true, chanType,
ourChanCfg, theirChanCfg,
)

remoteKeys := DeriveCommitmentKeys(
r.theirContribution.FirstCommitmentPoint, false, chanType,
ourChanCfg, theirChanCfg,
)

return localKeys, remoteKeys
}

// VerifyConstraints is a helper function that can be used to check the sanity
// of various channel constraints.
func VerifyConstraints(c *channeldb.ChannelConstraints,
Expand Down
2 changes: 2 additions & 0 deletions lnwallet/test/test_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -936,6 +937,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")

Expand Down
Loading

0 comments on commit 8a7167a

Please sign in to comment.