From 275d55c9e62fa54785b85a7c401459bfb0f0a559 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 13:17:36 +0200 Subject: [PATCH 01/11] blockcache: add blockcache package This commit adds a new blockcache package along with the GetBlock method to be used along with the blockcache. --- blockcache/blockcache.go | 70 +++++++++++++ blockcache/blockcache_test.go | 188 ++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 blockcache/blockcache.go create mode 100644 blockcache/blockcache_test.go diff --git a/blockcache/blockcache.go b/blockcache/blockcache.go new file mode 100644 index 000000000..34db764a7 --- /dev/null +++ b/blockcache/blockcache.go @@ -0,0 +1,70 @@ +package blockcache + +import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/neutrino/cache" + "github.com/lightninglabs/neutrino/cache/lru" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/multimutex" +) + +// BlockCache is an lru cache for blocks. +type BlockCache struct { + Cache *lru.Cache + HashMutex *multimutex.HashMutex +} + +// NewBlockCache creates a new BlockCache with the given maximum capacity. +func NewBlockCache(capacity uint64) *BlockCache { + return &BlockCache{ + Cache: lru.NewCache(capacity), + HashMutex: multimutex.NewHashMutex(), + } +} + +// GetBlock first checks to see if the BlockCache already contains the block +// with the given hash. If it does then the block is fetched from the cache and +// returned. Otherwise the getBlockImpl function is used in order to fetch the +// new block and then it is stored in the block cache and returned. +func (bc *BlockCache) GetBlock(hash *chainhash.Hash, + getBlockImpl func(hash *chainhash.Hash) (*wire.MsgBlock, + error)) (*wire.MsgBlock, error) { + + bc.HashMutex.Lock(lntypes.Hash(*hash)) + defer bc.HashMutex.Unlock(lntypes.Hash(*hash)) + + // Create an inv vector for getting the block. + inv := wire.NewInvVect(wire.InvTypeWitnessBlock, hash) + + // Check if the block corresponding to the given hash is already + // stored in the blockCache and return it if it is. + cacheBlock, err := bc.Cache.Get(*inv) + if err != nil && err != cache.ErrElementNotFound { + return nil, err + } + if cacheBlock != nil { + return cacheBlock.(*cache.CacheableBlock).MsgBlock(), nil + } + + // Fetch the block from the chain backends. + block, err := getBlockImpl(hash) + if err != nil { + return nil, err + } + + // Add the new block to blockCache. If the Cache is at its maximum + // capacity then the LFU item will be evicted in favour of this new + // block. + _, err = bc.Cache.Put( + *inv, &cache.CacheableBlock{ + Block: btcutil.NewBlock(block), + }, + ) + if err != nil { + return nil, err + } + + return block, nil +} diff --git a/blockcache/blockcache_test.go b/blockcache/blockcache_test.go new file mode 100644 index 000000000..5ea5ae9ca --- /dev/null +++ b/blockcache/blockcache_test.go @@ -0,0 +1,188 @@ +package blockcache + +import ( + "errors" + "fmt" + "sync" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/neutrino/cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type mockChainBackend struct { + blocks map[chainhash.Hash]*wire.MsgBlock + chainCallCount int + + sync.RWMutex +} + +func (m *mockChainBackend) addBlock(block *wire.MsgBlock, nonce uint32) { + m.Lock() + defer m.Unlock() + block.Header.Nonce = nonce + hash := block.Header.BlockHash() + m.blocks[hash] = block +} +func (m *mockChainBackend) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) { + m.RLock() + defer m.RUnlock() + m.chainCallCount++ + + block, ok := m.blocks[*blockHash] + if !ok { + return nil, fmt.Errorf("block not found") + } + + return block, nil +} + +func newMockChain() *mockChainBackend { + return &mockChainBackend{ + blocks: make(map[chainhash.Hash]*wire.MsgBlock), + } +} + +func (m *mockChainBackend) resetChainCallCount() { + m.RLock() + defer m.RUnlock() + + m.chainCallCount = 0 +} + +// TestBlockCacheGetBlock tests that the block Cache works correctly as a LFU block +// Cache for the given max capacity. +func TestBlockCacheGetBlock(t *testing.T) { + mc := newMockChain() + getBlockImpl := mc.GetBlock + + block1 := &wire.MsgBlock{Header: wire.BlockHeader{Nonce: 1}} + block2 := &wire.MsgBlock{Header: wire.BlockHeader{Nonce: 2}} + block3 := &wire.MsgBlock{Header: wire.BlockHeader{Nonce: 3}} + + blockhash1 := block1.BlockHash() + blockhash2 := block2.BlockHash() + blockhash3 := block3.BlockHash() + + inv1 := wire.NewInvVect(wire.InvTypeWitnessBlock, &blockhash1) + inv2 := wire.NewInvVect(wire.InvTypeWitnessBlock, &blockhash2) + inv3 := wire.NewInvVect(wire.InvTypeWitnessBlock, &blockhash3) + + // Determine the size of one of the blocks. + sz, _ := (&cache.CacheableBlock{Block: btcutil.NewBlock(block1)}).Size() + + // A new Cache is set up with a capacity of 2 blocks + bc := NewBlockCache(2 * sz) + + mc.addBlock(&wire.MsgBlock{}, 1) + mc.addBlock(&wire.MsgBlock{}, 2) + mc.addBlock(&wire.MsgBlock{}, 3) + + // We expect the initial Cache to be empty + require.Equal(t, 0, bc.Cache.Len()) + + // After calling getBlock for block1, it is expected that the Cache + // will have a size of 1 and will contain block1. One chain backends + // call is expected to fetch the block. + _, err := bc.GetBlock(&blockhash1, getBlockImpl) + require.NoError(t, err) + require.Equal(t, 1, bc.Cache.Len()) + require.Equal(t, 1, mc.chainCallCount) + mc.resetChainCallCount() + + _, err = bc.Cache.Get(*inv1) + require.NoError(t, err) + + // After calling getBlock for block2, it is expected that the Cache + // will have a size of 2 and will contain both block1 and block2. + // One chain backends call is expected to fetch the block. + _, err = bc.GetBlock(&blockhash2, getBlockImpl) + require.NoError(t, err) + require.Equal(t, 2, bc.Cache.Len()) + require.Equal(t, 1, mc.chainCallCount) + mc.resetChainCallCount() + + _, err = bc.Cache.Get(*inv1) + require.NoError(t, err) + + _, err = bc.Cache.Get(*inv2) + require.NoError(t, err) + + // getBlock is called again for block1 to make block2 the LFU block. + // No call to the chain backend is expected since block 1 is already + // in the Cache. + _, err = bc.GetBlock(&blockhash1, getBlockImpl) + require.NoError(t, err) + require.Equal(t, 2, bc.Cache.Len()) + require.Equal(t, 0, mc.chainCallCount) + mc.resetChainCallCount() + + // Since the Cache is now at its max capacity, it is expected that when + // getBlock is called for a new block then the LFU block will be + // evicted. It is expected that block2 will be evicted. After calling + // Getblock for block3, it is expected that the Cache will have a + // length of 2 and will contain block 1 and 3. + _, err = bc.GetBlock(&blockhash3, getBlockImpl) + require.NoError(t, err) + require.Equal(t, 2, bc.Cache.Len()) + require.Equal(t, 1, mc.chainCallCount) + mc.resetChainCallCount() + + _, err = bc.Cache.Get(*inv1) + require.NoError(t, err) + + _, err = bc.Cache.Get(*inv2) + require.True(t, errors.Is(err, cache.ErrElementNotFound)) + + _, err = bc.Cache.Get(*inv3) + require.NoError(t, err) +} + +// TestBlockCacheMutexes is used to test that concurrent calls to GetBlock with +// the same block hash does not result in multiple calls to the chain backend. +// In other words this tests the HashMutex. +func TestBlockCacheMutexes(t *testing.T) { + mc := newMockChain() + getBlockImpl := mc.GetBlock + + block1 := &wire.MsgBlock{Header: wire.BlockHeader{Nonce: 1}} + block2 := &wire.MsgBlock{Header: wire.BlockHeader{Nonce: 2}} + + blockhash1 := block1.BlockHash() + blockhash2 := block2.BlockHash() + + // Determine the size of the block. + sz, _ := (&cache.CacheableBlock{Block: btcutil.NewBlock(block1)}).Size() + + // A new Cache is set up with a capacity of 2 blocks + bc := NewBlockCache(2 * sz) + + mc.addBlock(&wire.MsgBlock{}, 1) + mc.addBlock(&wire.MsgBlock{}, 2) + + // Spin off multiple go routines and ensure that concurrent calls to the + // GetBlock method does not result in multiple calls to the chain + // backend. + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func(e int) { + if e%2 == 0 { + _, err := bc.GetBlock(&blockhash1, getBlockImpl) + assert.NoError(t, err) + } else { + _, err := bc.GetBlock(&blockhash2, getBlockImpl) + assert.NoError(t, err) + } + + wg.Done() + }(i) + } + + wg.Wait() + require.Equal(t, 2, mc.chainCallCount) +} From 6702c79216b7c472624be6ba34303ad19d5426d7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 13:40:53 +0200 Subject: [PATCH 02/11] multi: add block cache size config This commit adds block cache size to the main lnd config along with the chainreg config. --- chainreg/chainregistry.go | 3 +++ config.go | 7 +++++++ lnd.go | 1 + sample-lnd.conf | 6 ++++++ 4 files changed, 17 insertions(+) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index aec44b88d..ff5008207 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -73,6 +73,9 @@ type Config struct { // RemoteChanDB is a pointer to the remote backing channel database. RemoteChanDB *channeldb.DB + // BlockCacheSize is the size (in bytes) of blocks kept in memory. + BlockCacheSize uint64 + // PrivateWalletPw is the private wallet password to the underlying // btcwallet instance. PrivateWalletPw []byte diff --git a/config.go b/config.go index 2052acdae..67abc61db 100644 --- a/config.go +++ b/config.go @@ -101,6 +101,10 @@ const ( // initiated the channel closure. defaultCoopCloseTargetConfs = 6 + // defaultBlockCacheSize is the size (in bytes) of blocks that will be + // keep in memory if no size is specified. + defaultBlockCacheSize uint64 = 20 * 1024 * 1024 // 20 MB + // defaultHostSampleInterval is the default amount of time that the // HostAnnouncer will wait between DNS resolutions to check if the // backing IP of a host has changed. @@ -273,6 +277,8 @@ type Config struct { LtcdMode *lncfg.Btcd `group:"ltcd" namespace:"ltcd"` LitecoindMode *lncfg.Bitcoind `group:"litecoind" namespace:"litecoind"` + BlockCacheSize uint64 `long:"blockcachesize" description:"The maximum capacity of the block cache"` + Autopilot *lncfg.AutoPilot `group:"Autopilot" namespace:"autopilot"` Tor *lncfg.Tor `group:"Tor" namespace:"tor"` @@ -434,6 +440,7 @@ func DefaultConfig() Config { UserAgentName: neutrino.UserAgentName, UserAgentVersion: neutrino.UserAgentVersion, }, + BlockCacheSize: defaultBlockCacheSize, UnsafeDisconnect: true, MaxPendingChannels: lncfg.DefaultMaxPendingChannels, NoSeedBackup: defaultNoSeedBackup, diff --git a/lnd.go b/lnd.go index b82d2f7c5..8e05ee5de 100644 --- a/lnd.go +++ b/lnd.go @@ -546,6 +546,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error Dialer: func(addr string) (net.Conn, error) { return cfg.net.Dial("tcp", addr, cfg.ConnectionTimeout) }, + BlockCacheSize: cfg.BlockCacheSize, } activeChainControl, cleanup, err := chainreg.NewChainControl(chainControlCfg) diff --git a/sample-lnd.conf b/sample-lnd.conf index 4659c1cec..061583311 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -224,6 +224,12 @@ ; The target location of the channel backup file. ; backupfilepath=~/.lnd/data/chain/bitcoin/simnet/channel.backup +; The maximum capacity of the block cache in bytes. Increasing this will result +; in more blocks being kept in memory but will increase performance when the +; same block is required multiple times. +; The example value below is 40 MB (1024 * 1024 * 40) +; blockcachesize=41943040 + ; Optional URL for external fee estimation. If no URL is specified, the method ; for fee estimation will depend on the chosen backend and network. Must be set ; for neutrino on mainnet. From 106f93a1b47935cd8a2f3ae93a3af5ec37e2f99c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 14:08:53 +0200 Subject: [PATCH 03/11] btcwallet: make blockcache available to BtcWallet This commit makes the blockcache available to BtcWallet so that any GetBlock call made to BtcWallet is wrapped by the blockcache GetBlock call. --- chainreg/chainregistry.go | 6 +++++- lnwallet/btcwallet/blockchain.go | 2 +- lnwallet/btcwallet/btcwallet.go | 6 +++++- lnwallet/btcwallet/driver.go | 13 ++++++++++--- lnwallet/test/test_interface.go | 11 +++++++++-- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index ff5008207..d3b84130e 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wallet" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" @@ -306,6 +307,9 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { "cache: %v", err) } + // Initialize a new block cache. + blockCache := blockcache.NewBlockCache(cfg.BlockCacheSize) + // If spv mode is active, then we'll be using a distinct set of // chainControl interfaces that interface directly with the p2p network // of the selected chain. @@ -641,7 +645,7 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { return nil, nil, err } - wc, err := btcwallet.New(*walletConfig) + wc, err := btcwallet.New(*walletConfig, blockCache) if err != nil { fmt.Printf("unable to create wallet controller: %v\n", err) return nil, ccCleanup, err diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index f59c462e0..1b1147484 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -131,7 +131,7 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, pkScript []byte, // // This method is a part of the lnwallet.BlockChainIO interface. func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) { - return b.chain.GetBlock(blockHash) + return b.blockCache.GetBlock(blockHash, b.chain.GetBlock) } // GetBlockHash returns the hash of the block in the best blockchain at the diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index be5d570e1..63919e5ef 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -24,6 +24,7 @@ import ( "github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -74,6 +75,8 @@ type BtcWallet struct { netParams *chaincfg.Params chainKeyScope waddrmgr.KeyScope + + blockCache *blockcache.BlockCache } // A compile time check to ensure that BtcWallet implements the @@ -83,7 +86,7 @@ var _ lnwallet.BlockChainIO = (*BtcWallet)(nil) // New returns a new fully initialized instance of BtcWallet given a valid // configuration struct. -func New(cfg Config) (*BtcWallet, error) { +func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) { // Ensure the wallet exists or create it when the create flag is set. netDir := NetworkDir(cfg.DataDir, cfg.NetParams) @@ -142,6 +145,7 @@ func New(cfg Config) (*BtcWallet, error) { chain: cfg.ChainSource, netParams: cfg.NetParams, chainKeyScope: chainKeyScope, + blockCache: blockCache, }, nil } diff --git a/lnwallet/btcwallet/driver.go b/lnwallet/btcwallet/driver.go index 4939c16f8..55cdfb09c 100644 --- a/lnwallet/btcwallet/driver.go +++ b/lnwallet/btcwallet/driver.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcwallet/chain" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -16,9 +17,9 @@ const ( // properly create an instance of the lnwallet.WalletDriver struct for // BtcWallet. func createNewWallet(args ...interface{}) (lnwallet.WalletController, error) { - if len(args) != 1 { + if len(args) != 2 { return nil, fmt.Errorf("incorrect number of arguments to .New(...), "+ - "expected 1, instead passed %v", len(args)) + "expected 2, instead passed %v", len(args)) } config, ok := args[0].(*Config) @@ -27,7 +28,13 @@ func createNewWallet(args ...interface{}) (lnwallet.WalletController, error) { "incorrect, expected a *rpcclient.ConnConfig") } - return New(*config) + blockCache, ok := args[1].(*blockcache.BlockCache) + if !ok { + return nil, fmt.Errorf("second argument to btcdnotifier.New is " + + "incorrect, expected a *blockcache.BlockCache") + } + + return New(*config, blockCache) } // init registers a driver for the BtcWallet concrete implementation of the diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index c57fb49cc..cd01232ef 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -32,6 +32,7 @@ import ( _ "github.com/btcsuite/btcwallet/walletdb/bdb" "github.com/davecgh/go-spew/spew" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" "github.com/lightningnetwork/lnd/channeldb" @@ -3262,6 +3263,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, } defer os.RemoveAll(tempTestDirBob) + blockCache := blockcache.NewBlockCache(10000) + walletType := walletDriver.WalletType switch walletType { case "btcwallet": @@ -3430,7 +3433,9 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, // wallet starts in recovery mode RecoveryWindow: 2, } - aliceWalletController, err = walletDriver.New(aliceWalletConfig) + aliceWalletController, err = walletDriver.New( + aliceWalletConfig, blockCache, + ) if err != nil { t.Fatalf("unable to create btcwallet: %v", err) } @@ -3455,7 +3460,9 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, // wallet starts without recovery mode RecoveryWindow: 0, } - bobWalletController, err = walletDriver.New(bobWalletConfig) + bobWalletController, err = walletDriver.New( + bobWalletConfig, blockCache, + ) if err != nil { t.Fatalf("unable to create btcwallet: %v", err) } From 0193669ed8a959d9f38f9728083bc11de1f518aa Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 14:24:06 +0200 Subject: [PATCH 04/11] routing: add block cache to BitcoindFilteredChainView This commit adds the block cache to the BitcoindFilteredChainView struct and wraps its GetBlock function so that block cache is used. --- chainreg/chainregistry.go | 4 +++- routing/chainview/bitcoind.go | 18 ++++++++++++++++-- routing/chainview/interface_test.go | 7 ++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index d3b84130e..e81258fd1 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -418,7 +418,9 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { cc.ChainNotifier = bitcoindnotify.New( bitcoindConn, cfg.ActiveNetParams.Params, hintCache, hintCache, ) - cc.ChainView = chainview.NewBitcoindFilteredChainView(bitcoindConn) + cc.ChainView = chainview.NewBitcoindFilteredChainView( + bitcoindConn, blockCache, + ) walletConfig.ChainSource = bitcoindConn.NewBitcoindClient() // If we're not in regtest mode, then we'll attempt to use a diff --git a/routing/chainview/bitcoind.go b/routing/chainview/bitcoind.go index d793dfb72..9a298edd4 100644 --- a/routing/chainview/bitcoind.go +++ b/routing/chainview/bitcoind.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" ) @@ -37,6 +38,9 @@ type BitcoindFilteredChainView struct { // chainView. blockQueue *blockEventQueue + // blockCache is an LRU block cache. + blockCache *blockcache.BlockCache + // filterUpdates is a channel in which updates to the utxo filter // attached to this instance are sent over. filterUpdates chan filterUpdate @@ -61,12 +65,14 @@ var _ FilteredChainView = (*BitcoindFilteredChainView)(nil) // NewBitcoindFilteredChainView creates a new instance of a FilteredChainView // from RPC credentials and a ZMQ socket address for a bitcoind instance. func NewBitcoindFilteredChainView( - chainConn *chain.BitcoindConn) *BitcoindFilteredChainView { + chainConn *chain.BitcoindConn, + blockCache *blockcache.BlockCache) *BitcoindFilteredChainView { chainView := &BitcoindFilteredChainView{ chainFilter: make(map[wire.OutPoint]struct{}), filterUpdates: make(chan filterUpdate), filterBlockReqs: make(chan *filterBlockReq), + blockCache: blockCache, quit: make(chan struct{}), } @@ -390,7 +396,7 @@ func (b *BitcoindFilteredChainView) chainFilterer() { case req := <-b.filterBlockReqs: // First we'll fetch the block itself as well as some // additional information including its height. - block, err := b.chainClient.GetBlock(req.blockHash) + block, err := b.GetBlock(req.blockHash) if err != nil { req.err <- err req.resp <- nil @@ -479,3 +485,11 @@ func (b *BitcoindFilteredChainView) FilteredBlocks() <-chan *FilteredBlock { func (b *BitcoindFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock { return b.blockQueue.staleBlocks } + +// GetBlock is used to retrieve the block with the given hash. This function +// wraps the blockCache's GetBlock function. +func (b *BitcoindFilteredChainView) GetBlock(hash *chainhash.Hash) ( + *wire.MsgBlock, error) { + + return b.blockCache.GetBlock(hash, b.chainClient.GetBlock) +} diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 33975e0ae..875c06018 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -26,6 +26,7 @@ import ( _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to register the boltdb walletdb implementation. "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/kvdb" ) @@ -844,7 +845,11 @@ var interfaceImpls = []struct { cleanUp2() } - chainView := NewBitcoindFilteredChainView(chainConn) + blockCache := blockcache.NewBlockCache(10000) + + chainView := NewBitcoindFilteredChainView( + chainConn, blockCache, + ) return cleanUp3, chainView, nil }, From f470946379fb3480e2f97b571d81de94ad24c8bd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 14:30:58 +0200 Subject: [PATCH 05/11] routing: add block cache to BtcdFilteredChainView This commit makes the block cache available to BtcdFilteredChainView and wraps its GetBlock method so that the block cache is used. --- chainreg/chainregistry.go | 4 +++- routing/chainview/btcd.go | 19 +++++++++++++++++-- routing/chainview/interface_test.go | 5 ++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index e81258fd1..dda18a805 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -555,7 +555,9 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { // Finally, we'll create an instance of the default chain view to be // used within the routing layer. - cc.ChainView, err = chainview.NewBtcdFilteredChainView(*rpcConfig) + cc.ChainView, err = chainview.NewBtcdFilteredChainView( + *rpcConfig, blockCache, + ) if err != nil { log.Errorf("unable to create chain view: %v", err) return nil, nil, err diff --git a/routing/chainview/btcd.go b/routing/chainview/btcd.go index c4d9c0229..4e5a95b5b 100644 --- a/routing/chainview/btcd.go +++ b/routing/chainview/btcd.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" ) @@ -35,6 +36,9 @@ type BtcdFilteredChainView struct { // chainView. blockQueue *blockEventQueue + // blockCache is an LRU block cache. + blockCache *blockcache.BlockCache + // filterUpdates is a channel in which updates to the utxo filter // attached to this instance are sent over. filterUpdates chan filterUpdate @@ -58,11 +62,14 @@ var _ FilteredChainView = (*BtcdFilteredChainView)(nil) // NewBtcdFilteredChainView creates a new instance of a FilteredChainView from // RPC credentials for an active btcd instance. -func NewBtcdFilteredChainView(config rpcclient.ConnConfig) (*BtcdFilteredChainView, error) { +func NewBtcdFilteredChainView(config rpcclient.ConnConfig, + blockCache *blockcache.BlockCache) (*BtcdFilteredChainView, error) { + chainView := &BtcdFilteredChainView{ chainFilter: make(map[wire.OutPoint]struct{}), filterUpdates: make(chan filterUpdate), filterBlockReqs: make(chan *filterBlockReq), + blockCache: blockCache, quit: make(chan struct{}), } @@ -404,7 +411,7 @@ func (b *BtcdFilteredChainView) chainFilterer() { case req := <-b.filterBlockReqs: // First we'll fetch the block itself as well as some // additional information including its height. - block, err := b.btcdConn.GetBlock(req.blockHash) + block, err := b.GetBlock(req.blockHash) if err != nil { req.err <- err req.resp <- nil @@ -486,3 +493,11 @@ func (b *BtcdFilteredChainView) FilteredBlocks() <-chan *FilteredBlock { func (b *BtcdFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock { return b.blockQueue.staleBlocks } + +// GetBlock is used to retrieve the block with the given hash. This function +// wraps the blockCache's GetBlock function. +func (b *BtcdFilteredChainView) GetBlock(hash *chainhash.Hash) ( + *wire.MsgBlock, error) { + + return b.blockCache.GetBlock(hash, b.btcdConn.GetBlock) +} diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 875c06018..439b8d7f4 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -906,7 +906,10 @@ var interfaceImpls = []struct { { name: "btcd_websockets", chainViewInit: func(config rpcclient.ConnConfig, _ string) (func(), FilteredChainView, error) { - chainView, err := NewBtcdFilteredChainView(config) + blockCache := blockcache.NewBlockCache(10000) + chainView, err := NewBtcdFilteredChainView( + config, blockCache, + ) if err != nil { return nil, nil, err } From 8a33fbf11a81b1fa823c61471c62d611d6692fbf Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 14:42:18 +0200 Subject: [PATCH 06/11] chainntnfs: add block cache to BitcoindNotifier This commit adds a blockcache pointer to BitcoindNotifier and wraps its GetBlock method so that the block cache is used. --- chainntnfs/bitcoindnotify/bitcoind.go | 23 ++++++++++++++++++---- chainntnfs/bitcoindnotify/bitcoind_test.go | 16 +++++++++++---- chainntnfs/bitcoindnotify/driver.go | 14 ++++++++++--- chainntnfs/test/test_interface.go | 5 ++++- chainreg/chainregistry.go | 3 ++- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index 496589463..b30c45dca 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/queue" ) @@ -50,6 +51,9 @@ type BitcoindNotifier struct { bestBlock chainntnfs.BlockEpoch + // blockCache is a LRU block cache. + blockCache *blockcache.BlockCache + // spendHintCache is a cache used to query and update the latest height // hints for an outpoint. Each height hint represents the earliest // height at which the outpoint could have been spent within the chain. @@ -73,7 +77,8 @@ var _ chainntnfs.ChainNotifier = (*BitcoindNotifier)(nil) // willing to accept RPC requests and new zmq clients. func New(chainConn *chain.BitcoindConn, chainParams *chaincfg.Params, spendHintCache chainntnfs.SpendHintCache, - confirmHintCache chainntnfs.ConfirmHintCache) *BitcoindNotifier { + confirmHintCache chainntnfs.ConfirmHintCache, + blockCache *blockcache.BlockCache) *BitcoindNotifier { notifier := &BitcoindNotifier{ chainParams: chainParams, @@ -86,6 +91,8 @@ func New(chainConn *chain.BitcoindConn, chainParams *chaincfg.Params, spendHintCache: spendHintCache, confirmHintCache: confirmHintCache, + blockCache: blockCache, + quit: make(chan struct{}), } @@ -522,7 +529,7 @@ func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfReques "with height %d", height) } - block, err := b.chainConn.GetBlock(blockHash) + block, err := b.GetBlock(blockHash) if err != nil { return nil, chainntnfs.TxNotFoundManually, fmt.Errorf("unable to get block with hash "+ @@ -558,7 +565,7 @@ func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) err // First, we'll fetch the raw block as we'll need to gather all the // transactions to determine whether any are relevant to our registered // clients. - rawBlock, err := b.chainConn.GetBlock(block.Hash) + rawBlock, err := b.GetBlock(block.Hash) if err != nil { return fmt.Errorf("unable to get block: %v", err) } @@ -777,7 +784,7 @@ func (b *BitcoindNotifier) historicalSpendDetails( return nil, fmt.Errorf("unable to retrieve hash for "+ "block with height %d: %v", height, err) } - block, err := b.chainConn.GetBlock(blockHash) + block, err := b.GetBlock(blockHash) if err != nil { return nil, fmt.Errorf("unable to retrieve block "+ "with hash %v: %v", blockHash, err) @@ -955,3 +962,11 @@ func (b *BitcoindNotifier) RegisterBlockEpochNtfn( }, nil } } + +// GetBlock is used to retrieve the block with the given hash. This function +// wraps the blockCache's GetBlock function. +func (b *BitcoindNotifier) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, + error) { + + return b.blockCache.GetBlock(hash, b.chainConn.GetBlock) +} diff --git a/chainntnfs/bitcoindnotify/bitcoind_test.go b/chainntnfs/bitcoindnotify/bitcoind_test.go index 89c68d2e5..e48423bcb 100644 --- a/chainntnfs/bitcoindnotify/bitcoind_test.go +++ b/chainntnfs/bitcoindnotify/bitcoind_test.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcwallet/chain" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" ) @@ -55,13 +56,14 @@ func initHintCache(t *testing.T) *chainntnfs.HeightHintCache { // bitcoind driver. func setUpNotifier(t *testing.T, bitcoindConn *chain.BitcoindConn, spendHintCache chainntnfs.SpendHintCache, - confirmHintCache chainntnfs.ConfirmHintCache) *BitcoindNotifier { + confirmHintCache chainntnfs.ConfirmHintCache, + blockCache *blockcache.BlockCache) *BitcoindNotifier { t.Helper() notifier := New( bitcoindConn, chainntnfs.NetParams, spendHintCache, - confirmHintCache, + confirmHintCache, blockCache, ) if err := notifier.Start(); err != nil { t.Fatalf("unable to start notifier: %v", err) @@ -116,8 +118,11 @@ func TestHistoricalConfDetailsTxIndex(t *testing.T) { defer cleanUp() hintCache := initHintCache(t) + blockCache := blockcache.NewBlockCache(10000) - notifier := setUpNotifier(t, bitcoindConn, hintCache, hintCache) + notifier := setUpNotifier( + t, bitcoindConn, hintCache, hintCache, blockCache, + ) defer notifier.Stop() syncNotifierWithMiner(t, notifier, miner) @@ -209,8 +214,11 @@ func TestHistoricalConfDetailsNoTxIndex(t *testing.T) { defer cleanUp() hintCache := initHintCache(t) + blockCache := blockcache.NewBlockCache(10000) - notifier := setUpNotifier(t, bitcoindConn, hintCache, hintCache) + notifier := setUpNotifier( + t, bitcoindConn, hintCache, hintCache, blockCache, + ) defer notifier.Stop() // Since the node has its txindex disabled, we fall back to scanning the diff --git a/chainntnfs/bitcoindnotify/driver.go b/chainntnfs/bitcoindnotify/driver.go index 6054f0dee..634aa3545 100644 --- a/chainntnfs/bitcoindnotify/driver.go +++ b/chainntnfs/bitcoindnotify/driver.go @@ -6,15 +6,16 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/chain" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" ) // createNewNotifier creates a new instance of the ChainNotifier interface // implemented by BitcoindNotifier. func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { - if len(args) != 4 { + if len(args) != 5 { return nil, fmt.Errorf("incorrect number of arguments to "+ - ".New(...), expected 4, instead passed %v", len(args)) + ".New(...), expected 5, instead passed %v", len(args)) } chainConn, ok := args[0].(*chain.BitcoindConn) @@ -41,7 +42,14 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { "is incorrect, expected a chainntnfs.ConfirmHintCache") } - return New(chainConn, chainParams, spendHintCache, confirmHintCache), nil + blockCache, ok := args[4].(*blockcache.BlockCache) + if !ok { + return nil, errors.New("fifth argument to bitcoindnotify.New " + + "is incorrect, expected a *blockcache.BlockCache") + } + + return New(chainConn, chainParams, spendHintCache, + confirmHintCache, blockCache), nil } // init registers a driver for the BtcdNotifier concrete implementation of the diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index 622975e16..4b5c69f3b 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcwallet/chain" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to auto-register the boltdb walletdb implementation. "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" @@ -1930,6 +1931,8 @@ func TestInterfaces(t *testing.T, targetBackEnd string) { t.Fatalf("unable to create height hint cache: %v", err) } + blockCache := blockcache.NewBlockCache(10000) + var ( cleanUp func() newNotifier func() (chainntnfs.TestChainNotifier, error) @@ -1944,7 +1947,7 @@ func TestInterfaces(t *testing.T, targetBackEnd string) { newNotifier = func() (chainntnfs.TestChainNotifier, error) { return bitcoindnotify.New( bitcoindConn, chainntnfs.NetParams, - hintCache, hintCache, + hintCache, hintCache, blockCache, ), nil } diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index dda18a805..28f7e3864 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -416,7 +416,8 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { } cc.ChainNotifier = bitcoindnotify.New( - bitcoindConn, cfg.ActiveNetParams.Params, hintCache, hintCache, + bitcoindConn, cfg.ActiveNetParams.Params, hintCache, + hintCache, blockCache, ) cc.ChainView = chainview.NewBitcoindFilteredChainView( bitcoindConn, blockCache, From a0f7bf8b2d6d54d5a3fd10f7d12427dbd02e838a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Mar 2021 14:54:36 +0200 Subject: [PATCH 07/11] chainntnfs: add block cache to BtcdNotifier This commit adds gives BtcdNotifier access to the block cache and wraps its GetBlock method so that it uses the block cache. --- chainntnfs/btcdnotify/btcd.go | 21 ++++++++++++++++++--- chainntnfs/btcdnotify/btcd_test.go | 6 +++++- chainntnfs/btcdnotify/driver.go | 15 ++++++++++++--- chainntnfs/test/test_interface.go | 2 +- chainreg/chainregistry.go | 3 ++- lnwallet/test/test_interface.go | 3 ++- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index dc36503e1..8ad840e39 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/queue" ) @@ -69,6 +70,9 @@ type BtcdNotifier struct { bestBlock chainntnfs.BlockEpoch + // blockCache is a LRU block cache. + blockCache *blockcache.BlockCache + chainUpdates *queue.ConcurrentQueue txUpdates *queue.ConcurrentQueue @@ -94,7 +98,8 @@ var _ chainntnfs.ChainNotifier = (*BtcdNotifier)(nil) // accept new websockets clients. func New(config *rpcclient.ConnConfig, chainParams *chaincfg.Params, spendHintCache chainntnfs.SpendHintCache, - confirmHintCache chainntnfs.ConfirmHintCache) (*BtcdNotifier, error) { + confirmHintCache chainntnfs.ConfirmHintCache, + blockCache *blockcache.BlockCache) (*BtcdNotifier, error) { notifier := &BtcdNotifier{ chainParams: chainParams, @@ -110,6 +115,8 @@ func New(config *rpcclient.ConnConfig, chainParams *chaincfg.Params, spendHintCache: spendHintCache, confirmHintCache: confirmHintCache, + blockCache: blockCache, + quit: make(chan struct{}), } @@ -578,7 +585,7 @@ func (b *BtcdNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest, } // TODO: fetch the neutrino filters instead. - block, err := b.chainConn.GetBlock(blockHash) + block, err := b.GetBlock(blockHash) if err != nil { return nil, chainntnfs.TxNotFoundManually, fmt.Errorf("unable to get block with hash "+ @@ -616,7 +623,7 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { // First, we'll fetch the raw block as we'll need to gather all the // transactions to determine whether any are relevant to our registered // clients. - rawBlock, err := b.chainConn.GetBlock(epoch.Hash) + rawBlock, err := b.GetBlock(epoch.Hash) if err != nil { return fmt.Errorf("unable to get block: %v", err) } @@ -1012,3 +1019,11 @@ func (b *BtcdNotifier) RegisterBlockEpochNtfn( }, nil } } + +// GetBlock is used to retrieve the block with the given hash. This function +// wraps the blockCache's GetBlock function. +func (b *BtcdNotifier) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, + error) { + + return b.blockCache.GetBlock(hash, b.chainConn.GetBlock) +} diff --git a/chainntnfs/btcdnotify/btcd_test.go b/chainntnfs/btcdnotify/btcd_test.go index e5954f256..7302171c1 100644 --- a/chainntnfs/btcdnotify/btcd_test.go +++ b/chainntnfs/btcdnotify/btcd_test.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" ) @@ -53,9 +54,12 @@ func initHintCache(t *testing.T) *chainntnfs.HeightHintCache { // driver. func setUpNotifier(t *testing.T, h *rpctest.Harness) *BtcdNotifier { hintCache := initHintCache(t) + blockCache := blockcache.NewBlockCache(10000) rpcCfg := h.RPCConfig() - notifier, err := New(&rpcCfg, chainntnfs.NetParams, hintCache, hintCache) + notifier, err := New( + &rpcCfg, chainntnfs.NetParams, hintCache, hintCache, blockCache, + ) if err != nil { t.Fatalf("unable to create notifier: %v", err) } diff --git a/chainntnfs/btcdnotify/driver.go b/chainntnfs/btcdnotify/driver.go index 901426f2d..067b48cf8 100644 --- a/chainntnfs/btcdnotify/driver.go +++ b/chainntnfs/btcdnotify/driver.go @@ -6,15 +6,16 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" ) // createNewNotifier creates a new instance of the ChainNotifier interface // implemented by BtcdNotifier. func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { - if len(args) != 4 { + if len(args) != 5 { return nil, fmt.Errorf("incorrect number of arguments to "+ - ".New(...), expected 4, instead passed %v", len(args)) + ".New(...), expected 5, instead passed %v", len(args)) } config, ok := args[0].(*rpcclient.ConnConfig) @@ -41,7 +42,15 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { "is incorrect, expected a chainntnfs.ConfirmHintCache") } - return New(config, chainParams, spendHintCache, confirmHintCache) + blockCache, ok := args[4].(*blockcache.BlockCache) + if !ok { + return nil, errors.New("fifth argument to btcdnotify.New " + + "is incorrect, expected a *blockcache.BlockCache") + } + + return New( + config, chainParams, spendHintCache, confirmHintCache, blockCache, + ) } // init registers a driver for the BtcdNotifier concrete implementation of the diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index 4b5c69f3b..bfa4d23c3 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -1955,7 +1955,7 @@ func TestInterfaces(t *testing.T, targetBackEnd string) { newNotifier = func() (chainntnfs.TestChainNotifier, error) { return btcdnotify.New( &rpcConfig, chainntnfs.NetParams, - hintCache, hintCache, + hintCache, hintCache, blockCache, ) } diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 28f7e3864..db2d4b920 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -548,7 +548,8 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { DisableAutoReconnect: false, } cc.ChainNotifier, err = btcdnotify.New( - rpcConfig, cfg.ActiveNetParams.Params, hintCache, hintCache, + rpcConfig, cfg.ActiveNetParams.Params, hintCache, + hintCache, blockCache, ) if err != nil { return nil, nil, err diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index cd01232ef..1b7e7e489 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -3205,8 +3205,9 @@ func TestLightningWallet(t *testing.T, targetBackEnd string) { if err != nil { t.Fatalf("unable to create height hint cache: %v", err) } + blockCache := blockcache.NewBlockCache(10000) chainNotifier, err := btcdnotify.New( - &rpcConfig, netParams, hintCache, hintCache, + &rpcConfig, netParams, hintCache, hintCache, blockCache, ) if err != nil { t.Fatalf("unable to create notifier: %v", err) From ecf20ed35080d352bd7b7993303a3172dcc9b370 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 24 Mar 2021 08:48:33 +0200 Subject: [PATCH 08/11] multi: init neutrino backend with block cache This commit initializes the nwutrino backend with the lnd blockcache so that the two can share a block cache instead of each having its own. --- chainreg/chainregistry.go | 6 ++---- lnd.go | 14 +++++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index db2d4b920..5093f7884 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -235,7 +235,8 @@ type ChainControl struct { // full-node, another backed by a running bitcoind full-node, and the other // backed by a running neutrino light client instance. When running with a // neutrino light client instance, `neutrinoCS` must be non-nil. -func NewChainControl(cfg *Config) (*ChainControl, func(), error) { +func NewChainControl(cfg *Config, blockCache *blockcache.BlockCache) ( + *ChainControl, func(), error) { // Set the RPC config from the "home" chain. Multi-chain isn't yet // active, so we'll restrict usage to a particular chain for now. @@ -307,9 +308,6 @@ func NewChainControl(cfg *Config) (*ChainControl, func(), error) { "cache: %v", err) } - // Initialize a new block cache. - blockCache := blockcache.NewBlockCache(cfg.BlockCacheSize) - // If spv mode is active, then we'll be using a distinct set of // chainControl interfaces that interface directly with the p2p network // of the selected chain. diff --git a/lnd.go b/lnd.go index 8e05ee5de..2559d0aa1 100644 --- a/lnd.go +++ b/lnd.go @@ -34,6 +34,7 @@ import ( "gopkg.in/macaroon.v2" "github.com/lightningnetwork/lnd/autopilot" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/chainreg" @@ -254,6 +255,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error defer cleanUp() + // Initialize a new block cache. + blockCache := blockcache.NewBlockCache(cfg.BlockCacheSize) + // Before starting the wallet, we'll create and start our Neutrino // light client instance, if enabled, in order to allow it to sync // while the rest of the daemon continues startup. @@ -264,7 +268,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error var neutrinoCS *neutrino.ChainService if mainChain.Node == "neutrino" { neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend( - cfg, mainChain.ChainDir, + cfg, mainChain.ChainDir, blockCache, ) if err != nil { err := fmt.Errorf("unable to initialize neutrino "+ @@ -549,7 +553,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error BlockCacheSize: cfg.BlockCacheSize, } - activeChainControl, cleanup, err := chainreg.NewChainControl(chainControlCfg) + activeChainControl, cleanup, err := chainreg.NewChainControl( + chainControlCfg, blockCache, + ) if cleanup != nil { defer cleanup() } @@ -1554,7 +1560,8 @@ func initializeDatabases(ctx context.Context, // initNeutrinoBackend inits a new instance of the neutrino light client // backend given a target chain directory to store the chain state. -func initNeutrinoBackend(cfg *Config, chainDir string) (*neutrino.ChainService, +func initNeutrinoBackend(cfg *Config, chainDir string, + blockCache *blockcache.BlockCache) (*neutrino.ChainService, func(), error) { // Both channel validation flags are false by default but their meaning @@ -1662,6 +1669,7 @@ func initNeutrinoBackend(cfg *Config, chainDir string) (*neutrino.ChainService, return ips, nil }, AssertFilterHeader: headerStateAssertion, + BlockCache: blockCache.Cache, } neutrino.MaxPeers = 8 From 432b1f05881e3433c47dd63e69b3b88a0e72bc88 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 6 Apr 2021 10:42:09 +0200 Subject: [PATCH 09/11] btcwallet: lock blockcache for Neutrino GetBlock This commit ensures that for the neutrino implementation of lnwallet.BlockChainIO, the neutrino GetBlock method is called directly (since it already uses the blockcache). It also ensures that the block cache mutex for the given hash is locked before the call to GetBlock. --- lnwallet/btcwallet/blockchain.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index 1b1147484..1373c0602 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -8,10 +8,10 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/chain" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -127,11 +127,26 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, pkScript []byte, } } -// GetBlock returns a raw block from the server given its hash. +// GetBlock returns a raw block from the server given its hash. For the Neutrino +// implementation of the lnwallet.BlockChainIO interface, the Neutrino GetBlock +// method is called directly. For other implementations, the block cache is used +// to wrap the call to GetBlock. // // This method is a part of the lnwallet.BlockChainIO interface. func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) { - return b.blockCache.GetBlock(blockHash, b.chain.GetBlock) + _, ok := b.chain.(*chain.NeutrinoClient) + if !ok { + return b.blockCache.GetBlock(blockHash, b.chain.GetBlock) + } + + // For the neutrino implementation of lnwallet.BlockChainIO the neutrino + // GetBlock function can be called directly since it uses the same block + // cache. However, it does not lock the block cache mutex for the given + // block hash and so that is done here. + b.blockCache.HashMutex.Lock(lntypes.Hash(*blockHash)) + defer b.blockCache.HashMutex.Unlock(lntypes.Hash(*blockHash)) + + return b.chain.GetBlock(blockHash) } // GetBlockHash returns the hash of the block in the best blockchain at the From 6ad5781bf125bfb92ffba4f716d9f254ed145252 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 30 Mar 2021 10:04:30 +0200 Subject: [PATCH 10/11] routing: add block cache to CfFilteredChainView This commit adds the block cache to the CfFilteredChainView struct and wraps its GetBlock function so that block cache mutex map is used when the call to neutrino's GetBlock function is called. --- chainreg/chainregistry.go | 4 +++- routing/chainview/interface_test.go | 6 +++++- routing/chainview/neutrino.go | 26 ++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 5093f7884..5b6d59cfe 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -319,7 +319,9 @@ func NewChainControl(cfg *Config, blockCache *blockcache.BlockCache) ( cc.ChainNotifier = neutrinonotify.New( cfg.NeutrinoCS, hintCache, hintCache, ) - cc.ChainView, err = chainview.NewCfFilteredChainView(cfg.NeutrinoCS) + cc.ChainView, err = chainview.NewCfFilteredChainView( + cfg.NeutrinoCS, blockCache, + ) if err != nil { return nil, nil, err } diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index 439b8d7f4..8f6c25607 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -895,7 +895,11 @@ var interfaceImpls = []struct { os.RemoveAll(spvDir) } - chainView, err := NewCfFilteredChainView(spvNode) + blockCache := blockcache.NewBlockCache(10000) + + chainView, err := NewCfFilteredChainView( + spvNode, blockCache, + ) if err != nil { return nil, nil, err } diff --git a/routing/chainview/neutrino.go b/routing/chainview/neutrino.go index 792e2dba8..205043911 100644 --- a/routing/chainview/neutrino.go +++ b/routing/chainview/neutrino.go @@ -11,7 +11,9 @@ import ( "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/gcs/builder" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lntypes" ) // CfFilteredChainView is an implementation of the FilteredChainView interface @@ -40,6 +42,9 @@ type CfFilteredChainView struct { // chainView. blockQueue *blockEventQueue + // blockCache is an LRU block cache. + blockCache *blockcache.BlockCache + // chainFilter is the filterMtx sync.RWMutex chainFilter map[wire.OutPoint][]byte @@ -57,13 +62,15 @@ var _ FilteredChainView = (*CfFilteredChainView)(nil) // // NOTE: The node should already be running and syncing before being passed into // this function. -func NewCfFilteredChainView(node *neutrino.ChainService) (*CfFilteredChainView, error) { +func NewCfFilteredChainView(node *neutrino.ChainService, + blockCache *blockcache.BlockCache) (*CfFilteredChainView, error) { return &CfFilteredChainView{ blockQueue: newBlockEventQueue(), quit: make(chan struct{}), rescanErrChan: make(chan error), chainFilter: make(map[wire.OutPoint][]byte), p2pNode: node, + blockCache: blockCache, }, nil } @@ -269,7 +276,7 @@ func (c *CfFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredB // If we reach this point, then there was a match, so we'll need to // fetch the block itself so we can scan it for any actual matches (as // there's a fp rate). - block, err := c.p2pNode.GetBlock(*blockHash) + block, err := c.GetBlock(*blockHash) if err != nil { return nil, err } @@ -364,3 +371,18 @@ func (c *CfFilteredChainView) FilteredBlocks() <-chan *FilteredBlock { func (c *CfFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock { return c.blockQueue.staleBlocks } + +// GetBlock is used to retrieve the block with the given hash. Since the block +// cache used by neutrino will be the same as that used by LND (since it is +// passed to neutrino on initialisation), the neutrino GetBlock method can be +// called directly since it already uses the block cache. However, neutrino +// does not lock the block cache mutex for the given block hash and so that is +// done here. +func (c *CfFilteredChainView) GetBlock(hash chainhash.Hash) ( + *btcutil.Block, error) { + + c.blockCache.HashMutex.Lock(lntypes.Hash(hash)) + defer c.blockCache.HashMutex.Unlock(lntypes.Hash(hash)) + + return c.p2pNode.GetBlock(hash) +} From f8de10511e6edbc298a72e1e2399a8afe16ed7c6 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 30 Mar 2021 10:13:58 +0200 Subject: [PATCH 11/11] chainntnfs: add block cache to NeutrinoNotifier This commit adds gives BtcdNotifier access to the block cache and wraps its GetBlock method so that the block cache's mutex map for the specific hash is used. --- chainntnfs/neutrinonotify/driver.go | 13 +++++++++--- chainntnfs/neutrinonotify/neutrino.go | 29 ++++++++++++++++++++++++--- chainntnfs/test/test_interface.go | 1 + chainreg/chainregistry.go | 2 +- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/chainntnfs/neutrinonotify/driver.go b/chainntnfs/neutrinonotify/driver.go index 68a1b2f25..dbd897180 100644 --- a/chainntnfs/neutrinonotify/driver.go +++ b/chainntnfs/neutrinonotify/driver.go @@ -5,15 +5,16 @@ import ( "fmt" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" ) // createNewNotifier creates a new instance of the ChainNotifier interface // implemented by NeutrinoNotifier. func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { - if len(args) != 3 { + if len(args) != 4 { return nil, fmt.Errorf("incorrect number of arguments to "+ - ".New(...), expected 3, instead passed %v", len(args)) + ".New(...), expected 4, instead passed %v", len(args)) } config, ok := args[0].(*neutrino.ChainService) @@ -34,7 +35,13 @@ func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) { "is incorrect, expected a chainntfs.ConfirmHintCache") } - return New(config, spendHintCache, confirmHintCache), nil + blockCache, ok := args[3].(*blockcache.BlockCache) + if !ok { + return nil, errors.New("fourth argument to neutrinonotify.New " + + "is incorrect, expected a *blockcache.BlockCache") + } + + return New(config, spendHintCache, confirmHintCache, blockCache), nil } // init registers a driver for the NeutrinoNotify concrete implementation of diff --git a/chainntnfs/neutrinonotify/neutrino.go b/chainntnfs/neutrinonotify/neutrino.go index 8842d4ce5..2320c0e6e 100644 --- a/chainntnfs/neutrinonotify/neutrino.go +++ b/chainntnfs/neutrinonotify/neutrino.go @@ -17,7 +17,9 @@ import ( "github.com/btcsuite/btcutil/gcs/builder" "github.com/lightninglabs/neutrino" "github.com/lightninglabs/neutrino/headerfs" + "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/queue" ) @@ -73,6 +75,9 @@ type NeutrinoNotifier struct { // which the transaction could have confirmed within the chain. confirmHintCache chainntnfs.ConfirmHintCache + // blockCache is an LRU block cache. + blockCache *blockcache.BlockCache + wg sync.WaitGroup quit chan struct{} } @@ -86,7 +91,8 @@ var _ chainntnfs.ChainNotifier = (*NeutrinoNotifier)(nil) // NOTE: The passed neutrino node should already be running and active before // being passed into this function. func New(node *neutrino.ChainService, spendHintCache chainntnfs.SpendHintCache, - confirmHintCache chainntnfs.ConfirmHintCache) *NeutrinoNotifier { + confirmHintCache chainntnfs.ConfirmHintCache, + blockCache *blockcache.BlockCache) *NeutrinoNotifier { return &NeutrinoNotifier{ notificationCancels: make(chan interface{}), @@ -105,6 +111,8 @@ func New(node *neutrino.ChainService, spendHintCache chainntnfs.SpendHintCache, spendHintCache: spendHintCache, confirmHintCache: confirmHintCache, + blockCache: blockCache, + quit: make(chan struct{}), } } @@ -571,7 +579,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ // In the case that we do have a match, we'll fetch the block // from the network so we can find the positional data required // to send the proper response. - block, err := n.p2pNode.GetBlock(*blockHash) + block, err := n.GetBlock(*blockHash) if err != nil { return nil, fmt.Errorf("unable to get block from network: %v", err) } @@ -628,7 +636,7 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error { // getFilteredBlock is a utility to retrieve the full filtered block from a block epoch. func (n *NeutrinoNotifier) getFilteredBlock(epoch chainntnfs.BlockEpoch) (*filteredBlock, error) { - rawBlock, err := n.p2pNode.GetBlock(*epoch.Hash) + rawBlock, err := n.GetBlock(*epoch.Hash) if err != nil { return nil, fmt.Errorf("unable to get block: %v", err) } @@ -908,6 +916,21 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, return ntfn.Event, nil } +// GetBlock is used to retrieve the block with the given hash. Since the block +// cache used by neutrino will be the same as that used by LND (since it is +// passed to neutrino on initialisation), the neutrino GetBlock method can be +// called directly since it already uses the block cache. However, neutrino +// does not lock the block cache mutex for the given block hash and so that is +// done here. +func (n *NeutrinoNotifier) GetBlock(hash chainhash.Hash) ( + *btcutil.Block, error) { + + n.blockCache.HashMutex.Lock(lntypes.Hash(hash)) + defer n.blockCache.HashMutex.Unlock(lntypes.Hash(hash)) + + return n.p2pNode.GetBlock(hash) +} + // blockEpochRegistration represents a client's intent to receive a // notification with each newly connected block. type blockEpochRegistration struct { diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index bfa4d23c3..bd6b70aca 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -1967,6 +1967,7 @@ func TestInterfaces(t *testing.T, targetBackEnd string) { newNotifier = func() (chainntnfs.TestChainNotifier, error) { return neutrinonotify.New( spvNode, hintCache, hintCache, + blockCache, ), nil } } diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 5b6d59cfe..ffe236ba6 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -317,7 +317,7 @@ func NewChainControl(cfg *Config, blockCache *blockcache.BlockCache) ( // along with the wallet's ChainSource, which are all backed by // the neutrino light client. cc.ChainNotifier = neutrinonotify.New( - cfg.NeutrinoCS, hintCache, hintCache, + cfg.NeutrinoCS, hintCache, hintCache, blockCache, ) cc.ChainView, err = chainview.NewCfFilteredChainView( cfg.NeutrinoCS, blockCache,