Merge pull request #6152 from sellout/orchard-anchor-hash

Fix finalorchardroot serialization
This commit is contained in:
Kris Nuttycombe 2022-09-23 19:53:54 -06:00 committed by GitHub
commit 34f0a973c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 312 additions and 7 deletions

View File

@ -19,3 +19,13 @@ has a memory limit of 100 MiB.
- (counter) `zcashd.wallet.batchscanner.outputs.scanned`
- (gauge) `zcashd.wallet.batchscanner.size.transactions`
- (gauge) `zcashd.wallet.batchscanner.usage.bytes`
RPC Interface
-------------
- The `finalorchardroot` field in the `getblock` result and the
`orchard.commitments.finalRoot` field in the `z_gettreestate` result have
been changed to match the byte ordering used for the `orchard.anchor`
field in the `getrawtransaction` result. These previously produced different
hash values from the `orchard.anchor` field due to having been byte-flipped
in their internal representation in zcashd.

View File

@ -57,6 +57,7 @@ BASE_SCRIPTS= [
'wallet_listreceived.py',
'mempool_tx_expiry.py',
'finalsaplingroot.py',
'finalorchardroot.py',
'wallet_orchard.py',
'wallet_overwintertx.py',
'wallet_persistence.py',

View File

@ -57,7 +57,7 @@ class Zip221Test(BitcoinTestFramework):
if height >= 35:
orchard_root = hex_str_to_bytes(
self.nodes[0].getblock(str(height))["finalorchardroot"])[::-1]
self.nodes[0].getblock(str(height))["finalorchardroot"])
v2_data = (orchard_root, 0)
else:
v2_data = None

283
qa/rpc-tests/finalorchardroot.py Executable file
View File

@ -0,0 +1,283 @@
#!/usr/bin/env python3
# Copyright (c) 2022 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
NU5_BRANCH_ID,
assert_equal,
connect_nodes_bi,
get_coinbase_address,
nuparams,
start_nodes,
wait_and_assert_operationid_status,
)
from decimal import Decimal
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
ORCHARD_TREE_EMPTY_ROOT = "ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f"
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
# Verify block header field 'hashFinalOrchardRoot' (returned in rpc as 'finalorchardroot')
# is updated when Orchard transactions with outputs (commitments) are mined into a block.
class FinalOrchardRootTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.num_nodes = 2
self.cache_behavior = 'sprout'
def setup_network(self, split=False):
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
'-txindex', # Avoid JSONRPC error: No information available about transaction
'-reindex', # Required due to enabling -txindex
nuparams(NU5_BRANCH_ID, 200),
'-debug',
]] * self.num_nodes)
connect_nodes_bi(self.nodes,0,1)
self.is_network_split=False
self.sync_all()
def run_test(self):
# Verify genesis block doesn't contain the final orchard root field.
blk = self.nodes[0].getblock("0")
assert "finalorchardroot" not in blk
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"]
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
# There is no orchard 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["orchard"]
assert "skipHash" not in treestate["orchard"]
# Verify no generated blocks before NU5 contain the empty root of the Orchard tree.
blockcount = self.nodes[0].getblockcount()
for height in range(1, blockcount + 1):
blk = self.nodes[0].getblock(str(height))
assert "finalorchardroot" not in blk
treestate = self.nodes[0].z_gettreestate(str(height))
assert_equal(treestate["height"], height)
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
if height < 100:
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")
assert "skipHash" not in treestate["orchard"]
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
assert "finalState" not in treestate["orchard"]
self.sync_all()
self.nodes[0].generate(11)
self.sync_all()
# post-NU5
for height in range(200, 211):
blk = self.nodes[0].getblock(str(height))
assert_equal(blk["finalorchardroot"], ORCHARD_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))
if height < 100:
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")
assert "skipHash" not in treestate["orchard"]
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], ORCHARD_TREE_EMPTY_ROOT)
assert_equal(treestate["orchard"]["commitments"]["finalState"], "000000")
# Node 0 shields some funds
taddr0 = get_coinbase_address(self.nodes[0])
acct0 = self.nodes[0].z_getnewaccount()['account']
addrRes0 = self.nodes[0].z_getaddressforaccount(acct0, ['orchard'])
assert_equal(acct0, addrRes0['account'])
assert_equal(addrRes0['receiver_types'], ['orchard'])
orchardAddr0 = addrRes0['address']
recipients = []
recipients.append({"address": orchardAddr0, "amount": Decimal('10')})
myopid = self.nodes[0].z_sendmany(taddr0, recipients, 1, 0, 'AllowRevealedSenders')
mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
# Verify the final Orchard root has changed
blk = self.nodes[0].getblock("211")
root = blk["finalorchardroot"]
assert root is not ORCHARD_TREE_EMPTY_ROOT
assert root is not NULL_FIELD
# Verify there is a Orchard output description (its commitment was added to tree)
result = self.nodes[0].getrawtransaction(mytxid, 1)
assert_equal(len(result["orchard"]["actions"]), 2)
# Since there is a now orchard shielded input in the blockchain,
# the orchard values should have changed
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], root)
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert new_treestate["orchard"]["commitments"]["finalRoot"] != treestate["orchard"]["commitments"]["finalRoot"]
assert new_treestate["orchard"]["commitments"]["finalState"] != treestate["orchard"]["commitments"]["finalState"]
assert_equal(len(new_treestate["orchard"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["orchard"]["commitments"]["finalState"]), 196)
treestate = new_treestate
# Mine an empty block and verify the final Orchard root does not change
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(root, self.nodes[0].getblock("212")["finalorchardroot"])
# Mine a block with a transparent tx and verify the final Orchard root does not change
taddr1 = self.nodes[1].getnewaddress()
self.nodes[0].sendtoaddress(taddr1, Decimal("1.23"))
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(len(self.nodes[0].getblock("213")["tx"]), 2)
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal("1.23"))
assert_equal(root, self.nodes[0].getblock("213")["finalorchardroot"])
# Mine a block with a Sprout shielded tx and verify the final Orchard root does not change
zaddr0 = self.nodes[0].listaddresses()[0]['sprout']['addresses'][0]
assert_equal(self.nodes[0].z_getbalance(zaddr0), Decimal('50'))
recipients = [{"address": taddr0, "amount": Decimal('12.34')}]
opid = self.nodes[0].z_sendmany(zaddr0, recipients, 1, 0, 'AllowRevealedRecipients')
wait_and_assert_operationid_status(self.nodes[0], opid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(len(self.nodes[0].getblock("214")["tx"]), 2)
assert_equal(self.nodes[0].z_getbalance(zaddr0), Decimal("37.66"))
assert_equal(root, self.nodes[0].getblock("214")["finalorchardroot"])
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], root)
assert_equal(new_treestate["orchard"], treestate["orchard"])
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"]), 266)
treestate = new_treestate
# Mine a block with a Sapling shielded tx and verify the final Orchard root does not change
saplingAddr1 = self.nodes[1].z_getnewaddress("sapling")
recipients = [{"address": saplingAddr1, "amount": Decimal('2.34')}]
myopid = self.nodes[0].z_sendmany(zaddr0, recipients, 1, 0, 'AllowRevealedAmounts')
mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(len(self.nodes[0].getblock("215")["tx"]), 2)
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal("2.34"))
assert_equal(root, self.nodes[0].getblock("215")["finalorchardroot"])
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], root)
assert_equal(new_treestate["orchard"], treestate["orchard"])
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 a block with an Orchard shielded recipient and verify the final Orchard root changes
acct1 = self.nodes[1].z_getnewaccount()['account']
addrRes1 = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
assert_equal(acct1, addrRes1['account'])
assert_equal(addrRes1['receiver_types'], ['orchard'])
orchardAddr1 = addrRes1['address']
recipients = [{"address": orchardAddr1, "amount": Decimal('2.34')}]
myopid = self.nodes[0].z_sendmany(orchardAddr0, recipients, 1, 0)
mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(len(self.nodes[0].getblock("216")["tx"]), 2)
assert_equal(self.nodes[1].z_getbalance(orchardAddr1), Decimal("2.34"))
assert root is not self.nodes[0].getblock("216")["finalorchardroot"]
# Verify there is a Orchard output description (its commitment was added to tree)
result = self.nodes[0].getrawtransaction(mytxid, 1)
assert_equal(len(result["orchard"]["actions"]), 2) # there is Orchard shielded change
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert_equal(new_treestate["sapling"], treestate["sapling"])
assert new_treestate["orchard"]["commitments"]["finalRoot"] != treestate["orchard"]["commitments"]["finalRoot"]
assert new_treestate["orchard"]["commitments"]["finalState"] != treestate["orchard"]["commitments"]["finalState"]
assert_equal(len(new_treestate["orchard"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["orchard"]["commitments"]["finalState"]), 260)
treestate = new_treestate
# Mine a block with an Orchard shielded sender and transparent recipient and verify the final Orchard root changes (because actions)
taddr2 = self.nodes[0].getnewaddress()
recipients = []
recipients.append({"address": taddr2, "amount": Decimal('2.34')})
myopid = self.nodes[1].z_sendmany(orchardAddr1, recipients, 1, 0, 'AllowRevealedRecipients')
mytxid = wait_and_assert_operationid_status(self.nodes[1], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(len(self.nodes[0].getblock("217")["tx"]), 2)
assert_equal(self.nodes[0].z_getbalance(taddr2), Decimal("2.34"))
assert root is not self.nodes[0].getblock("217")["finalorchardroot"]
# Verify there is a Orchard output description (its commitment was added to tree)
result = self.nodes[0].getrawtransaction(mytxid, 1)
assert_equal(len(result["orchard"]["actions"]), 2) # there is Orchard shielded change
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert_equal(new_treestate["sapling"], treestate["sapling"])
assert new_treestate["orchard"]["commitments"]["finalRoot"] != treestate["orchard"]["commitments"]["finalRoot"]
assert new_treestate["orchard"]["commitments"]["finalState"] != treestate["orchard"]["commitments"]["finalState"]
assert_equal(len(new_treestate["orchard"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["orchard"]["commitments"]["finalState"]), 260)
if __name__ == '__main__':
FinalOrchardRootTest().main()

View File

@ -19,7 +19,7 @@ from decimal import Decimal
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
ORCHARD_TREE_EMPTY_ROOT = "2fd8e51a03d9bbe2dd809831b1497aeb68a6e37ddf707ced4aa2d8dff13529ae"
ORCHARD_TREE_EMPTY_ROOT = "ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f"
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
# Verify block header field 'hashFinalSaplingRoot' (returned in rpc as 'finalsaplingroot')

View File

@ -241,7 +241,8 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.pushKV("authdataroot", blockindex->hashAuthDataRoot.GetHex());
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
if (nu5Active) {
result.pushKV("finalorchardroot", blockindex->hashFinalOrchardRoot.GetHex());
auto finalOrchardRootBytes = blockindex->hashFinalOrchardRoot;
result.pushKV("finalorchardroot", HexStr(finalOrchardRootBytes.begin(), finalOrchardRootBytes.end()));
}
result.pushKV("chainhistoryroot", blockindex->hashChainHistoryRoot.GetHex());
UniValue txs(UniValue::VARR);
@ -716,9 +717,14 @@ UniValue getblock(const UniValue& params, bool fHelp)
" \"version\" : n, (numeric) The block version\n"
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
" \"finalsaplingroot\" : \"xxxx\", (string) The root of the Sapling commitment tree after applying this block\n"
" \"finalorchardroot\" : \"xxxx\", (string) The root of the Orchard commitment tree after applying this block.\n"
" Omitted for blocks prior to NU5 activation. This will be the null\n"
" hash if this block has never been connected to a main chain.\n"
" \"finalorchardroot\" : \"xxxx\", (string, optional) The root of the Orchard commitment tree after\n"
" applying this block. Omitted for blocks prior to NU5 activation. This\n"
" will be the null hash if this block has never been connected to a\n"
" main chain.\n"
" NB: The serialized representation of this field returned by this method\n"
" was byte-flipped relative to its representation in the `getrawtransaction`\n"
" output in prior releases up to and including v5.2.0. This has now been\n"
" rectified.\n"
" \"tx\" : [ (array of string) The transaction ids\n"
" \"transactionid\" (string) The transaction id\n"
" ,...\n"
@ -1247,6 +1253,10 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
" \"commitments\": {\n"
" \"finalRoot\": \"hex\", (string)\n"
" NB: The serialized representation of this field returned by this method\n"
" was byte-flipped relative to its representation in the `getrawtransaction`\n"
" output in prior releases up to and including v5.2.0. This has now been\n"
" rectified.\n"
" \"finalState\": \"hex\" (string)\n"
" }\n"
" },\n"
@ -1345,7 +1355,8 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
if (nu5_activation_height.has_value()) {
UniValue orchard_result(UniValue::VOBJ);
UniValue orchard_commitments(UniValue::VOBJ);
orchard_commitments.pushKV("finalRoot", pindex->hashFinalOrchardRoot.GetHex());
auto finalOrchardRootBytes = pindex->hashFinalOrchardRoot;
orchard_commitments.pushKV("finalRoot", HexStr(finalOrchardRootBytes.begin(), finalOrchardRootBytes.end()));
bool need_skiphash = false;
OrchardMerkleFrontier tree;
if (pcoinsTip->GetOrchardAnchorAt(pindex->hashFinalOrchardRoot, tree)) {