rpcserver: implement rescanblocks command backported from dcrd

This commit is contained in:
Alex 2017-01-24 18:39:46 -07:00
parent 03a8bf2eb4
commit 47b5478cfc
3 changed files with 146 additions and 6 deletions

View file

@ -44,9 +44,9 @@ import (
// API version constants
const (
jsonrpcSemverString = "1.2.0"
jsonrpcSemverString = "1.3.0"
jsonrpcSemverMajor = 1
jsonrpcSemverMinor = 2
jsonrpcSemverMinor = 3
jsonrpcSemverPatch = 0
)
@ -238,6 +238,7 @@ var rpcLimited = map[string]struct{}{
"notifyreceived": {},
"notifyspent": {},
"rescan": {},
"rescanblocks": {},
"session": {},
// Websockets AND HTTP/S commands

View file

@ -606,6 +606,15 @@ var helpDescsEnUS = map[string]string{
"rescan-outpoints": "List of transaction outpoints to include in the rescan",
"rescan-endblock": "Hash of final block to rescan",
// RescanBlocks help.
"rescanblocks--synopsis": "Rescan blocks for transactions matching the loaded transaction filter.",
"rescanblocks-blockhashes": "List of hashes to rescan. Each next block must be a child of the previous.",
"rescanblocks--result0": "List of matching blocks.",
// RescannedBlock help.
"rescannedblock-hash": "Hash of the matching block.",
"rescannedblock-transactions": "List of matching transactions, serialized and hex-encoded.",
// Version help.
"version--synopsis": "Returns the JSON-RPC API version (semver)",
"version--result0--desc": "Version objects keyed by the program or API name",
@ -680,6 +689,7 @@ var rpcResultTypes = map[string][]interface{}{
"notifyspent": nil,
"stopnotifyspent": nil,
"rescan": nil,
"rescanblocks": {(*[]btcjson.RescannedBlock)(nil)},
}
// helpCacher provides a concurrent safe type that provides help and usage for

View file

@ -74,6 +74,7 @@ var wsHandlersBeforeInit = map[string]wsCommandHandler{
"stopnotifyspent": handleStopNotifySpent,
"stopnotifyreceived": handleStopNotifyReceived,
"rescan": handleRescan,
"rescanblocks": handleRescanBlocks,
}
// WebsocketHandler handles a new websocket client by creating a new wsClient,
@ -245,8 +246,7 @@ func (m *wsNotificationManager) NotifyMempoolTx(tx *btcutil.Tx, isNew bool) {
}
// wsClientFilter tracks relevant addresses for each websocket client for
// the future `rescanblocks` extension. It is modified by the `loadtxfilter`
// command.
// the `rescanblocks` extension. It is modified by the `loadtxfilter` command.
//
// NOTE: This extension was ported from github.com/decred/dcrd
type wsClientFilter struct {
@ -759,7 +759,6 @@ func (m *wsNotificationManager) notifyFilteredBlockConnected(clients map[chan st
}
}
for quitChan, wsc := range clients {
// Add all discovered transactions for this client. For clients
// that have no new-style filter, add the empty string slice.
ntfn.SubscribedTxs = subscribedTxs[quitChan]
@ -1273,7 +1272,7 @@ type wsClient struct {
// filterData is the new generation transaction filter backported from
// github.com/decred/dcrd for the new backported `loadtxfilter` and
// future `rescanblocks` methods.
// `rescanblocks` methods.
filterData *wsClientFilter
// Networking infrastructure.
@ -2119,6 +2118,136 @@ func rescanBlock(wsc *wsClient, lookups *rescanKeys, blk *btcutil.Block) {
}
}
// rescanBlockFilter rescans a block for any relevant transactions for the
// passed lookup keys. Any discovered transactions are returned hex encoded as
// a string slice.
//
// NOTE: This extension is ported from github.com/decred/dcrd
func rescanBlockFilter(filter *wsClientFilter, block *btcutil.Block) []string {
var transactions []string
filter.mu.Lock()
for _, tx := range block.Transactions() {
msgTx := tx.MsgTx()
// Keep track of whether the transaction has already been added
// to the result. It shouldn't be added twice.
added := false
// Scan inputs if not a coinbase transaction.
if !blockchain.IsCoinBaseTx(msgTx) {
for _, input := range msgTx.TxIn {
if !filter.existsUnspentOutPoint(&input.PreviousOutPoint) {
continue
}
if !added {
transactions = append(
transactions,
txHexString(msgTx))
added = true
}
}
}
// Scan outputs.
for i, output := range msgTx.TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
output.PkScript,
activeNetParams.Params)
if err != nil {
continue
}
for _, a := range addrs {
if !filter.existsAddress(a) {
continue
}
op := wire.OutPoint{
Hash: *tx.Hash(),
Index: uint32(i),
}
filter.addUnspentOutPoint(&op)
if !added {
transactions = append(
transactions,
txHexString(msgTx))
added = true
}
}
}
}
filter.mu.Unlock()
return transactions
}
// handleRescanBlocks implements the rescanblocks command extension for
// websocket connections.
//
// NOTE: This extension is ported from github.com/decred/dcrd
func handleRescanBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) {
cmd, ok := icmd.(*btcjson.RescanBlocksCmd)
if !ok {
return nil, btcjson.ErrRPCInternal
}
// Load client's transaction filter. Must exist in order to continue.
wsc.Lock()
filter := wsc.filterData
wsc.Unlock()
if filter == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCMisc,
Message: "Transaction filter must be loaded before rescanning",
}
}
blockHashes := make([]*chainhash.Hash, len(cmd.BlockHashes))
for i := range cmd.BlockHashes {
hash, err := chainhash.NewHashFromStr(cmd.BlockHashes[i])
if err != nil {
return nil, err
}
blockHashes[i] = hash
}
discoveredData := make([]btcjson.RescannedBlock, 0, len(blockHashes))
// Iterate over each block in the request and rescan. When a block
// contains relevant transactions, add it to the response.
bc := wsc.server.server.blockManager.chain
var lastBlockHash *chainhash.Hash
for i := range blockHashes {
block, err := bc.BlockByHash(blockHashes[i])
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCBlockNotFound,
Message: "Failed to fetch block: " + err.Error(),
}
}
if lastBlockHash != nil && block.MsgBlock().Header.PrevBlock != *lastBlockHash {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: fmt.Sprintf("Block %v is not a child of %v",
blockHashes[i], lastBlockHash),
}
}
lastBlockHash = blockHashes[i]
transactions := rescanBlockFilter(filter, block)
if len(transactions) != 0 {
discoveredData = append(discoveredData, btcjson.RescannedBlock{
Hash: cmd.BlockHashes[i],
Transactions: transactions,
})
}
}
return &discoveredData, nil
}
// recoverFromReorg attempts to recover from a detected reorganize during a
// rescan. It fetches a new range of block shas from the database and
// verifies that the new range of blocks is on the same fork as a previous