add z_gettreestate rpc
This commit is contained in:
parent
d264471f2a
commit
a91a7d37cf
|
@ -16,6 +16,7 @@ from test_framework.util import (
|
|||
|
||||
from decimal import Decimal
|
||||
|
||||
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
|
||||
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
|
||||
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
|
@ -45,6 +46,19 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
# Verfify genesis block contains null field for what is now called the final sapling root field.
|
||||
blk = self.nodes[0].getblock("0")
|
||||
assert_equal(blk["finalsaplingroot"], NULL_FIELD)
|
||||
treestate = self.nodes[0].z_gettreestate("0")
|
||||
assert_equal(treestate["height"], 0)
|
||||
assert_equal(treestate["hash"], self.nodes[0].getblockhash(0))
|
||||
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||
assert("skipHash" not in treestate["sprout"])
|
||||
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||
# There is no sapling state tree yet, and trying to find it in an earlier
|
||||
# block won't succeed (we're at genesis block), so skipHash is absent.
|
||||
assert("finalState" not in treestate["sapling"])
|
||||
assert("skipHash" not in treestate["sapling"])
|
||||
|
||||
# Verify all generated blocks contain the empty root of the Sapling tree.
|
||||
blockcount = self.nodes[0].getblockcount()
|
||||
|
@ -52,6 +66,18 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
blk = self.nodes[0].getblock(str(height))
|
||||
assert_equal(blk["finalsaplingroot"], SAPLING_TREE_EMPTY_ROOT)
|
||||
|
||||
treestate = self.nodes[0].z_gettreestate(str(height))
|
||||
assert_equal(treestate["height"], height)
|
||||
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
|
||||
|
||||
assert("skipHash" not in treestate["sprout"])
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||
|
||||
assert("skipHash" not in treestate["sapling"])
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT)
|
||||
assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000")
|
||||
|
||||
# Node 0 shields some funds
|
||||
taddr0 = get_coinbase_address(self.nodes[0])
|
||||
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
|
||||
|
@ -74,6 +100,17 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||
assert_equal(len(result["vShieldedOutput"]), 1)
|
||||
|
||||
# Since there is a now sapling shielded input in the blockchain,
|
||||
# the sapling values should have changed
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70)
|
||||
treestate = new_treestate
|
||||
|
||||
# Mine an empty block and verify the final Sapling root does not change
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(1)
|
||||
|
@ -107,6 +144,15 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
assert_equal(self.nodes[1].z_getbalance(zaddr1), Decimal("10"))
|
||||
assert_equal(root, self.nodes[0].getblock("204")["finalsaplingroot"])
|
||||
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||
assert(new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"])
|
||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134)
|
||||
treestate = new_treestate
|
||||
|
||||
# Mine a block with a Sapling shielded recipient and verify the final Sapling root changes
|
||||
saplingAddr1 = self.nodes[1].z_getnewaddress("sapling")
|
||||
recipients = []
|
||||
|
@ -126,6 +172,14 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||
assert_equal(len(result["vShieldedOutput"]), 2) # there is Sapling shielded change
|
||||
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136)
|
||||
treestate = new_treestate
|
||||
|
||||
# Mine a block with a Sapling shielded sender and transparent recipient and verify the final Sapling root doesn't change
|
||||
taddr2 = self.nodes[0].getnewaddress()
|
||||
recipients = []
|
||||
|
@ -144,6 +198,10 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
|||
root = blk["finalsaplingroot"]
|
||||
assert_equal(root, self.nodes[0].getblock("205")["finalsaplingroot"])
|
||||
|
||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
FinalSaplingRootTest().main()
|
||||
|
|
|
@ -67,3 +67,41 @@ TEST(rpc, CheckExperimentalDisabledHelpMsg) {
|
|||
"experimentalfeatures=1\n"
|
||||
"athirdvalue=1\n");
|
||||
}
|
||||
|
||||
TEST(rpc, ParseHeightArg) {
|
||||
EXPECT_EQ(parseHeightArg("15", 21), 15);
|
||||
EXPECT_EQ(parseHeightArg("21", 21), 21);
|
||||
ASSERT_THROW(parseHeightArg("22", 21), UniValue);
|
||||
EXPECT_EQ(parseHeightArg("0", 21), 0);
|
||||
EXPECT_EQ(parseHeightArg("011", 21), 11); // allowed and parsed as decimal, not octal
|
||||
|
||||
// negative values count back from current height
|
||||
EXPECT_EQ(parseHeightArg("-1", 21), 21);
|
||||
EXPECT_EQ(parseHeightArg("-2", 21), 20);
|
||||
EXPECT_EQ(parseHeightArg("-22", 21), 0);
|
||||
ASSERT_THROW(parseHeightArg("-23", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("-0", 21), UniValue);
|
||||
|
||||
// currentHeight zero
|
||||
EXPECT_EQ(parseHeightArg("0", 0), 0);
|
||||
EXPECT_EQ(parseHeightArg("-1", 0), 0);
|
||||
|
||||
// maximum possible height, just beyond, far beyond
|
||||
EXPECT_EQ(parseHeightArg("2147483647", 2147483647), 2147483647);
|
||||
ASSERT_THROW(parseHeightArg("2147483648", 2147483647), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("999999999999999999999999999999999999999", 21), UniValue);
|
||||
|
||||
// disallowed characters and formats
|
||||
ASSERT_THROW(parseHeightArg("5.21", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("5.0", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("a21", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg(" 21", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("21 ", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("21x", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("+21", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("0x15", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("-0", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("-01", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("-0x15", 21), UniValue);
|
||||
ASSERT_THROW(parseHeightArg("", 21), UniValue);
|
||||
}
|
||||
|
|
|
@ -570,6 +570,37 @@ UniValue getblockhashes(const UniValue& params, bool fHelp)
|
|||
return result;
|
||||
}
|
||||
|
||||
//! Sanity-check a height argument and interpret negative values.
|
||||
int interpretHeightArg(int nHeight, int currentHeight)
|
||||
{
|
||||
if (nHeight < 0) {
|
||||
nHeight += currentHeight + 1;
|
||||
}
|
||||
if (nHeight < 0 || nHeight > currentHeight) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
|
||||
}
|
||||
return nHeight;
|
||||
}
|
||||
|
||||
//! Parse and sanity-check a height argument, return its integer representation.
|
||||
int parseHeightArg(const std::string& strHeight, int currentHeight)
|
||||
{
|
||||
// std::stoi allows (locale-dependent) whitespace and optional '+' sign,
|
||||
// whereas we want to be strict.
|
||||
regex r("(?:(-?)[1-9][0-9]*|[0-9]+)");
|
||||
if (!regex_match(strHeight, r)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter");
|
||||
}
|
||||
int nHeight;
|
||||
try {
|
||||
nHeight = std::stoi(strHeight);
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter");
|
||||
}
|
||||
return interpretHeightArg(nHeight, currentHeight);
|
||||
}
|
||||
|
||||
UniValue getblockhash(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
|
@ -587,16 +618,7 @@ 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");
|
||||
|
||||
CBlockIndex* pblockindex = chainActive[nHeight];
|
||||
const CBlockIndex* pblockindex = chainActive[interpretHeightArg(params[0].get_int(), chainActive.Height())];
|
||||
return pblockindex->GetBlockHash().GetHex();
|
||||
}
|
||||
|
||||
|
@ -711,29 +733,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("(?:(-?)[1-9][0-9]*|[0-9]+)");
|
||||
if (!regex_match(strHash, r)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block height parameter");
|
||||
}
|
||||
|
||||
int nHeight = -1;
|
||||
try {
|
||||
nHeight = std::stoi(strHash);
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
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();
|
||||
strHash = chainActive[parseHeightArg(strHash, chainActive.Height())]->GetBlockHash().GetHex();
|
||||
}
|
||||
|
||||
uint256 hash(uint256S(strHash));
|
||||
|
@ -1202,6 +1202,114 @@ UniValue getchaintips(const UniValue& params, bool fHelp)
|
|||
return res;
|
||||
}
|
||||
|
||||
UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"z_gettreestate \"hash|height\"\n"
|
||||
"Return information about the given block's tree state.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"hash|height\" (string, required) The block hash or height. Height can be negative where -1 is the last known valid block\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"hash\": \"hash\", (string) hex block hash\n"
|
||||
" \"height\": n, (numeric) block height\n"
|
||||
" \"sprout\": {\n"
|
||||
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
||||
" \"commitments\": {\n"
|
||||
" \"finalRoot\": \"hex\", (string)\n"
|
||||
" \"finalState\": \"hex\" (string)\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
" \"sapling\": {\n"
|
||||
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
||||
" \"commitments\": {\n"
|
||||
" \"finalRoot\": \"hex\", (string)\n"
|
||||
" \"finalState\": \"hex\" (string)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_gettreestate", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
||||
+ HelpExampleRpc("z_gettreestate", "\"00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5\"")
|
||||
+ HelpExampleCli("z_gettreestate", "12800")
|
||||
+ HelpExampleRpc("z_gettreestate", "12800")
|
||||
);
|
||||
|
||||
LOCK(cs_main);
|
||||
|
||||
std::string strHash = params[0].get_str();
|
||||
|
||||
// If height is supplied, find the hash
|
||||
if (strHash.size() < (2 * sizeof(uint256))) {
|
||||
strHash = chainActive[parseHeightArg(strHash, chainActive.Height())]->GetBlockHash().GetHex();
|
||||
}
|
||||
uint256 hash(uint256S(strHash));
|
||||
|
||||
if (mapBlockIndex.count(hash) == 0)
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
const CBlockIndex* const pindex = mapBlockIndex[hash];
|
||||
if (!chainActive.Contains(pindex)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Requested block is not part of the main chain");
|
||||
}
|
||||
|
||||
UniValue res(UniValue::VOBJ);
|
||||
res.pushKV("hash", pindex->GetBlockHash().GetHex());
|
||||
res.pushKV("height", pindex->nHeight);
|
||||
res.pushKV("time", int64_t(pindex->nTime));
|
||||
|
||||
// sprout
|
||||
{
|
||||
UniValue sprout_result(UniValue::VOBJ);
|
||||
UniValue sprout_commitments(UniValue::VOBJ);
|
||||
sprout_commitments.pushKV("finalRoot", pindex->hashFinalSproutRoot.GetHex());
|
||||
SproutMerkleTree tree;
|
||||
if (pcoinsTip->GetSproutAnchorAt(pindex->hashFinalSproutRoot, tree)) {
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
s << tree;
|
||||
sprout_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
||||
} else {
|
||||
// Set skipHash to the most recent block that has a finalState.
|
||||
const CBlockIndex* pindex_skip = pindex->pprev;
|
||||
while (pindex_skip && !pcoinsTip->GetSproutAnchorAt(pindex_skip->hashFinalSproutRoot, tree)) {
|
||||
pindex_skip = pindex_skip->pprev;
|
||||
}
|
||||
if (pindex_skip) {
|
||||
sprout_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
||||
}
|
||||
}
|
||||
sprout_result.pushKV("commitments", sprout_commitments);
|
||||
res.pushKV("sprout", sprout_result);
|
||||
}
|
||||
|
||||
// sapling
|
||||
{
|
||||
UniValue sapling_result(UniValue::VOBJ);
|
||||
UniValue sapling_commitments(UniValue::VOBJ);
|
||||
sapling_commitments.pushKV("finalRoot", pindex->hashFinalSaplingRoot.GetHex());
|
||||
bool need_skiphash = false;
|
||||
SaplingMerkleTree tree;
|
||||
if (pcoinsTip->GetSaplingAnchorAt(pindex->hashFinalSaplingRoot, tree)) {
|
||||
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||
s << tree;
|
||||
sapling_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
||||
} else {
|
||||
// Set skipHash to the most recent block that has a finalState.
|
||||
const CBlockIndex* pindex_skip = pindex->pprev;
|
||||
while (pindex_skip && !pcoinsTip->GetSaplingAnchorAt(pindex_skip->hashFinalSaplingRoot, tree)) {
|
||||
pindex_skip = pindex_skip->pprev;
|
||||
}
|
||||
if (pindex_skip) {
|
||||
sapling_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
||||
}
|
||||
}
|
||||
sapling_result.pushKV("commitments", sapling_commitments);
|
||||
res.pushKV("sapling", sapling_result);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
UniValue mempoolInfoToJSON()
|
||||
{
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
@ -1323,6 +1431,7 @@ static const CRPCCommand commands[] =
|
|||
{ "blockchain", "getblockhash", &getblockhash, true },
|
||||
{ "blockchain", "getblockheader", &getblockheader, true },
|
||||
{ "blockchain", "getchaintips", &getchaintips, true },
|
||||
{ "blockchain", "z_gettreestate", &z_gettreestate, true },
|
||||
{ "blockchain", "getdifficulty", &getdifficulty, true },
|
||||
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
|
||||
{ "blockchain", "getrawmempool", &getrawmempool, true },
|
||||
|
|
|
@ -185,4 +185,7 @@ std::string JSONRPCExecBatch(const UniValue& vReq);
|
|||
|
||||
extern std::string experimentalDisabledHelpMsg(const std::string& rpc, const std::vector<std::string>& enableArgs);
|
||||
|
||||
extern int interpretHeightArg(int nHeight, int currentHeight);
|
||||
extern int parseHeightArg(const std::string& strHeight, int currentHeight);
|
||||
|
||||
#endif // BITCOIN_RPCSERVER_H
|
||||
|
|
Loading…
Reference in New Issue