#!/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()