Skip to content

Commit 19308c9

Browse files
committed
rpc: Add getblockfilter RPC method.
Retrieves and returns block filter and header from index.
1 parent ff35105 commit 19308c9

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

src/rpc/blockchain.cpp

+82
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
#include <amount.h>
99
#include <base58.h>
10+
#include <blockfilter.h>
1011
#include <chain.h>
1112
#include <chainparams.h>
1213
#include <checkpoints.h>
1314
#include <coins.h>
1415
#include <consensus/validation.h>
1516
#include <core_io.h>
1617
#include <hash.h>
18+
#include <index/blockfilterindex.h>
1719
#include <index/txindex.h>
1820
#include <key_io.h>
1921
#include <policy/feerate.h>
@@ -2296,6 +2298,85 @@ UniValue scantxoutset(const JSONRPCRequest& request)
22962298
return result;
22972299
}
22982300

2301+
static UniValue getblockfilter(const JSONRPCRequest& request)
2302+
{
2303+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
2304+
throw std::runtime_error(
2305+
RPCHelpMan{"getblockfilter",
2306+
"\nRetrieve a BIP 157 content filter for a particular block.\n",
2307+
{
2308+
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},
2309+
{"filtertype", RPCArg::Type::STR, /*default*/ "basic", "The type name of the filter"},
2310+
},
2311+
RPCResult{
2312+
"{\n"
2313+
" \"filter\" : (string) the hex-encoded filter data\n"
2314+
" \"header\" : (string) the hex-encoded filter header\n"
2315+
"}\n"
2316+
},
2317+
RPCExamples{
2318+
HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"")
2319+
}
2320+
}.ToString()
2321+
);
2322+
}
2323+
2324+
uint256 block_hash = ParseHashV(request.params[0], "blockhash");
2325+
std::string filtertype_name = "basic";
2326+
if (!request.params[1].isNull()) {
2327+
filtertype_name = request.params[1].get_str();
2328+
}
2329+
2330+
BlockFilterType filtertype;
2331+
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
2332+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
2333+
}
2334+
2335+
BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
2336+
if (!index) {
2337+
throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
2338+
}
2339+
2340+
const CBlockIndex* block_index;
2341+
bool block_was_connected;
2342+
{
2343+
LOCK(cs_main);
2344+
block_index = LookupBlockIndex(block_hash);
2345+
if (!block_index) {
2346+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
2347+
}
2348+
block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
2349+
}
2350+
2351+
bool index_ready = index->BlockUntilSyncedToCurrentChain();
2352+
2353+
BlockFilter filter;
2354+
uint256 filter_header;
2355+
if (!index->LookupFilter(block_index, filter) ||
2356+
!index->LookupFilterHeader(block_index, filter_header)) {
2357+
int err_code;
2358+
std::string errmsg = "Filter not found.";
2359+
2360+
if (!block_was_connected) {
2361+
err_code = RPC_INVALID_ADDRESS_OR_KEY;
2362+
errmsg += " Block was not connected to active chain.";
2363+
} else if (!index_ready) {
2364+
err_code = RPC_MISC_ERROR;
2365+
errmsg += " Block filters are still in the process of being indexed.";
2366+
} else {
2367+
err_code = RPC_INTERNAL_ERROR;
2368+
errmsg += " This error is unexpected and indicates index corruption.";
2369+
}
2370+
2371+
throw JSONRPCError(err_code, errmsg);
2372+
}
2373+
2374+
UniValue ret(UniValue::VOBJ);
2375+
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
2376+
ret.pushKV("header", filter_header.GetHex());
2377+
return ret;
2378+
}
2379+
22992380
// clang-format off
23002381
static const CRPCCommand commands[] =
23012382
{ // category name actor (function) argNames
@@ -2323,6 +2404,7 @@ static const CRPCCommand commands[] =
23232404

23242405
{ "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
23252406
{ "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
2407+
{ "blockchain", "getblockfilter", &getblockfilter, {"blockhash", "filtertype"} },
23262408

23272409
/* Not shown in help */
23282410
{ "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },

test/functional/rpc_getblockfilter.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test the getblockfilter RPC."""
6+
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal, assert_is_hex_string, assert_raises_rpc_error,
10+
connect_nodes, disconnect_nodes, sync_blocks
11+
)
12+
13+
FILTER_TYPES = ["basic"]
14+
15+
class GetBlockFilterTest(BitcoinTestFramework):
16+
def set_test_params(self):
17+
self.setup_clean_chain = True
18+
self.num_nodes = 2
19+
self.extra_args = [["-blockfilterindex"], []]
20+
21+
def run_test(self):
22+
# Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting
23+
disconnect_nodes(self.nodes[0], 1)
24+
25+
self.nodes[0].generate(3)
26+
self.nodes[1].generate(4)
27+
28+
assert_equal(self.nodes[0].getblockcount(), 3)
29+
chain0_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
30+
31+
# Reorg node 0 to a new chain
32+
connect_nodes(self.nodes[0], 1)
33+
sync_blocks(self.nodes)
34+
35+
assert_equal(self.nodes[0].getblockcount(), 4)
36+
chain1_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)]
37+
38+
# Test getblockfilter returns a filter for all blocks and filter types on active chain
39+
for block_hash in chain1_hashes:
40+
for filter_type in FILTER_TYPES:
41+
result = self.nodes[0].getblockfilter(block_hash, filter_type)
42+
assert_is_hex_string(result['filter'])
43+
44+
# Test getblockfilter returns a filter for all blocks and filter types on stale chain
45+
for block_hash in chain0_hashes:
46+
for filter_type in FILTER_TYPES:
47+
result = self.nodes[0].getblockfilter(block_hash, filter_type)
48+
assert_is_hex_string(result['filter'])
49+
50+
# Test getblockfilter with unknown block
51+
bad_block_hash = "0123456789abcdef" * 4
52+
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockfilter, bad_block_hash, "basic")
53+
54+
# Test getblockfilter with undefined filter type
55+
genesis_hash = self.nodes[0].getblockhash(0)
56+
assert_raises_rpc_error(-5, "Unknown filtertype", self.nodes[0].getblockfilter, genesis_hash, "unknown")
57+
58+
if __name__ == '__main__':
59+
GetBlockFilterTest().main()

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
'wallet_txn_doublespend.py',
145145
'wallet_txn_clone.py --mineblock',
146146
'feature_notifications.py',
147+
'rpc_getblockfilter.py',
147148
'rpc_invalidateblock.py',
148149
'feature_rbf.py',
149150
'mempool_packages.py',

0 commit comments

Comments
 (0)