diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2770,10 +2770,48 @@ return result; } +static UniValue findforkhash(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error(RPCHelpMan{ + "findforkhash", + "Finds the last common block hash between the parameter chain and " + "a locator block defined by a given block hash", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, + /* default_val */ "", "The block hash of the locator block"}, + }, + RPCResult{ + "\"hash\" (string) The block hash of the last common " + "ancestor between the parameter chain and the locator\n"}, + RPCExamples{HelpExampleCli("findforkhash", + "\"00000000c937983704a73af28acdec37b049d" + "214adbda81d7e2a3dd146f6ed09\"") + + HelpExampleRpc("findforkhash", + "\"00000000c937983704a73af28acdec37b049d" + "214adbda81d7e2a3dd146f6ed09\"")}, + } + .ToStringWithResultsAndExamples()); + } + + BlockHash hash(ParseHashV(request.params[0], "hash")); + std::vector locatorHash(1, hash); + + CBlockLocator locator(locatorHash); + + LOCK(cs_main); + + const CBlockIndex *ancestorIndex = + FindForkInGlobalIndex(::ChainActive(), locator); + + return ancestorIndex->GetBlockHash().GetHex(); +} + // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- + { "blockchain", "findforkhash", findforkhash, {"blockhash"} }, { "blockchain", "getbestblockhash", getbestblockhash, {} }, { "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, { "blockchain", "getblockchaininfo", getblockchaininfo, {} }, diff --git a/test/functional/rpc_findforkhash.py b/test/functional/rpc_findforkhash.py new file mode 100644 --- /dev/null +++ b/test/functional/rpc_findforkhash.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the findforkhash RPC.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class FindForkHashTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [[]] + + def setup_network(self): + self.setup_nodes() + + def run_test(self): + genesis_hash = self.nodes[0].getbestblockhash() + + # Create the fork block + self.nodes[0].generatetoaddress( + 1, self.nodes[0].get_deterministic_priv_key().address) + fork_block_hash = self.nodes[0].getblockhash( + self.nodes[0].getblockcount()) + + # Create off-chain fork + self.nodes[0].generatetoaddress( + 1, self.nodes[0].get_deterministic_priv_key().address) + off_chain_hash = self.nodes[0].getblockhash( + self.nodes[0].getblockcount()) + self.nodes[0].invalidateblock(off_chain_hash) + + # Off-chain block + assert_equal(self.nodes[0].findforkhash( + off_chain_hash), fork_block_hash) + + # On-chain block + assert_equal(self.nodes[0].findforkhash( + fork_block_hash), fork_block_hash) + + # Non-existent block + assert_equal(self.nodes[0].findforkhash( + "0cf7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844"), genesis_hash) + + +if __name__ == '__main__': + FindForkHashTest().main()