diff --git a/qa/rpc-tests/merkle_blocks.py b/qa/rpc-tests/merkle_blocks.py index caaace9c2..48f0e6c72 100755 --- a/qa/rpc-tests/merkle_blocks.py +++ b/qa/rpc-tests/merkle_blocks.py @@ -95,5 +95,21 @@ class MerkleBlockTest(BitcoinTestFramework): result = self.nodes[0].getblock(blockhash, 0) assert(c in string.hexdigits for c in result) # verbosity 0 returns raw hex + # Test getblock heights including negatives relative to the head + assert_equal(self.nodes[0].getblock("0")["height"], 0) + assert_raises(JSONRPCException, self.nodes[0].getblock, ["108"]) + assert_equal(self.nodes[0].getblock("107")["height"], 107) + assert_equal(self.nodes[0].getblock("-1")["height"], 107) + assert_equal(self.nodes[0].getblock("-2")["height"], 106) + assert_equal(self.nodes[0].getblock("-20")["height"], 88) + assert_equal(self.nodes[0].getblock("-107")["height"], 1) + assert_equal(self.nodes[0].getblock("-108")["height"], 0) + assert_raises(JSONRPCException, self.nodes[0].getblock, ["-109"]) + assert_raises(JSONRPCException, self.nodes[0].getblock, ["-0"]) + + # Test getblockhash negative heights + assert_equal(self.nodes[0].getblockhash(-1), self.nodes[0].getblockhash(107)) + assert_equal(self.nodes[0].getblockhash(-2), self.nodes[0].getblockhash(106)) + if __name__ == '__main__': MerkleBlockTest().main() diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1fb2d814c..f9f73532f 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -578,7 +578,7 @@ UniValue getblockhash(const UniValue& params, bool fHelp) "getblockhash index\n" "\nReturns hash of block in best-block-chain at index provided.\n" "\nArguments:\n" - "1. index (numeric, required) The block index\n" + "1. index (numeric, required) The block index. If negative then -1 is the last known valid block\n" "\nResult:\n" "\"hash\" (string) The block hash\n" "\nExamples:\n" @@ -589,6 +589,11 @@ UniValue getblockhash(const UniValue& params, bool fHelp) LOCK(cs_main); int nHeight = params[0].get_int(); + + if (nHeight < 0) { + nHeight += chainActive.Height() + 1; + } + if (nHeight < 0 || nHeight > chainActive.Height()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); @@ -662,7 +667,7 @@ UniValue getblock(const UniValue& params, bool fHelp) "If verbosity is 1, returns an Object with information about the block.\n" "If verbosity is 2, returns an Object with information about the block and information about each transaction. \n" "\nArguments:\n" - "1. \"hash|height\" (string, required) The block hash or height\n" + "1. \"hash|height\" (string, required) The block hash or height. Height can be negative where -1 is the last known valid block\n" "2. verbosity (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data\n" "\nResult (for verbosity = 0):\n" "\"data\" (string) A string that is serialized, hex-encoded data for the block.\n" @@ -708,7 +713,7 @@ UniValue getblock(const UniValue& params, bool fHelp) // If height is supplied, find the hash if (strHash.size() < (2 * sizeof(uint256))) { // std::stoi allows characters, whereas we want to be strict - regex r("[[:digit:]]+"); + regex r("(?:(-?)[1-9][0-9]*|[0-9]+)"); if (!regex_match(strHash, r)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter"); } @@ -721,9 +726,14 @@ UniValue getblock(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter"); } + if (nHeight < 0) { + nHeight += chainActive.Height() + 1; + } + if (nHeight < 0 || nHeight > chainActive.Height()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); } + strHash = chainActive[nHeight]->GetBlockHash().GetHex(); } diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 6effcfff6..3c0593f83 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -310,7 +310,8 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) * getblock */ BOOST_CHECK_THROW(CallRPC("getblock too many args"), runtime_error); - BOOST_CHECK_THROW(CallRPC("getblock -1"), runtime_error); + BOOST_CHECK_NO_THROW(CallRPC("getblock -1")); // negative heights relative are allowed + BOOST_CHECK_THROW(CallRPC("getblock -2147483647"), runtime_error); // allowed, but chain tip - height < 0 BOOST_CHECK_THROW(CallRPC("getblock 2147483647"), runtime_error); // allowed, but > height of active chain tip BOOST_CHECK_THROW(CallRPC("getblock 2147483648"), runtime_error); // not allowed, > int32 used for nHeight BOOST_CHECK_THROW(CallRPC("getblock 100badchars"), runtime_error);