mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-29 15:11:09 +02:00
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:
51
funding/aux_funding.go
Normal file
51
funding/aux_funding.go
Normal 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
|
||||
}
|
@@ -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,
|
||||
|
@@ -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")
|
||||
|
||||
|
Reference in New Issue
Block a user