rpcserver: implement rescanblocks
command backported from dcrd
This commit is contained in:
parent
03a8bf2eb4
commit
47b5478cfc
3 changed files with 146 additions and 6 deletions
|
@ -44,9 +44,9 @@ import (
|
||||||
|
|
||||||
// API version constants
|
// API version constants
|
||||||
const (
|
const (
|
||||||
jsonrpcSemverString = "1.2.0"
|
jsonrpcSemverString = "1.3.0"
|
||||||
jsonrpcSemverMajor = 1
|
jsonrpcSemverMajor = 1
|
||||||
jsonrpcSemverMinor = 2
|
jsonrpcSemverMinor = 3
|
||||||
jsonrpcSemverPatch = 0
|
jsonrpcSemverPatch = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -238,6 +238,7 @@ var rpcLimited = map[string]struct{}{
|
||||||
"notifyreceived": {},
|
"notifyreceived": {},
|
||||||
"notifyspent": {},
|
"notifyspent": {},
|
||||||
"rescan": {},
|
"rescan": {},
|
||||||
|
"rescanblocks": {},
|
||||||
"session": {},
|
"session": {},
|
||||||
|
|
||||||
// Websockets AND HTTP/S commands
|
// Websockets AND HTTP/S commands
|
||||||
|
|
|
@ -606,6 +606,15 @@ var helpDescsEnUS = map[string]string{
|
||||||
"rescan-outpoints": "List of transaction outpoints to include in the rescan",
|
"rescan-outpoints": "List of transaction outpoints to include in the rescan",
|
||||||
"rescan-endblock": "Hash of final block to 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 help.
|
||||||
"version--synopsis": "Returns the JSON-RPC API version (semver)",
|
"version--synopsis": "Returns the JSON-RPC API version (semver)",
|
||||||
"version--result0--desc": "Version objects keyed by the program or API name",
|
"version--result0--desc": "Version objects keyed by the program or API name",
|
||||||
|
@ -680,6 +689,7 @@ var rpcResultTypes = map[string][]interface{}{
|
||||||
"notifyspent": nil,
|
"notifyspent": nil,
|
||||||
"stopnotifyspent": nil,
|
"stopnotifyspent": nil,
|
||||||
"rescan": nil,
|
"rescan": nil,
|
||||||
|
"rescanblocks": {(*[]btcjson.RescannedBlock)(nil)},
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpCacher provides a concurrent safe type that provides help and usage for
|
// helpCacher provides a concurrent safe type that provides help and usage for
|
||||||
|
|
137
rpcwebsocket.go
137
rpcwebsocket.go
|
@ -74,6 +74,7 @@ var wsHandlersBeforeInit = map[string]wsCommandHandler{
|
||||||
"stopnotifyspent": handleStopNotifySpent,
|
"stopnotifyspent": handleStopNotifySpent,
|
||||||
"stopnotifyreceived": handleStopNotifyReceived,
|
"stopnotifyreceived": handleStopNotifyReceived,
|
||||||
"rescan": handleRescan,
|
"rescan": handleRescan,
|
||||||
|
"rescanblocks": handleRescanBlocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketHandler handles a new websocket client by creating a new wsClient,
|
// 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
|
// wsClientFilter tracks relevant addresses for each websocket client for
|
||||||
// the future `rescanblocks` extension. It is modified by the `loadtxfilter`
|
// the `rescanblocks` extension. It is modified by the `loadtxfilter` command.
|
||||||
// command.
|
|
||||||
//
|
//
|
||||||
// NOTE: This extension was ported from github.com/decred/dcrd
|
// NOTE: This extension was ported from github.com/decred/dcrd
|
||||||
type wsClientFilter struct {
|
type wsClientFilter struct {
|
||||||
|
@ -759,7 +759,6 @@ func (m *wsNotificationManager) notifyFilteredBlockConnected(clients map[chan st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for quitChan, wsc := range clients {
|
for quitChan, wsc := range clients {
|
||||||
|
|
||||||
// Add all discovered transactions for this client. For clients
|
// Add all discovered transactions for this client. For clients
|
||||||
// that have no new-style filter, add the empty string slice.
|
// that have no new-style filter, add the empty string slice.
|
||||||
ntfn.SubscribedTxs = subscribedTxs[quitChan]
|
ntfn.SubscribedTxs = subscribedTxs[quitChan]
|
||||||
|
@ -1273,7 +1272,7 @@ type wsClient struct {
|
||||||
|
|
||||||
// filterData is the new generation transaction filter backported from
|
// filterData is the new generation transaction filter backported from
|
||||||
// github.com/decred/dcrd for the new backported `loadtxfilter` and
|
// github.com/decred/dcrd for the new backported `loadtxfilter` and
|
||||||
// future `rescanblocks` methods.
|
// `rescanblocks` methods.
|
||||||
filterData *wsClientFilter
|
filterData *wsClientFilter
|
||||||
|
|
||||||
// Networking infrastructure.
|
// 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
|
// recoverFromReorg attempts to recover from a detected reorganize during a
|
||||||
// rescan. It fetches a new range of block shas from the database and
|
// 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
|
// verifies that the new range of blocks is on the same fork as a previous
|
||||||
|
|
Loading…
Add table
Reference in a new issue