From 5943f227dafaece87bf2a2febd3d604ca5de18cc Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 22 Feb 2018 23:27:38 -0800 Subject: [PATCH] Add mempool_tx_expiry.py test --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/mempool_tx_expiry.py | 187 ++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 qa/rpc-tests/mempool_tx_expiry.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index e2272ad43..6b0125aa8 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -32,6 +32,7 @@ testScripts=( 'mempool_reorg.py' 'mempool_tx_input_limit.py' 'mempool_nu_activation.py' + 'mempool_tx_expiry.py' 'httpbasics.py' 'zapwallettxes.py' 'proxy_test.py' diff --git a/qa/rpc-tests/mempool_tx_expiry.py b/qa/rpc-tests/mempool_tx_expiry.py new file mode 100755 index 000000000..b78af986c --- /dev/null +++ b/qa/rpc-tests/mempool_tx_expiry.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python2 +# Copyright (c) 2018 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test proper expiry for transactions >= version 3 +# + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.authproxy import JSONRPCException +from test_framework.util import assert_equal, connect_nodes, \ + connect_nodes_bi, sync_blocks, start_nodes, sync_blocks, sync_mempools, \ + wait_and_assert_operationid_status + +from decimal import Decimal +import time + +class MempoolTxExpiryTest(BitcoinTestFramework): + + def setup_nodes(self): + return start_nodes(4, self.options.tmpdir, [["-nuparams=5ba81b19:205", "-txexpirydelta=4"]] * 4) + + # Test before, at, and after expiry block + # TODO: Test case of dependent txs in reorgs + # chain is at block height 199 when run_test executes + def run_test(self): + alice = self.nodes[0].getnewaddress() + z_alice = self.nodes[0].z_getnewaddress() + bob = self.nodes[2].getnewaddress() + z_bob = self.nodes[2].z_getnewaddress() + + # When Overwinter not yet activated, no expiryheight in tx + sapling_tx = self.nodes[0].sendtoaddress(bob, 0.01) + rawtx = self.nodes[0].getrawtransaction(sapling_tx, 1) + assert_equal(rawtx["overwintered"], False) + assert("expiryheight" not in rawtx) + + self.nodes[0].generate(6) + self.sync_all() + + ## Shield one of Alice's coinbase funds to her zaddr + res = self.nodes[0].z_shieldcoinbase("*", z_alice, 0.0001, 1) + wait_and_assert_operationid_status(self.nodes[0], res['opid']) + self.nodes[0].generate(1) + self.sync_all() + + # Get balance on node 0 + bal = self.nodes[0].z_gettotalbalance() + print "Balance before zsend, after shielding 10: ", bal + assert_equal(Decimal(bal["private"]), Decimal("9.9999")) + + print "Splitting network..." + self.split_network() + + # Create transactions + zsendamount = Decimal('1.0') - Decimal('0.0001') + recipients = [] + recipients.append({"address": z_bob, "amount": zsendamount}) + myopid = self.nodes[0].z_sendmany(z_alice, recipients) + persist_shielded = wait_and_assert_operationid_status(self.nodes[0], myopid) + persist_transparent = self.nodes[0].sendtoaddress(bob, 0.01) + # Verify transparent transaction is version 3 intended for Overwinter branch + rawtx = self.nodes[0].getrawtransaction(persist_transparent, 1) + assert_equal(rawtx["version"], 3) + assert_equal(rawtx["overwintered"], True) + assert_equal(rawtx["expiryheight"], 212) + print "Blockheight at persist_transparent & persist_shielded creation:", self.nodes[0].getblockchaininfo()['blocks'] + print "Expiryheight of persist_transparent:", rawtx['expiryheight'] + # Verify shielded transaction is version 3 intended for Overwinter branch + rawtx = self.nodes[0].getrawtransaction(persist_shielded, 1) + print "Expiryheight of persist_shielded", rawtx['expiryheight'] + assert_equal(rawtx["version"], 3) + assert_equal(rawtx["overwintered"], True) + assert_equal(rawtx["expiryheight"], 212) + + print "\n Blockheight advances to less than expiry block height. After reorg, txs should persist in mempool" + assert(persist_transparent in self.nodes[0].getrawmempool()) + assert(persist_shielded in self.nodes[0].getrawmempool()) + assert_equal(set(self.nodes[2].getrawmempool()), set()) + print "mempool node 0:", self.nodes[0].getrawmempool() + print "mempool node 2:", self.nodes[2].getrawmempool() + bal = self.nodes[0].z_gettotalbalance() + print "Printing balance before persist_shielded & persist_transparent are initially mined from mempool", bal + # Txs are mined on node 0; will later be rolled back + self.nodes[0].generate(1) + print "Node 0 generated 1 block" + print "Node 0 height:", self.nodes[0].getblockchaininfo()['blocks'] + print "Node 2 height:", self.nodes[2].getblockchaininfo()['blocks'] + bal = self.nodes[0].z_gettotalbalance() + print "Printing balance after persist_shielded & persist_transparent are mined:", bal + assert_equal(set(self.nodes[0].getrawmempool()), set()) + + print "Mine 2 competing blocks on Node 2..." + blocks = self.nodes[2].generate(2) + for block in blocks: + blk = self.nodes[2].getblock(block) + print "Height: {0}, Mined block txs: {1}".format(blk["height"], blk["tx"]) + print "Connect nodes to force a reorg" + connect_nodes_bi(self.nodes,0,2) + self.is_network_split = False + + print "Syncing blocks" + sync_blocks(self.nodes) + + print "Ensure that txs are back in mempool of node 0" + print "Blockheight node 0:", self.nodes[0].getblockchaininfo()['blocks'] + print "Blockheight node 2:", self.nodes[2].getblockchaininfo()['blocks'] + print "mempool node 0: ", self.nodes[0].getrawmempool() + print "mempool node 2: ", self.nodes[2].getrawmempool() + assert(persist_transparent in self.nodes[0].getrawmempool()) + assert(persist_shielded in self.nodes[0].getrawmempool()) + bal = self.nodes[0].z_gettotalbalance() + # Mine txs to get them out of the way of mempool sync in split_network() + print "Generating another block on node 0 to clear txs from mempool" + self.nodes[0].generate(1) + assert_equal(set(self.nodes[0].getrawmempool()), set()) + sync_blocks(self.nodes) + + print "Splitting network..." + self.split_network() + + print "\n Blockheight advances to equal expiry block height. After reorg, txs should persist in mempool" + myopid = self.nodes[0].z_sendmany(z_alice, recipients) + persist_shielded_2 = wait_and_assert_operationid_status(self.nodes[0], myopid) + persist_transparent_2 = self.nodes[0].sendtoaddress(bob, 0.01) + rawtx_trans = self.nodes[0].getrawtransaction(persist_transparent_2, 1) + rawtx_shield = self.nodes[0].getrawtransaction(persist_shielded_2, 1) + print "Blockheight node 0 at persist_transparent_2 creation:", self.nodes[0].getblockchaininfo()['blocks'] + print "Blockheight node 2 at persist_transparent_2 creation:", self.nodes[2].getblockchaininfo()['blocks'] + print "Expiryheight of persist_transparent_2:", rawtx_trans['expiryheight'] + print "Expiryheight of persist_shielded_2:", rawtx_shield['expiryheight'] + blocks = self.nodes[2].generate(4) + for block in blocks: + blk = self.nodes[2].getblock(block) + print "Height: {0}, Mined block txs: {1}".format(blk["height"], blk["tx"]) + print "Connect nodes to force a reorg" + connect_nodes_bi(self.nodes, 0, 2) + self.is_network_split = False + sync_blocks(self.nodes) + print "Ensure that persist_transparent_2 & persist_shielded_2 are in mempool at expiry block height" + print "Blockheight node 0:", self.nodes[0].getblockchaininfo()['blocks'] + print "Blockheight node 2:", self.nodes[2].getblockchaininfo()['blocks'] + print "mempool node 0: ", self.nodes[0].getrawmempool() + print "mempool node 2: ", self.nodes[2].getrawmempool() + assert(persist_transparent_2 in self.nodes[0].getrawmempool()) + assert(persist_shielded_2 in self.nodes[0].getrawmempool()) + # Mine persist txs to get them out of the way of mempool sync in split_network() + self.nodes[0].generate(1) + assert_equal(set(self.nodes[0].getrawmempool()), set()) + sync_blocks(self.nodes) + print "Balance after persist_shielded_2 is mined to remove from mempool: ", self.nodes[0].z_gettotalbalance() + + print "Splitting network..." + self.split_network() + + print "\n Blockheight advances to greater than expiry block height. After reorg, txs should expire from mempool" + print "Balance before expire_shielded is sent: ", self.nodes[0].z_gettotalbalance() + myopid = self.nodes[0].z_sendmany(z_alice, recipients) + expire_shielded = wait_and_assert_operationid_status(self.nodes[0], myopid) + expire_transparent = self.nodes[0].sendtoaddress(bob, 0.01) + print "Blockheight node 0 at expire_transparent creation:", self.nodes[0].getblockchaininfo()['blocks'] + print "Blockheight node 2 at expire_shielded creation:", self.nodes[2].getblockchaininfo()['blocks'] + print "Expiryheight of expire_transparent:", self.nodes[0].getrawtransaction(expire_transparent, 1)['expiryheight'] + print "Expiryheight of expire_shielded:", self.nodes[0].getrawtransaction(expire_shielded, 1)['expiryheight'] + assert(expire_transparent in self.nodes[0].getrawmempool()) + assert(expire_shielded in self.nodes[0].getrawmempool()) + blocks = self.nodes[2].generate(6) + for block in blocks: + blk = self.nodes[2].getblock(block) + print "Height: {0}, Mined block txs: {1}".format(blk["height"], blk["tx"]) + print "Connect nodes to force a reorg" + connect_nodes_bi(self.nodes, 0, 2) + self.is_network_split = False + sync_blocks(self.nodes) + print "Ensure that expire_transparent & expire_shielded are in mempool at expiry block height" + print "mempool node 0: ", self.nodes[0].getrawmempool() + print "mempool node 2: ", self.nodes[2].getrawmempool() + assert_equal(set(self.nodes[0].getrawmempool()), set()) + print "Ensure balance of node 0 is correct" + bal = self.nodes[0].z_gettotalbalance() + print "Balance after expire_shielded has expired: ", bal + assert_equal(Decimal(bal["private"]), Decimal("7.9999")) + + +if __name__ == '__main__': + MempoolTxExpiryTest().main()