diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 37e72fdee..41a2fcbb7 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -63,6 +64,10 @@ type Config struct { // state. ChanStateDB *channeldb.ChannelStateDB + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + // BlockCache is the main cache for storing block information. BlockCache *blockcache.BlockCache diff --git a/config_builder.go b/config_builder.go index bef59b9a0..7c399297e 100644 --- a/config_builder.go +++ b/config_builder.go @@ -105,7 +105,7 @@ type DatabaseBuilder interface { type WalletConfigBuilder interface { // BuildWalletConfig is responsible for creating or unlocking and then // fully initializing a wallet. - BuildWalletConfig(context.Context, *DatabaseInstances, + BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents, *rpcperms.InterceptorChain, []*ListenerWithSignal) (*chainreg.PartialChainControl, *btcwallet.Config, func(), error) @@ -120,14 +120,6 @@ type ChainControlBuilder interface { *btcwallet.Config) (*chainreg.ChainControl, func(), error) } -// AuxComponents is a set of auxiliary components that can be used by lnd for -// certain custom channel types. -type AuxComponents struct { - // MsgRouter is an optional message router that if set will be used in - // place of a new blank default message router. - MsgRouter fn.Option[msgmux.Router] -} - // ImplementationCfg is a struct that holds all configuration items for // components that can be implemented outside lnd itself. type ImplementationCfg struct { @@ -160,6 +152,18 @@ type ImplementationCfg struct { AuxComponents } +// AuxComponents is a set of auxiliary components that can be used by lnd for +// certain custom channel types. +type AuxComponents struct { + // AuxLeafStore is an optional data source that can be used by custom + // channels to fetch+store various data. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // MsgRouter is an optional message router that if set will be used in + // place of a new blank default message router. + MsgRouter fn.Option[msgmux.Router] +} + // DefaultWalletImpl is the default implementation of our normal, btcwallet // backed configuration. type DefaultWalletImpl struct { @@ -242,7 +246,8 @@ func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op { // // NOTE: This is part of the WalletConfigBuilder interface. func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, - dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain, + dbs *DatabaseInstances, aux *AuxComponents, + interceptorChain *rpcperms.InterceptorChain, grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl, *btcwallet.Config, func(), error) { @@ -562,6 +567,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, HeightHintDB: dbs.HeightHintDB, ChanStateDB: dbs.ChanStateDB.ChannelStateDB(), NeutrinoCS: neutrinoCS, + AuxLeafStore: aux.AuxLeafStore, ActiveNetParams: d.cfg.ActiveNetParams, FeeURL: d.cfg.FeeURL, Fee: &lncfg.Fee{ @@ -625,8 +631,9 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, // proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino // rebroadcaster client. -func proxyBlockEpoch(notifier chainntnfs.ChainNotifier, -) func() (*blockntfns.Subscription, error) { +func proxyBlockEpoch( + notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription, + error) { return func() (*blockntfns.Subscription, error) { blockEpoch, err := notifier.RegisterBlockEpochNtfn( @@ -717,6 +724,7 @@ func (d *DefaultWalletImpl) BuildChainControl( ChainIO: walletController, NetParams: *walletConfig.NetParams, CoinSelectionStrategy: walletConfig.CoinSelectionStrategy, + AuxLeafStore: partialChainControl.Cfg.AuxLeafStore, } // The broadcast is already always active for neutrino nodes, so we @@ -899,6 +907,10 @@ type DatabaseInstances struct { // for native SQL queries for tables that already support it. This may // be nil if the use-native-sql flag was not set. NativeSQLStore *sqldb.BaseDB + + // AuxLeafStore is an optional data source that can be used by custom + // channels to fetch+store various data. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] } // DefaultDatabaseBuilder is a type that builds the default database backends diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go index 6a1865444..5940ee25b 100644 --- a/contractcourt/breach_arbitrator_test.go +++ b/contractcourt/breach_arbitrator_test.go @@ -22,6 +22,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntest/channels" @@ -1590,6 +1591,7 @@ func testBreachSpends(t *testing.T, test breachTest) { // Notify the breach arbiter about the breach. retribution, err := lnwallet.NewBreachRetribution( alice.State(), height, 1, forceCloseTx, + fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create breach retribution") @@ -1799,6 +1801,7 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { // Notify the breach arbiter about the breach. retribution, err := lnwallet.NewBreachRetribution( alice.State(), height, uint32(blockHeight), forceCloseTx, + fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create breach retribution") diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 0cc4b111a..dbc97939a 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -217,6 +217,10 @@ type ChainArbitratorConfig struct { // meanwhile, turn `PaymentCircuit` into an interface or bring it to a // lower package. QueryIncomingCircuit func(circuit models.CircuitKey) *models.CircuitKey + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] } // ChainArbitrator is a sub-system that oversees the on-chain resolution of all @@ -299,8 +303,13 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, return nil, err } + var chanOpts []lnwallet.ChannelOpt + a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + chanMachine, err := lnwallet.NewLightningChannel( - a.c.cfg.Signer, channel, nil, + a.c.cfg.Signer, channel, nil, chanOpts..., ) if err != nil { return nil, err @@ -344,10 +353,15 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) return nil, err } + var chanOpts []lnwallet.ChannelOpt + a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + // Finally, we'll force close the channel completing // the force close workflow. chanMachine, err := lnwallet.NewLightningChannel( - a.c.cfg.Signer, channel, nil, + a.c.cfg.Signer, channel, nil, chanOpts..., ) if err != nil { return nil, err diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 66a61fe3b..4012f031d 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -193,6 +193,9 @@ type chainWatcherConfig struct { // obfuscater. This is used by the chain watcher to identify which // state was broadcast and confirmed on-chain. extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64 + + // auxLeafStore can be used to fetch information for custom channels. + auxLeafStore fn.Option[lnwallet.AuxLeafStore] } // chainWatcher is a system that's assigned to every active channel. The duty @@ -867,7 +870,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, spendHeight := uint32(commitSpend.SpendingHeight) retribution, err := lnwallet.NewBreachRetribution( c.cfg.chanState, broadcastStateNum, spendHeight, - commitSpend.SpendingTx, + commitSpend.SpendingTx, c.cfg.auxLeafStore, ) switch { @@ -1117,8 +1120,8 @@ func (c *chainWatcher) dispatchLocalForceClose( "detected", c.cfg.chanState.FundingOutpoint) forceClose, err := lnwallet.NewLocalForceCloseSummary( - c.cfg.chanState, c.cfg.signer, - commitSpend.SpendingTx, stateNum, + c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum, + c.cfg.auxLeafStore, ) if err != nil { return err @@ -1211,7 +1214,7 @@ func (c *chainWatcher) dispatchRemoteForceClose( // channel on-chain. uniClose, err := lnwallet.NewUnilateralCloseSummary( c.cfg.chanState, c.cfg.signer, commitSpend, - remoteCommit, commitPoint, + remoteCommit, commitPoint, c.cfg.auxLeafStore, ) if err != nil { return err diff --git a/funding/manager.go b/funding/manager.go index b22f2e691..6bb027725 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/discovery" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/graph" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -544,6 +545,10 @@ type Config struct { // backed funding flow to not use utxos still being swept by the sweeper // subsystem. IsSweeperOutpoint func(wire.OutPoint) bool + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] } // Manager acts as an orchestrator/bridge between the wallet's @@ -1069,9 +1074,14 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, } } + var chanOpts []lnwallet.ChannelOpt + f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + // We create the state-machine object which wraps the database state. lnChannel, err := lnwallet.NewLightningChannel( - nil, channel, nil, + nil, channel, nil, chanOpts..., ) if err != nil { log.Errorf("Unable to create LightningChannel(%v): %v", diff --git a/funding/manager_test.go b/funding/manager_test.go index c4c8b4f36..471d0209f 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/channelnotifier" "github.com/lightningnetwork/lnd/discovery" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" @@ -563,6 +564,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, IsSweeperOutpoint: func(wire.OutPoint) bool { return false }, + AuxLeafStore: fn.Some[lnwallet.AuxLeafStore]( + &lnwallet.MockAuxLeafStore{}, + ), } for _, op := range options { @@ -672,6 +676,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { OpenChannelPredicate: chainedAcceptor, DeleteAliasEdge: oldCfg.DeleteAliasEdge, AliasManager: oldCfg.AliasManager, + AuxLeafStore: oldCfg.AuxLeafStore, }) require.NoError(t, err, "failed recreating aliceFundingManager") diff --git a/lnd.go b/lnd.go index e483d5512..da7747e91 100644 --- a/lnd.go +++ b/lnd.go @@ -456,7 +456,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer cleanUp() partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig( - ctx, dbs, interceptorChain, grpcListeners, + ctx, dbs, &implCfg.AuxComponents, interceptorChain, + grpcListeners, ) if err != nil { return mkErr("error creating wallet config: %v", err) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 7c0a367da..e794da471 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1965,7 +1965,8 @@ type BreachRetribution struct { // required to construct the BreachRetribution. If the revocation log is missing // the required fields then ErrRevLogDataMissing will be returned. func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, - breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) { + breachHeight uint32, spendTx *wire.MsgTx, + leafStore fn.Option[AuxLeafStore]) (*BreachRetribution, error) { // Query the on-disk revocation log for the snapshot which was recorded // at this particular state num. Based on whether a legacy revocation @@ -3023,9 +3024,16 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, // signature can be submitted to the sigPool to generate all the signatures // asynchronously and in parallel. func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, - chanType channeldb.ChannelType, isRemoteInitiator bool, - leaseExpiry uint32, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - remoteCommitView *commitment) ([]SignJob, chan struct{}, error) { + chanState *channeldb.OpenChannel, leaseExpiry uint32, + remoteCommitView *commitment, + leafStore fn.Option[AuxLeafStore]) ([]SignJob, chan struct{}, error) { + + var ( + isRemoteInitiator = !chanState.IsInitiator + localChanCfg = chanState.LocalChanCfg + remoteChanCfg = chanState.RemoteChanCfg + chanType = chanState.ChanType + ) txHash := remoteCommitView.txn.TxHash() dustLimit := remoteChanCfg.DustLimit @@ -3191,9 +3199,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // validate this new state. This function is called right before sending the // new commitment to the remote party. The commit diff returned contains all // information necessary for retransmission. -func (lc *LightningChannel) createCommitDiff( - newCommit *commitment, commitSig lnwire.Sig, - htlcSigs []lnwire.Sig) (*channeldb.CommitDiff, error) { +func (lc *LightningChannel) createCommitDiff(newCommit *commitment, + commitSig lnwire.Sig, htlcSigs []lnwire.Sig) (*channeldb.CommitDiff, + error) { // First, we need to convert the funding outpoint into the ID that's // used on the wire to identify this channel. We'll use this shortly @@ -3892,9 +3900,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { leaseExpiry = lc.channelState.ThawHeight } sigBatch, cancelChan, err := genRemoteHtlcSigJobs( - keyRing, lc.channelState.ChanType, !lc.channelState.IsInitiator, - leaseExpiry, &lc.channelState.LocalChanCfg, - &lc.channelState.RemoteChanCfg, newCommitView, + keyRing, lc.channelState, leaseExpiry, newCommitView, + lc.leafStore, ) if err != nil { return nil, err @@ -4465,10 +4472,18 @@ func (lc *LightningChannel) computeView(view *HtlcView, // meant to verify all the signatures for HTLC's attached to a newly created // commitment state. The jobs generated are fully populated, and can be sent // directly into the pool of workers. -func genHtlcSigValidationJobs(localCommitmentView *commitment, - keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig, - chanType channeldb.ChannelType, isLocalInitiator bool, leaseExpiry uint32, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) { +// +//nolint:funlen +func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel, + localCommitmentView *commitment, keyRing *CommitmentKeyRing, + htlcSigs []lnwire.Sig, leaseExpiry uint32, + leafStore fn.Option[AuxLeafStore]) ([]VerifyJob, error) { + + var ( + isLocalInitiator = chanState.IsInitiator + localChanCfg = chanState.LocalChanCfg + chanType = chanState.ChanType + ) txHash := localCommitmentView.txn.TxHash() feePerKw := localCommitmentView.feePerKw @@ -4858,10 +4873,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { leaseExpiry = lc.channelState.ThawHeight } verifyJobs, err := genHtlcSigValidationJobs( - localCommitmentView, keyRing, commitSigs.HtlcSigs, - lc.channelState.ChanType, lc.channelState.IsInitiator, - leaseExpiry, &lc.channelState.LocalChanCfg, - &lc.channelState.RemoteChanCfg, + lc.channelState, localCommitmentView, keyRing, + commitSigs.HtlcSigs, leaseExpiry, lc.leafStore, ) if err != nil { return err @@ -6308,10 +6321,10 @@ type UnilateralCloseSummary struct { // happen in case we have lost state) it should be set to an empty struct, in // which case we will attempt to sweep the non-HTLC output using the passed // commitPoint. -func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer, - commitSpend *chainntnfs.SpendDetail, - remoteCommit channeldb.ChannelCommitment, - commitPoint *btcec.PublicKey) (*UnilateralCloseSummary, error) { +func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, + signer input.Signer, commitSpend *chainntnfs.SpendDetail, + remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey, + leafStore fn.Option[AuxLeafStore]) (*UnilateralCloseSummary, error) { // First, we'll generate the commitment point and the revocation point // so we can re-construct the HTLC state and also our payment key. @@ -7254,7 +7267,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { localCommitment := lc.channelState.LocalCommitment summary, err := NewLocalForceCloseSummary( lc.channelState, lc.Signer, commitTx, - localCommitment.CommitHeight, + localCommitment.CommitHeight, lc.leafStore, ) if err != nil { return nil, fmt.Errorf("unable to gen force close "+ @@ -7271,8 +7284,8 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { // channel state. The passed commitTx must be a fully signed commitment // transaction corresponding to localCommit. func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, - signer input.Signer, commitTx *wire.MsgTx, stateNum uint64) ( - *LocalForceCloseSummary, error) { + signer input.Signer, commitTx *wire.MsgTx, stateNum uint64, + leafStore fn.Option[AuxLeafStore]) (*LocalForceCloseSummary, error) { // Re-derive the original pkScript for to-self output within the // commitment transaction. We'll need this to find the corresponding diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 1bfd4ad88..110d323f6 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -5695,6 +5695,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { spendDetail, aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -5844,6 +5845,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { spendDetail, aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -5861,6 +5863,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { spendDetail, aliceRemoteChainTip.Commitment, aliceChannel.channelState.RemoteNextRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -6741,6 +6744,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx breachRet, err := NewBreachRetribution( aliceChannel.channelState, revokedStateNum, 100, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err, "unable to create breach retribution") @@ -10291,6 +10295,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // error as there are no past delta state saved as revocation logs yet. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, + fn.None[AuxLeafStore](), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10298,6 +10303,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // provided. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, + fn.None[AuxLeafStore](), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10343,6 +10349,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // successfully. br, err := NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err) @@ -10354,6 +10361,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // since the necessary info should now be found in the revocation log. br, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.NoError(t, err) assertRetribution(br, 1, 0) @@ -10362,6 +10370,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // error. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) @@ -10369,6 +10378,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // provided. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, nil, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) } diff --git a/lnwallet/config.go b/lnwallet/config.go index 7eeacb6ea..24961f38e 100644 --- a/lnwallet/config.go +++ b/lnwallet/config.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -62,4 +63,8 @@ type Config struct { // CoinSelectionStrategy is the strategy that is used for selecting // coins when funding a transaction. CoinSelectionStrategy wallet.CoinSelectionStrategy + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[AuxLeafStore] } diff --git a/lnwallet/mock.go b/lnwallet/mock.go index faac5fa67..2afff4f21 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -17,8 +17,10 @@ import ( "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/tlv" ) var ( @@ -397,3 +399,45 @@ func (*mockChainIO) GetBlockHeader( return nil, nil } + +type MockAuxLeafStore struct{} + +// A compile time check to ensure that MockAuxLeafStore implements the +// AuxLeafStore interface. +var _ AuxLeafStore = (*MockAuxLeafStore)(nil) + +// FetchLeavesFromView attempts to fetch the auxiliary leaves that +// correspond to the passed aux blob, and pending original (unfiltered) +// HTLC view. +func (*MockAuxLeafStore) FetchLeavesFromView( + _ CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that +// correspond to the passed aux blob, and an existing channel +// commitment. +func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState, + _ channeldb.ChannelCommitment, + _ CommitmentKeyRing) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves +// from a channel revocation that stores balance + blob information. +func (*MockAuxLeafStore) FetchLeavesFromRevocation( + _ *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// ApplyHtlcView serves as the state transition function for the custom +// channel's blob. Given the old blob, and an HTLC view, then a new +// blob should be returned that reflects the pending updates. +func (*MockAuxLeafStore) ApplyHtlcView( + _ CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] { + + return fn.Ok(fn.None[tlv.Blob]()) +} diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 077e17a3a..7e455ab48 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -2496,9 +2496,16 @@ func initStateHints(commit1, commit2 *wire.MsgTx, func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, fundingTx *wire.MsgTx) error { + var chanOpts []ChannelOpt + l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) { + chanOpts = append(chanOpts, WithLeafStore(s)) + }) + // First, we'll obtain a fully signed commitment transaction so we can // pass into it on the chanvalidate package for verification. - channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil) + channel, err := NewLightningChannel( + l.Cfg.Signer, channelState, nil, chanOpts..., + ) if err != nil { return err } diff --git a/peer/brontide.go b/peer/brontide.go index 25c7cea6f..1324044da 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -372,6 +372,10 @@ type Config struct { AddLocalAlias func(alias, base lnwire.ShortChannelID, gossip bool) error + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + // PongBuf is a slice we'll reuse instead of allocating memory on the // heap. Since only reads will occur and no writes, there is no need // for any synchronization primitives. As a result, it's safe to share @@ -943,8 +947,12 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( } } + var chanOpts []lnwallet.ChannelOpt + p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) lnChan, err := lnwallet.NewLightningChannel( - p.cfg.Signer, dbChan, p.cfg.SigPool, + p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts..., ) if err != nil { return nil, fmt.Errorf("unable to create channel "+ @@ -4151,6 +4159,10 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { chanOpts = append(chanOpts, lnwallet.WithSkipNonceInit()) } + p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + // If not already active, we'll add this channel to the set of active // channels, so we can look it up later easily according to its channel // ID. diff --git a/server.go b/server.go index 75fd02b97..1f8db90a6 100644 --- a/server.go +++ b/server.go @@ -1273,6 +1273,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return &pc.Incoming }, + AuxLeafStore: implCfg.AuxLeafStore, }, dbs.ChanStateDB) // Select the configuration and funding parameters for Bitcoin. @@ -1607,6 +1608,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, br, err := lnwallet.NewBreachRetribution( channel, commitHeight, 0, nil, + implCfg.AuxLeafStore, ) if err != nil { return nil, 0, err @@ -4073,6 +4075,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(), MaxFeeExposure: thresholdMSats, Quit: s.quit, + AuxLeafStore: s.implCfg.AuxLeafStore, MsgRouter: s.implCfg.MsgRouter, }