diff options
author | Andrew Chow <github@achow101.com> | 2022-10-13 10:43:08 -0400 |
---|---|---|
committer | Andrew Chow <github@achow101.com> | 2022-10-13 10:48:16 -0400 |
commit | bc2b1f0fe2fe0e9a71ba51484e2f9b8943dbe595 (patch) | |
tree | 255eb9da14137a6b24300845c12c6cefb2799d9c /test/functional | |
parent | 6912a28f08cabd306cafb93a2be421ae6014778d (diff) | |
parent | 626b7c8493ea1063f30ae4f62e1b36eb87adf685 (diff) |
Merge bitcoin/bitcoin#23549: Add scanblocks RPC call (attempt 2)
626b7c8493ea1063f30ae4f62e1b36eb87adf685 fuzz: add scanblocks as safe for fuzzing (James O'Beirne)
94fe5453c7f8a86136dc9a6e0b370afd32564209 test: rpc: add scanblocks functional test (Jonas Schnelli)
6ef2566b68cb8570220c13df11c5cb5a5f4f7a4d rpc: add scanblocks - scan for relevant blocks with descriptors (Jonas Schnelli)
a4258f6e81a476ce1687e2d58f7d2bf16162a172 rpc: 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-ACK 626b7c8
Tree-SHA512: f84e4dcb851b122b39e9700c58fbc31e899cdcf9b587df9505eaf1f45578cc4253e89ce2a45d1ff21bd213e31ddeedbbcad2c80810f46755b30acc17b07e2873
Diffstat (limited to 'test/functional')
-rwxr-xr-x | test/functional/rpc_scanblocks.py | 93 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 |
2 files changed, 94 insertions, 0 deletions
diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py new file mode 100755 index 0000000000..328095ee75 --- /dev/null +++ b/test/functional/rpc_scanblocks.py @@ -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() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d78c1c634f..d50d22b75c 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -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', |