diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -2684,6 +2686,89 @@ return result; } +static UniValue getblockfilter(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() < 1 || + request.params.size() > 2) { + throw std::runtime_error(RPCHelpMan{ + "getblockfilter", + "\nRetrieve a BIP 157 content filter for a particular block.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, + "The hash of the block"}, + {"filtertype", RPCArg::Type::STR, /*default*/ "basic", + "The type name of the filter"}, + }, + RPCResult{"{\n" + " \"filter\" : (string) the hex-encoded filter data\n" + " \"header\" : (string) the hex-encoded filter header\n" + "}\n"}, + RPCExamples{HelpExampleCli( + "getblockfilter", + "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6" + "ed09\" \"basic\"")}}.ToString()); + } + + const BlockHash block_hash(ParseHashV(request.params[0], "blockhash")); + std::string filtertype_name = "basic"; + if (!request.params[1].isNull()) { + filtertype_name = request.params[1].get_str(); + } + + BlockFilterType filtertype; + if (!BlockFilterTypeByName(filtertype_name, filtertype)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype"); + } + + BlockFilterIndex *index = GetBlockFilterIndex(filtertype); + if (!index) { + throw JSONRPCError(RPC_MISC_ERROR, + "Index is not enabled for filtertype " + + filtertype_name); + } + + const CBlockIndex *block_index; + bool block_was_connected; + { + LOCK(cs_main); + block_index = LookupBlockIndex(block_hash); + if (!block_index) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + block_was_connected = block_index->IsValid(BlockValidity::SCRIPTS); + } + + bool index_ready = index->BlockUntilSyncedToCurrentChain(); + + BlockFilter filter; + uint256 filter_header; + if (!index->LookupFilter(block_index, filter) || + !index->LookupFilterHeader(block_index, filter_header)) { + int err_code; + std::string errmsg = "Filter not found."; + + if (!block_was_connected) { + err_code = RPC_INVALID_ADDRESS_OR_KEY; + errmsg += " Block was not connected to active chain."; + } else if (!index_ready) { + err_code = RPC_MISC_ERROR; + errmsg += + " Block filters are still in the process of being indexed."; + } else { + err_code = RPC_INTERNAL_ERROR; + errmsg += + " This error is unexpected and indicates index corruption."; + } + + throw JSONRPCError(err_code, errmsg); + } + + UniValue ret(UniValue::VOBJ); + ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); + ret.pushKV("header", filter_header.GetHex()); + return ret; +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -2710,6 +2795,7 @@ { "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, { "blockchain", "scantxoutset", scantxoutset, {"action", "scanobjects"} }, + { "blockchain", "getblockfilter", getblockfilter, {"blockhash", "filtertype"} }, /* Not shown in help */ { "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} }, diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py new file mode 100755 --- /dev/null +++ b/test/functional/rpc_getblockfilter.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 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 getblockfilter RPC.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, assert_is_hex_string, assert_raises_rpc_error, + connect_nodes, disconnect_nodes, sync_blocks +) + +FILTER_TYPES = ["basic"] + + +class GetBlockFilterTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [["-blockfilterindex", + "-noparkdeepreorg"], ["-noparkdeepreorg"]] + + def run_test(self): + # Create two chains by disconnecting nodes 0 & 1, mining, then + # reconnecting + disconnect_nodes(self.nodes[0], self.nodes[1]) + + self.nodes[0].generate(3) + self.nodes[1].generate(4) + + assert_equal(self.nodes[0].getblockcount(), 3) + chain0_hashes = [self.nodes[0].getblockhash( + block_height) for block_height in range(4)] + + # Reorg node 0 to a new chain + connect_nodes(self.nodes[0], self.nodes[1]) + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getblockcount(), 4) + chain1_hashes = [self.nodes[0].getblockhash( + block_height) for block_height in range(4)] + + # Test getblockfilter returns a filter for all blocks and filter types + # on active chain + for block_hash in chain1_hashes: + for filter_type in FILTER_TYPES: + result = self.nodes[0].getblockfilter(block_hash, filter_type) + assert_is_hex_string(result['filter']) + + # Test getblockfilter returns a filter for all blocks and filter types + # on stale chain + for block_hash in chain0_hashes: + for filter_type in FILTER_TYPES: + result = self.nodes[0].getblockfilter(block_hash, filter_type) + assert_is_hex_string(result['filter']) + + # Test getblockfilter with unknown block + bad_block_hash = "0123456789abcdef" * 4 + assert_raises_rpc_error(-5, + "Block not found", + self.nodes[0].getblockfilter, + bad_block_hash, + "basic") + + # Test getblockfilter with undefined filter type + genesis_hash = self.nodes[0].getblockhash(0) + assert_raises_rpc_error(-5, + "Unknown filtertype", + self.nodes[0].getblockfilter, + genesis_hash, + "unknown") + + +if __name__ == '__main__': + GetBlockFilterTest().main()