From 9a60cdeed95c80a19959f235b9bc02f7ba55f554 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 3 Mar 2022 01:45:30 +0000 Subject: [PATCH] Add RPC test for the Orchard commitment tree bug on first NU5 testnet --- qa/pull-tester/rpc-tests.py | 1 + qa/rpc-tests/orchard_reorg.py | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100755 qa/rpc-tests/orchard_reorg.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index f22051bc7..7c8ed42f0 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -60,6 +60,7 @@ BASE_SCRIPTS= [ 'wallet_persistence.py', 'wallet_listnotes.py', # vv Tests less than 60s vv + 'orchard_reorg.py', 'fundrawtransaction.py', 'reorg_limit.py', 'mempool_limit.py', diff --git a/qa/rpc-tests/orchard_reorg.py b/qa/rpc-tests/orchard_reorg.py new file mode 100755 index 000000000..53ec9bf2a --- /dev/null +++ b/qa/rpc-tests/orchard_reorg.py @@ -0,0 +1,146 @@ +#!/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 . + +# +# Test the effect of reorgs on the Orchard commitment tree. +# + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + BLOSSOM_BRANCH_ID, + HEARTWOOD_BRANCH_ID, + CANOPY_BRANCH_ID, + NU5_BRANCH_ID, + assert_equal, + connect_nodes_bi, + get_coinbase_address, + nuparams, + start_nodes, + stop_nodes, + sync_blocks, + wait_and_assert_operationid_status, + wait_bitcoinds, +) + +from finalsaplingroot import ORCHARD_TREE_EMPTY_ROOT + +from decimal import Decimal + +class OrchardReorgTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + self.setup_clean_chain = True + + def setup_nodes(self): + return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[ + nuparams(BLOSSOM_BRANCH_ID, 1), + nuparams(HEARTWOOD_BRANCH_ID, 5), + nuparams(CANOPY_BRANCH_ID, 5), + nuparams(NU5_BRANCH_ID, 10), + '-nurejectoldversions=false', + '-experimentalfeatures', + '-orchardwallet', + # '-debug', + ]] * self.num_nodes) + + def run_test(self): + # Activate NU5 so we can test Orchard. + self.nodes[0].generate(10) + self.sync_all() + + # Generate a UA with only an Orchard receiver. + account = self.nodes[0].z_getnewaccount()['account'] + addr = self.nodes[0].z_getaddressforaccount(account, ['orchard']) + assert_equal(addr['account'], account) + assert_equal(set(addr['pools']), set(['orchard'])) + ua = addr['unifiedaddress'] + + # Before mining any Orchard notes, finalorchardroot should be the empty Orchard root. + assert_equal( + ORCHARD_TREE_EMPTY_ROOT, + self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'], + ) + + # finalorchardroot should not change if we mine additional blocks without Orchard notes. + self.nodes[0].generate(100) + self.sync_all() + assert_equal( + ORCHARD_TREE_EMPTY_ROOT, + self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'], + ) + + # Create an Orchard note. + recipients = [{'address': ua, 'amount': Decimal('12.5')}] + opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0) + wait_and_assert_operationid_status(self.nodes[0], opid) + + # After mining a block, finalorchardroot should have changed. + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + orchardroot_oneleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'] + print("Root of Orchard commitment tree with one leaf:", orchardroot_oneleaf) + assert(orchardroot_oneleaf != ORCHARD_TREE_EMPTY_ROOT) + + # finalorchardroot should not change if we mine additional blocks without Orchard notes. + self.nodes[0].generate(4) + self.sync_all() + assert_equal( + orchardroot_oneleaf, + self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'], + ) + + # Split the network so we can test the effect of a reorg. + print("Splitting the network") + self.split_network() + + # Create another Orchard note on node 0. + recipients = [{'address': ua, 'amount': Decimal('12.5')}] + opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0) + wait_and_assert_operationid_status(self.nodes[0], opid) + + # Mine two blocks on node 0. + print("Mining 2 blocks on node 0") + self.nodes[0].generate(2) + self.sync_all() + orchardroot_twoleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'] + print("Root of Orchard commitment tree with two leaves:", orchardroot_twoleaf) + assert(orchardroot_twoleaf != ORCHARD_TREE_EMPTY_ROOT) + assert(orchardroot_twoleaf != orchardroot_oneleaf) + + # Generate 10 blocks on node 2. + print("Mining alternate chain on node 2") + self.nodes[2].generate(10) + self.sync_all() + assert_equal( + orchardroot_oneleaf, + self.nodes[2].getblock(self.nodes[2].getbestblockhash())['finalorchardroot'], + ) + + # Reconnect the nodes; node 0 will re-org to node 2's chain. + print("Re-joining the network so that node 0 reorgs") + # We can't use `self.join_network()` because the coinbase-spending second Orchard + # transaction doesn't propagate from node 1's mempool to node 2 on restart. Inline + # the block-syncing parts here. + assert self.is_network_split + stop_nodes(self.nodes) + wait_bitcoinds() + self.nodes = self.setup_nodes() + connect_nodes_bi(self.nodes, 1, 2) + sync_blocks(self.nodes[1:3]) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 2, 3) + self.is_network_split = False + sync_blocks(self.nodes) + + # Verify that node 0's latest Orchard root matches what we expect. + orchardroot_postreorg = self.nodes[0].getblock(self.nodes[2].getbestblockhash())['finalorchardroot'] + print("Root of Orchard commitment tree after reorg:", orchardroot_postreorg) + assert_equal(orchardroot_postreorg, orchardroot_oneleaf) + + +if __name__ == '__main__': + OrchardReorgTest().main()