Merge pull request #9072 from lightningnetwork/extract-part3-from-staging-branch

[custom channels 3/5]: Extract PART3 from mega staging branch
This commit is contained in:
Oliver Gugger
2024-09-19 01:20:55 -06:00
committed by GitHub
40 changed files with 1926 additions and 162 deletions

51
funding/aux_funding.go Normal file
View File

@@ -0,0 +1,51 @@
package funding
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/msgmux"
)
// AuxFundingDescResult is a type alias for a function that returns an optional
// aux funding desc.
type AuxFundingDescResult = fn.Result[fn.Option[lnwallet.AuxFundingDesc]]
// AuxTapscriptResult is a type alias for a function that returns an optional
// tapscript root.
type AuxTapscriptResult = fn.Result[fn.Option[chainhash.Hash]]
// AuxFundingController permits the implementation of the funding of custom
// channels types. The controller serves as a MsgEndpoint which allows it to
// intercept custom messages, or even the regular funding messages. The
// controller might also pass along an aux funding desc based on an existing
// pending channel ID.
type AuxFundingController interface {
// Endpoint is the embedded interface that signals that the funding
// controller is also a message endpoint. This'll allow it to handle
// custom messages specific to the funding type.
msgmux.Endpoint
// 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.
DescFromPendingChanID(pid PendingChanID, openChan lnwallet.AuxChanState,
keyRing lntypes.Dual[lnwallet.CommitmentKeyRing],
initiator bool) AuxFundingDescResult
// 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) AuxTapscriptResult
// ChannelReady is called when a channel has been fully opened (multiple
// confirmations) and is ready to be used. This can be used to perform
// any final setup or cleanup.
ChannelReady(openChan lnwallet.AuxChanState) error
// ChannelFinalized is called when a channel has been fully finalized.
// In this state, we've received the commitment sig from the remote
// party, so we are safe to broadcast the funding transaction.
ChannelFinalized(PendingChanID) error
}

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
@@ -549,6 +548,16 @@ type Config struct {
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// AuxFundingController is an optional controller that can be used to
// modify the way we handle certain custom channel types. It's also
// able to automatically handle new custom protocol messages related to
// the funding process.
AuxFundingController fn.Option[AuxFundingController]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
}
// Manager acts as an orchestrator/bridge between the wallet's
@@ -1078,6 +1087,9 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel,
f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
f.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
// We create the state-machine object which wraps the database state.
lnChannel, err := lnwallet.NewLightningChannel(
@@ -1256,8 +1268,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.
@@ -1626,6 +1638,23 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
return
}
// 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
// MuSig funding output.
tapscriptRoot, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxTapscriptResult {
return c.DeriveTapscriptRoot(msg.PendingChannelID)
},
).Unpack()
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
log.Error(err)
f.failFundingFlow(peer, cid, err)
return
}
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
PendingChanID: msg.PendingChannelID,
@@ -1642,6 +1671,7 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
ZeroConf: zeroConf,
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
TapscriptRoot: tapscriptRoot,
}
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
@@ -1898,6 +1928,8 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
log.Debugf("Remote party accepted commitment rendering params: %v",
lnutils.SpewLogClosure(params))
reservation.SetState(lnwallet.SentAcceptChannel)
// With the initiator's contribution recorded, respond with our
// contribution in the next message of the workflow.
fundingAccept := lnwire.AcceptChannel{
@@ -1958,6 +1990,10 @@ func (f *Manager) funderProcessAcceptChannel(peer lnpeer.Peer,
// Update the timestamp once the fundingAcceptMsg has been handled.
defer resCtx.updateTimestamp()
if resCtx.reservation.State() != lnwallet.SentOpenChannel {
return
}
log.Infof("Recv'd fundingResponse for pending_id(%x)",
pendingChanID[:])
@@ -2261,10 +2297,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
@@ -2359,6 +2419,8 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx,
}
}
resCtx.reservation.SetState(lnwallet.SentFundingCreated)
if err := resCtx.peer.SendMessage(true, fundingCreated); err != nil {
log.Errorf("Unable to send funding complete message: %v", err)
f.failFundingFlow(resCtx.peer, cid, err)
@@ -2390,11 +2452,14 @@ 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)
if resCtx.reservation.State() != lnwallet.SentAcceptChannel {
return
}
// Create the channel identifier without setting the active channel ID.
cid := newChanIdentifier(pendingChanID)
@@ -2422,16 +2487,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
@@ -2632,6 +2719,14 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer,
return
}
if resCtx.reservation.State() != lnwallet.SentFundingCreated {
err := fmt.Errorf("unable to find reservation for chan_id=%x",
msg.ChanID)
f.failFundingFlow(peer, cid, err)
return
}
// Create an entry in the local discovery map so we can ensure that we
// process the channel confirmation fully before we receive a
// channel_ready message.
@@ -2727,6 +2822,21 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer,
}
}
// Before we proceed, if we have a funding hook that wants a
// notification that it's safe to broadcast the funding transaction,
// then we'll send that now.
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelFinalized(cid.tempChanID)
},
)
if err != nil {
log.Errorf("Failed to inform aux funding controller about "+
"ChannelPoint(%v) being finalized: %v", fundingPoint,
err)
}
// Now that we have a finalized reservation for this funding flow,
// we'll send the to be active channel to the ChainArbitrator so it can
// watch for any on-chain actions before the channel has fully
@@ -2742,9 +2852,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{
@@ -3450,6 +3557,7 @@ func (f *Manager) addToGraph(completeChan *channeldb.OpenChannel,
errChan := f.cfg.SendAnnouncement(
ann.chanAnn, discovery.ChannelCapacity(completeChan.Capacity),
discovery.ChannelPoint(completeChan.FundingOutpoint),
discovery.TapscriptRoot(completeChan.TapscriptRoot),
)
select {
case err := <-errChan:
@@ -3646,7 +3754,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
@@ -3977,6 +4085,26 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen
PubNonce: remoteNonce,
}),
)
// Inform the aux funding controller that the liquidity in the
// custom channel is now ready to be advertised. We potentially
// haven't sent our own channel ready message yet, but other
// than that the channel is ready to count toward available
// liquidity.
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelReady(
lnwallet.NewAuxChanState(channel),
)
},
)
if err != nil {
cid := newChanIdentifier(msg.ChanID)
f.sendWarning(peer, cid, err)
return
}
}
// The channel_ready message contains the next commitment point we'll
@@ -4063,6 +4191,19 @@ func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel,
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"added to graph", chanID, scid)
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelReady(
lnwallet.NewAuxChanState(channel),
)
},
)
if err != nil {
return fmt.Errorf("failed notifying aux funding controller "+
"about channel ready: %w", err)
}
// Give the caller a final update notifying them that the channel is
fundingPoint := channel.FundingOutpoint
cp := &lnrpc.ChannelPoint{
@@ -4376,9 +4517,9 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
//
// We can pass in zeroes for the min and max htlc policy, because we
// only use the channel announcement message from the returned struct.
ann, err := f.newChanAnnouncement(localIDKey, remoteIDKey,
localFundingKey, remoteFundingKey, shortChanID, chanID,
0, 0, nil, chanType,
ann, err := f.newChanAnnouncement(
localIDKey, remoteIDKey, localFundingKey, remoteFundingKey,
shortChanID, chanID, 0, 0, nil, chanType,
)
if err != nil {
log.Errorf("can't generate channel announcement: %v", err)
@@ -4444,7 +4585,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
}
@@ -4634,6 +4774,23 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
scidFeatureVal = true
}
// 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, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxTapscriptResult {
return c.DeriveTapscriptRoot(chanID)
},
).Unpack()
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
log.Error(err)
msg.Err <- err
return
}
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
PendingChanID: chanID,
@@ -4673,6 +4830,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
Memo: msg.Memo,
TapscriptRoot: tapscriptRoot,
}
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
@@ -4824,6 +4982,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
log.Infof("Starting funding workflow with %v for pending_id(%x), "+
"committype=%v", msg.Peer.Address(), chanID, commitType)
reservation.SetState(lnwallet.SentOpenChannel)
fundingOpen := lnwire.OpenChannel{
ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash,
PendingChannelID: chanID,

View File

@@ -567,6 +567,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
AuxLeafStore: fn.Some[lnwallet.AuxLeafStore](
&lnwallet.MockAuxLeafStore{},
),
AuxSigner: fn.Some[lnwallet.AuxSigner](
&lnwallet.MockAuxSigner{},
),
}
for _, op := range options {
@@ -677,6 +680,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
AliasManager: oldCfg.AliasManager,
AuxLeafStore: oldCfg.AuxLeafStore,
AuxSigner: oldCfg.AuxSigner,
})
require.NoError(t, err, "failed recreating aliceFundingManager")