mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-04 04:45:10 +02:00
Merge bitcoin/bitcoin#23549: Add scanblocks RPC call (attempt 2)
626b7c8493fuzz: add scanblocks as safe for fuzzing (James O'Beirne)94fe5453c7test: rpc: add scanblocks functional test (Jonas Schnelli)6ef2566b68rpc: add scanblocks - scan for relevant blocks with descriptors (Jonas Schnelli)a4258f6e81rpc: move-only: consolidate blockchain scan args (James O'Beirne) Pull request description: Revives #20664. All feedback from the previous PR has either been responded to inline or incorporated here. --- Major changes from Jonas' PR: - consolidated arguments for scantxoutset/scanblocks - substantial cleanup of the functional test Here's the range-diff (`git range-diff master jonasschnelli/2020/12/filterblocks_rpc jamesob/2021-11-scanblocks`): https://gist.github.com/jamesob/aa4a975344209f0316444b8de2ec1d18 ### Original PR description > The `scanblocks` RPC call allows one to get relevant blockhashes from a set of descriptors by scanning all blockfilters in a given range. > > **Example:** > > `scanblocks start '["addr(<bitcoin_address>)"]' 661000` (returns relevant blockhashes for `<bitcoin_address>` from blockrange 661000->tip) > > ## Why is this useful? > **Fast wallet rescans**: get the relevant blocks and only rescan those via `rescanblockchain getblockheader(<hash>)[height] getblockheader(<hash>)[height])`. A future PR may add an option to allow to provide an array of blockhashes to `rescanblockchain`. > > **prune wallet rescans**: (_needs additional changes_): together with a call to fetch blocks from the p2p network if they have been pruned, it would allow to rescan wallets back to the genesis block in pruned mode (relevant #15946). > > **SPV mode** (_needs additional changes_): it would be possible to build the blockfilterindex from the p2p network (rather then deriving them from the blocks) and thus allow some sort of hybrid-SPV mode with moderate bandwidth consumption (related #9483) ACKs for top commit: furszy: diff re-ACK626b7c8Tree-SHA512: f84e4dcb851b122b39e9700c58fbc31e899cdcf9b587df9505eaf1f45578cc4253e89ce2a45d1ff21bd213e31ddeedbbcad2c80810f46755b30acc17b07e2873
This commit is contained in:
93
test/functional/rpc_scanblocks.py
Executable file
93
test/functional/rpc_scanblocks.py
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2021 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test the scanblocks RPC call."""
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
|
||||
|
||||
class ScanblocksTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.extra_args = [["-blockfilterindex=1"], []]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
# send 1.0, mempool only
|
||||
addr_1 = node.getnewaddress()
|
||||
node.sendtoaddress(addr_1, 1.0)
|
||||
|
||||
parent_key = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
|
||||
# send 1.0, mempool only
|
||||
# childkey 5 of `parent_key`
|
||||
node.sendtoaddress("mkS4HXoTYWRTescLGaUTGbtTTYX5EjJyEE", 1.0)
|
||||
|
||||
# mine a block and assure that the mined blockhash is in the filterresult
|
||||
blockhash = self.generate(node, 1)[0]
|
||||
height = node.getblockheader(blockhash)['height']
|
||||
self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))
|
||||
|
||||
out = node.scanblocks("start", [f"addr({addr_1})"])
|
||||
assert(blockhash in out['relevant_blocks'])
|
||||
assert_equal(height, out['to_height'])
|
||||
assert_equal(0, out['from_height'])
|
||||
|
||||
# mine another block
|
||||
blockhash_new = self.generate(node, 1)[0]
|
||||
height_new = node.getblockheader(blockhash_new)['height']
|
||||
|
||||
# make sure the blockhash is not in the filter result if we set the start_height
|
||||
# to the just mined block (unlikely to hit a false positive)
|
||||
assert(blockhash not in node.scanblocks(
|
||||
"start", [f"addr({addr_1})"], height_new)['relevant_blocks'])
|
||||
|
||||
# make sure the blockhash is present when using the first mined block as start_height
|
||||
assert(blockhash in node.scanblocks(
|
||||
"start", [f"addr({addr_1})"], height)['relevant_blocks'])
|
||||
|
||||
# also test the stop height
|
||||
assert(blockhash in node.scanblocks(
|
||||
"start", [f"addr({addr_1})"], height, height)['relevant_blocks'])
|
||||
|
||||
# use the stop_height to exclude the relevant block
|
||||
assert(blockhash not in node.scanblocks(
|
||||
"start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks'])
|
||||
|
||||
# make sure the blockhash is present when using the first mined block as start_height
|
||||
assert(blockhash in node.scanblocks(
|
||||
"start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks'])
|
||||
|
||||
# test node with disabled blockfilterindex
|
||||
assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic",
|
||||
self.nodes[1].scanblocks, "start", [f"addr({addr_1})"])
|
||||
|
||||
# test unknown filtertype
|
||||
assert_raises_rpc_error(-5, "Unknown filtertype",
|
||||
node.scanblocks, "start", [f"addr({addr_1})"], 0, 10, "extended")
|
||||
|
||||
# test invalid start_height
|
||||
assert_raises_rpc_error(-1, "Invalid start_height",
|
||||
node.scanblocks, "start", [f"addr({addr_1})"], 100000000)
|
||||
|
||||
# test invalid stop_height
|
||||
assert_raises_rpc_error(-1, "Invalid stop_height",
|
||||
node.scanblocks, "start", [f"addr({addr_1})"], 10, 0)
|
||||
assert_raises_rpc_error(-1, "Invalid stop_height",
|
||||
node.scanblocks, "start", [f"addr({addr_1})"], 10, 100000000)
|
||||
|
||||
# test accessing the status (must be empty)
|
||||
assert_equal(node.scanblocks("status"), None)
|
||||
|
||||
# test aborting the current scan (there is no, must return false)
|
||||
assert_equal(node.scanblocks("abort"), False)
|
||||
|
||||
# test invalid command
|
||||
assert_raises_rpc_error(-8, "Invalid command", node.scanblocks, "foobar")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ScanblocksTest().main()
|
||||
@@ -316,6 +316,7 @@ BASE_SCRIPTS = [
|
||||
'rpc_deriveaddresses.py',
|
||||
'rpc_deriveaddresses.py --usecli',
|
||||
'p2p_ping.py',
|
||||
'rpc_scanblocks.py',
|
||||
'rpc_scantxoutset.py',
|
||||
'feature_txindex_compatibility.py',
|
||||
'feature_unsupported_utxo_db.py',
|
||||
|
||||
Reference in New Issue
Block a user