Skip to content

Commit

Permalink
chain: use neutrino filters to speed up bitcoind seed recovery
Browse files Browse the repository at this point in the history
In this commit, we use neutrino filters to speed up bitcoind seed
recovery. We use the recently created `maybeShouldFetchBlock` function
to check the filters to see if we need to fetch a block at all. This
saves us from fetching, decoding, then scanning the block contents if we
know nothing is present in them.

At this point, we can also further consolidate the `FilterBlocks`
methods between the two backends, as they're now identical.
  • Loading branch information
Roasbeef committed Sep 29, 2023
1 parent fae6e74 commit a0b0db1
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 5 deletions.
107 changes: 102 additions & 5 deletions chain/bitcoind_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package chain
import (
"container/list"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"sync"
Expand All @@ -11,6 +12,8 @@ import (

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/gcs"
"github.com/btcsuite/btcd/btcutil/gcs/builder"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
Expand Down Expand Up @@ -956,6 +959,74 @@ func (c *BitcoindClient) reorg(currentBlock waddrmgr.BlockStamp,
return nil
}

// blockFilterReq is a request to fetch a neutrino filter for a block from the
// target bitocind node.
type blockFilterReq struct {
BlockHash string `json:"blockhash"`
FilterType string `json:"filtertype"`
}

// blockFilterResp is a response containing a neutrino filter for a block from
// bitcond node.
type blockFilterResp struct {
Filter string `json:"filter"`
FilterHeader string `json:"header"`
}

const (
// bitcoindFilterRPC is the name of the RPC used to fetch a block
// filter from bitcoind.
bitcoindFilterRPC = "getblockfilter"

// bitcoindFilterType is the type of filter to fetch from bitcoind.
bitcoindFilterType = "basic"
)

// fetchBlockFilter fetches the GCS filter for a block from the remote node.
func (c *BitcoindClient) fetchBlockFilter(blkHash chainhash.Hash,
) (*gcs.Filter, error) {

filterReq := blockFilterReq{
BlockHash: blkHash.String(),
FilterType: bitcoindFilterType,
}
jsonFilterReq, err := json.Marshal(filterReq)
if err != nil {
return nil, err
}

resp, err := c.chainConn.client.RawRequest(
bitcoindFilterRPC, []json.RawMessage{jsonFilterReq},
)
if err != nil {
return nil, err
}

var filterResp blockFilterResp
if err := json.Unmarshal(resp, &filterResp); err != nil {
return nil, err
}

rawFilter, err := hex.DecodeString(filterResp.Filter)
if err != nil {
return nil, err
}

filter, err := gcs.FromNBytes(
builder.DefaultP, builder.DefaultM, rawFilter,
)
if err != nil {
return nil, err
}

// Skip any empty filters.
if filter.N() == 0 {
return nil, nil
}

return filter, nil
}

// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any
// addresses of interest. Each block will be fetched and filtered sequentially,
// returning a FilterBlocksReponse for the first block containing a matching
Expand All @@ -968,12 +1039,38 @@ func (c *BitcoindClient) FilterBlocks(

blockFilterer := NewBlockFilterer(c.chainConn.cfg.ChainParams, req)

// Iterate over the requested blocks, fetching each from the rpc client.
// Each block will scanned using the reverse addresses indexes generated
// above, breaking out early if any addresses are found.
// Construct the watchlist using the addresses and outpoints contained
// in the filter blocks request.
watchList, err := buildFilterBlocksWatchList(req)
if err != nil {
return nil, err
}

// Iterate over the requested blocks, fetching each from the rpc
// client. Each block will scanned using the reverse addresses indexes
// generated above, breaking out early if any addresses are found.
for i, block := range req.Blocks {
// TODO(conner): add prefetching, since we already know we'll be
// fetching *every* block
shouldFetchBlock, err := maybeShouldFetchBlock(
c.fetchBlockFilter, block, watchList,
)
if err != nil {
return nil, err
}

// If the filter concluded that there're no matches in this
// block, then we don't need to fetch it, as there're no false
// negatives.
if !shouldFetchBlock {
log.Infof("Skipping block height=%d hash=%v, no "+
"filter match", block.Height, block.Hash)
continue
}

log.Infof("Fetching block height=%d hash=%v",
block.Height, block.Hash)

// TODO(conner): add prefetching, since we already know we'll
// be fetching *every* block
rawBlock, err := c.GetBlock(&block.Hash)
if err != nil {
return nil, err
Expand Down
28 changes: 28 additions & 0 deletions chain/bitcoind_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/ticker"

"encoding/json"
)

const (
Expand Down Expand Up @@ -327,6 +329,32 @@ func getCurrentNet(client *rpcclient.Client) (wire.BitcoinNet, error) {
}
}

// hasNeutrinoFilters returns whether or not the bitcoind node is able to serve
// neutrino filters based on its version.
func hasNeutrinoFilters(client *rpcclient.Client) (bool, error) {

Check failure on line 334 in chain/bitcoind_conn.go

View workflow job for this annotation

GitHub Actions / Format, compilation and lint check

`hasNeutrinoFilters` is unused (deadcode)
// Fetch the bitcoind version.
resp, err := client.RawRequest("getnetworkinfo", nil)
if err != nil {
return false, err
}

info := struct {
Version int64 `json:"version"`
}{}

if err := json.Unmarshal(resp, &info); err != nil {
return false, err
}

// Bitcoind returns a single value representing the semantic version:
// 10000 * CLIENT_VERSION_MAJOR + 100 * CLIENT_VERSION_MINOR + 1 *
// CLIENT_VERSION_BUILD
//
// The getblockfilter call was added in version 19.0.0, so we return
// for versions >= 190000.
return info.Version >= 190000, nil
}

// NewBitcoindClient returns a bitcoind client using the current bitcoind
// connection. This allows us to share the same connection using multiple
// clients.
Expand Down

0 comments on commit a0b0db1

Please sign in to comment.