From 9437c3784a69f0bbee12dcc701e828170f046da5 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 18 Apr 2018 19:43:39 -0700 Subject: [PATCH] chain/rpc: impl FilterBlocks using gc filter rescan --- chain/rpc.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/chain/rpc.go b/chain/rpc.go index fc40456..36c0f3e 100644 --- a/chain/rpc.go +++ b/chain/rpc.go @@ -13,9 +13,12 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/ltcsuite/ltcutil/gcs" + "github.com/ltcsuite/ltcutil/gcs/builder" ) // RPCClient represents a persistent client connection to a bitcoin RPC server @@ -163,6 +166,91 @@ func (c *RPCClient) BlockStamp() (*waddrmgr.BlockStamp, error) { } } +// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any +// addresses of interest. For each requested block, the corresponding compact +// filter will first be checked for matches, skipping those that do not report +// anything. If the filter returns a postive match, the full block will be +// fetched and filtered. This method returns a FilterBlocksReponse for the first +// block containing a matching address. If no matches are found in the range of +// blocks requested, the returned response will be nil. +func (c *RPCClient) FilterBlocks( + req *FilterBlocksRequest) (*FilterBlocksResponse, error) { + + blockFilterer := NewBlockFilterer(c.chainParams, req) + + // 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 the compact filter for + // each one, and matching it against the watchlist generated above. If + // the filter returns a positive match, the full block is then requested + // and scanned for addresses using the block filterer. + for i, blk := range req.Blocks { + rawFilter, err := c.GetCFilter(&blk.Hash, wire.GCSFilterRegular) + if err != nil { + return nil, err + } + + // Ensure the filter is large enough to be deserialized. + if len(rawFilter.Data) < 4 { + continue + } + + filter, err := gcs.FromNBytes(builder.DefaultP, rawFilter.Data) + if err != nil { + return nil, err + } + + // Skip any empty filters. + if filter.N() == 0 { + continue + } + + key := builder.DeriveKey(&blk.Hash) + matched, err := filter.MatchAny(key, watchList) + if err != nil { + return nil, err + } else if !matched { + continue + } + + log.Infof("Fetching block height=%d hash=%v", + blk.Height, blk.Hash) + + rawBlock, err := c.GetBlock(&blk.Hash) + if err != nil { + return nil, err + } + + if !blockFilterer.FilterBlock(rawBlock) { + continue + } + + // If any external or internal addresses were detected in this + // block, we return them to the caller so that the rescan + // windows can widened with subsequent addresses. The + // `BatchIndex` is returned so that the caller can compute the + // *next* block from which to begin again. + resp := &FilterBlocksResponse{ + BatchIndex: uint32(i), + BlockMeta: blk, + FoundExternalAddrs: blockFilterer.FoundExternal, + FoundInternalAddrs: blockFilterer.FoundInternal, + FoundOutPoints: blockFilterer.FoundOutPoints, + RelevantTxns: blockFilterer.RelevantTxns, + } + + return resp, nil + } + + // No addresses were found for this range. + return nil, nil +} + // parseBlock parses a btcws definition of the block a tx is mined it to the // Block structure of the wtxmgr package, and the block index. This is done // here since rpcclient doesn't parse this nicely for us.