222 lines
11 KiB
Python
Executable File
222 lines
11 KiB
Python
Executable File
#!/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.util import assert_equal, \
|
|
connect_nodes_bi, sync_blocks, start_nodes, \
|
|
wait_and_assert_operationid_status
|
|
|
|
from decimal import Decimal
|
|
|
|
class MempoolTxExpiryTest(BitcoinTestFramework):
|
|
|
|
def setup_nodes(self):
|
|
return start_nodes(4, self.options.tmpdir, [["-nuparams=5ba81b19:205", "-txexpirydelta=4", "-debug=mempool"]] * 4)
|
|
|
|
# Test before, at, and after expiry block
|
|
# 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()
|
|
|
|
print "Splitting network..."
|
|
self.split_network()
|
|
|
|
# When Overwinter is activated, test dependent txs
|
|
firstTx = self.nodes[0].sendtoaddress(alice, 0.1)
|
|
firstTxInfo = self.nodes[0].getrawtransaction(firstTx, 1)
|
|
print "First tx expiry height:", firstTxInfo['expiryheight']
|
|
# Mine first transaction
|
|
self.nodes[0].generate(1)
|
|
for outpoint in firstTxInfo['vout']:
|
|
if outpoint['value'] == Decimal('0.10000000'):
|
|
vout = outpoint
|
|
break
|
|
inputs = [{'txid': firstTx, 'vout': vout['n'], 'scriptPubKey': vout['scriptPubKey']['hex']}]
|
|
outputs = {alice: 0.1}
|
|
rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
|
|
rawTxSigned = self.nodes[0].signrawtransaction(rawTx)
|
|
assert(rawTxSigned['complete'])
|
|
secondTx = self.nodes[0].sendrawtransaction(rawTxSigned['hex'])
|
|
secondTxInfo = self.nodes[0].getrawtransaction(secondTx, 1)
|
|
print "Second tx expiry height:", secondTxInfo['expiryheight']
|
|
# Mine second, dependent transaction
|
|
self.nodes[0].generate(1)
|
|
print "Mine 6 competing blocks on Node 2..."
|
|
blocks = self.nodes[2].generate(6)
|
|
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 both txs are dropped from mempool of node 0"
|
|
print "Blockheight node 0:", self.nodes[0].getblockchaininfo()['blocks']
|
|
print "Blockheight node 2:", self.nodes[2].getblockchaininfo()['blocks']
|
|
assert_equal(set(self.nodes[0].getrawmempool()), set())
|
|
assert_equal(set(self.nodes[2].getrawmempool()), set())
|
|
|
|
## 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
|
|
blockheight = self.nodes[0].getblockchaininfo()['blocks']
|
|
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"], blockheight + 5)
|
|
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"], blockheight + 5)
|
|
|
|
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()
|