diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,5 @@ This release includes the following features and fixes: + - Add `getfinalizedblock` rpc to allow node operators to introspec + the current finalized block. \ No newline at end of file diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -176,6 +176,24 @@ return chainActive.Tip()->GetBlockHash().GetHex(); } +UniValue getfinalizedblockhash(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 0) { + throw std::runtime_error( + "getfinalizedblockhash\n" + "\nReturns the hash of the currently finalized block\n" + "\nResult:\n" + "\"hex\" (string) the block hash hex encoded\n"); + } + + LOCK(cs_main); + const CBlockIndex *blockIndexFinalized = GetFinalizedBlock(); + if (blockIndexFinalized) { + return blockIndexFinalized->GetBlockHash().GetHex(); + } + return UniValue(UniValue::VSTR); +} + void RPCNotifyBlockChange(bool ibd, const CBlockIndex *pindex) { if (pindex) { std::lock_guard lock(cs_blockchange); @@ -1815,6 +1833,7 @@ { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, /* Not shown in help */ + { "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} }, { "hidden", "finalizeblock", finalizeblock, {"blockhash"} }, { "hidden", "invalidateblock", invalidateblock, {"blockhash"} }, { "hidden", "parkblock", parkblock, {"blockhash"} }, diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -636,6 +636,8 @@ bool FinalizeBlockAndInvalidate(const Config &config, CValidationState &state, CBlockIndex *pindex); +const CBlockIndex *GetFinalizedBlock(); + /** Mark a block as invalid. */ bool InvalidateBlock(const Config &config, CValidationState &state, CBlockIndex *pindex); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2915,6 +2915,11 @@ return true; } +const CBlockIndex *GetFinalizedBlock() { + AssertLockHeld(cs_main); + return pindexFinalized; +} + bool InvalidateBlock(const Config &config, CValidationState &state, CBlockIndex *pindex) { return UnwindBlock(config, state, pindex, true); diff --git a/test/functional/abc-finalize-block.py b/test/functional/abc-finalize-block.py --- a/test/functional/abc-finalize-block.py +++ b/test/functional/abc-finalize-block.py @@ -9,11 +9,14 @@ from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, wait_until RPC_FINALIZE_INVALID_BLOCK_ERROR = 'finalize-invalid-block' +AUTO_FINALIZATION_DEPTH = 10 class FinalizeBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + self.extra_flags = [["-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)], [ + "-maxreorgdepth={}".format(AUTO_FINALIZATION_DEPTH)]] # There should only be one chaintip, which is expected_tip def only_valid_tip(self, expected_tip, other_tip_status=None): @@ -39,6 +42,8 @@ sync_blocks(self.nodes[0:2]) alt_node.invalidateblock(tip) + # We will use this later to check auto-finalization during a reorg + auto_finalized_tip = alt_node.getbestblockhash() alt_node.generate(10) # Wait for node 0 to invalidate the chain. @@ -67,19 +72,29 @@ "Test that invalidating a finalized block moves the finalization backward...") node.invalidateblock(tip) node.reconsiderblock(tip) + finalized_block = node.getblockhash( + int(node.getblockheader(tip)['height']) - AUTO_FINALIZATION_DEPTH) assert_equal(node.getbestblockhash(), tip) + assert_equal( + node.getfinalizedblockhash(), + finalized_block) # The node will now accept that chain as the finalized block moved back. node.reconsiderblock(alt_node.getbestblockhash()) assert_equal(node.getbestblockhash(), alt_node.getbestblockhash()) + assert_equal(node.getfinalizedblockhash(), auto_finalized_tip) self.log.info("Trigger reorg via block finalization...") node.finalizeblock(tip) - assert_equal(node.getbestblockhash(), tip) + assert_equal(node.getfinalizedblockhash(), + finalized_block) self.log.info("Try to finalized a block on a competiting fork...") assert_raises_rpc_error(-20, RPC_FINALIZE_INVALID_BLOCK_ERROR, node.finalizeblock, alt_node.getbestblockhash()) + assert node.getfinalizedblockhash() != alt_node.getbestblockhash(), \ + "Finalize block is alt_node's tip!" + assert_equal(node.getfinalizedblockhash(), finalized_block) if __name__ == '__main__':