zcashd/qa/rpc-tests/prioritisetransaction.py

141 lines
5.9 KiB
Python
Raw Normal View History

2019-12-04 07:29:16 -08:00
#!/usr/bin/env python3
2017-01-11 04:25:19 -08:00
# Copyright (c) 2017 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
2017-01-11 04:25:19 -08:00
from test_framework.test_framework import BitcoinTestFramework
Eliminate TX trickle bypass, sort TX invs for privacy and priority. Previously Bitcoin would send 1/4 of transactions out to all peers instantly. This causes high overhead because it makes >80% of INVs size 1. Doing so harms privacy, because it limits the amount of source obscurity a transaction can receive. These randomized broadcasts also disobeyed transaction dependencies and required use of the orphan pool. Because the orphan pool is so small this leads to poor propagation for dependent transactions. When the bypass wasn't in effect, transactions were sent in the order they were received. This avoided creating orphans but undermines privacy fairly significantly. This commit: Eliminates the bypass. The bypass is replaced by halving the average delay for outbound peers. Sorts candidate transactions for INV by their topological depth then by their feerate (then hash); removing the information leakage and providing priority service to higher fee transactions. Limits the amount of transactions sent in a single INV to 7tx/sec (and twice that for outbound); this limits the harm of low fee transaction floods, gives faster relay service to higher fee transactions. The 7 sounds lower than it really is because received advertisements need not be sent, and because the aggregate rate is multipled by the number of peers. (cherry picked from commit f2d3ba73860e875972738d1da1507124d0971ae5) Zcash: Candidate transactions for INV are not sorted by their topological depth because we haven't backported bitcoin/bitcoin#6654.
2016-04-03 19:36:47 -07:00
from test_framework.util import (
assert_equal,
connect_nodes,
initialize_chain_clean,
start_node,
sync_blocks,
sync_mempools,
)
2017-01-11 04:25:19 -08:00
from test_framework.mininode import COIN
import time
2017-01-11 04:25:19 -08:00
class PrioritiseTransactionTest (BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
def setup_network(self, split=False):
self.nodes = []
# Start nodes with tiny block size of 11kb
self.nodes.append(start_node(0, self.options.tmpdir, ["-blockprioritysize=7000", "-blockmaxsize=11000", "-maxorphantx=1000", "-relaypriority=true", "-printpriority=1"]))
self.nodes.append(start_node(1, self.options.tmpdir, ["-blockprioritysize=7000", "-blockmaxsize=11000", "-maxorphantx=1000", "-relaypriority=true", "-printpriority=1"]))
connect_nodes(self.nodes[1], 0)
self.is_network_split=False
self.sync_all()
def run_test (self):
# tx priority is calculated: priority = sum(input_value_in_base_units * input_age)/size_in_bytes
2019-12-04 07:29:16 -08:00
print("Mining 11kb blocks...")
2017-01-11 04:25:19 -08:00
self.nodes[0].generate(501)
base_fee = self.nodes[0].getnetworkinfo()['relayfee']
# 11 kb blocks will only hold about 50 txs, so this will fill mempool with older txs
taddr = self.nodes[1].getnewaddress()
2017-02-27 14:11:09 -08:00
for _ in range(900):
2017-01-11 04:25:19 -08:00
self.nodes[0].sendtoaddress(taddr, 0.1)
self.nodes[0].generate(1)
Eliminate TX trickle bypass, sort TX invs for privacy and priority. Previously Bitcoin would send 1/4 of transactions out to all peers instantly. This causes high overhead because it makes >80% of INVs size 1. Doing so harms privacy, because it limits the amount of source obscurity a transaction can receive. These randomized broadcasts also disobeyed transaction dependencies and required use of the orphan pool. Because the orphan pool is so small this leads to poor propagation for dependent transactions. When the bypass wasn't in effect, transactions were sent in the order they were received. This avoided creating orphans but undermines privacy fairly significantly. This commit: Eliminates the bypass. The bypass is replaced by halving the average delay for outbound peers. Sorts candidate transactions for INV by their topological depth then by their feerate (then hash); removing the information leakage and providing priority service to higher fee transactions. Limits the amount of transactions sent in a single INV to 7tx/sec (and twice that for outbound); this limits the harm of low fee transaction floods, gives faster relay service to higher fee transactions. The 7 sounds lower than it really is because received advertisements need not be sent, and because the aggregate rate is multipled by the number of peers. (cherry picked from commit f2d3ba73860e875972738d1da1507124d0971ae5) Zcash: Candidate transactions for INV are not sorted by their topological depth because we haven't backported bitcoin/bitcoin#6654.
2016-04-03 19:36:47 -07:00
sync_blocks(self.nodes)
# With a rate of either 7tx/s or 14tx/s per peer (depending on whether
# the connection is inbound or outbound), syncing this many transactions
# could take up to 128s. So use a higher timeout on the mempool sync.
sync_mempools(self.nodes, timeout=200)
2017-01-11 04:25:19 -08:00
# Create tx of lower value to be prioritized on node 0
# Older transactions get mined first, so this lower value, newer tx is unlikely to be mined without prioritisation
2017-01-11 04:25:19 -08:00
priority_tx_0 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
# Check that priority_tx_0 is not in block_template() prior to prioritisation
block_template = self.nodes[0].getblocktemplate()
2017-02-27 14:11:09 -08:00
in_block_template = False
2017-01-11 04:25:19 -08:00
for tx in block_template['transactions']:
if tx['hash'] == priority_tx_0:
2017-02-27 14:11:09 -08:00
in_block_template = True
2017-01-11 04:25:19 -08:00
break
2017-02-27 14:11:09 -08:00
assert_equal(in_block_template, False)
2017-01-11 04:25:19 -08:00
priority_success = self.nodes[0].prioritisetransaction(priority_tx_0, 1000, int(3 * base_fee * COIN))
assert(priority_success)
2017-01-11 04:25:19 -08:00
# Check that prioritized transaction is not in getblocktemplate()
# (not updated because no new txns)
in_block_template = False
block_template = self.nodes[0].getblocktemplate()
for tx in block_template['transactions']:
if tx['hash'] == priority_tx_0:
in_block_template = True
break
assert_equal(in_block_template, False)
# Sending a new transaction will make getblocktemplate refresh within 10s
self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
# Check that prioritized transaction is not in getblocktemplate()
# (too soon)
2017-02-27 14:11:09 -08:00
in_block_template = False
2017-01-11 04:25:19 -08:00
block_template = self.nodes[0].getblocktemplate()
for tx in block_template['transactions']:
if tx['hash'] == priority_tx_0:
2017-02-27 14:11:09 -08:00
in_block_template = True
2017-01-11 04:25:19 -08:00
break
2017-02-27 14:11:09 -08:00
assert_equal(in_block_template, False)
2017-01-11 04:25:19 -08:00
# Check that prioritized transaction is in getblocktemplate()
# getblocktemplate() will refresh after 1 min, or after 10 sec if new transaction is added to mempool
# Mempool is probed every 10 seconds. We'll give getblocktemplate() a maximum of 30 seconds to refresh
block_template = self.nodes[0].getblocktemplate()
start = time.time();
in_block_template = False
while in_block_template == False:
for tx in block_template['transactions']:
if tx['hash'] == priority_tx_0:
in_block_template = True
break
if time.time() - start > 30:
raise AssertionError("Test timed out because prioritised transaction was not returned by getblocktemplate within 30 seconds.")
time.sleep(1)
block_template = self.nodes[0].getblocktemplate()
assert(in_block_template)
2017-01-11 04:25:19 -08:00
# Node 1 doesn't get the next block, so this *shouldn't* be mined despite being prioritized on node 1
priority_tx_1 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
self.nodes[1].prioritisetransaction(priority_tx_1, 1000, int(3 * base_fee * COIN))
# Mine block on node 0
blk_hash = self.nodes[0].generate(1)
block = self.nodes[0].getblock(blk_hash[0])
self.sync_all()
# Check that priority_tx_0 was mined
mempool = self.nodes[0].getrawmempool()
assert_equal(priority_tx_0 in block['tx'], True)
assert_equal(priority_tx_0 in mempool, False)
# Check that priority_tx_1 was not mined
assert_equal(priority_tx_1 in mempool, True)
assert_equal(priority_tx_1 in block['tx'], False)
# Mine a block on node 1 and sync
blk_hash_1 = self.nodes[1].generate(1)
block_1 = self.nodes[1].getblock(blk_hash_1[0])
self.sync_all()
# Check to see if priority_tx_1 is now mined
mempool_1 = self.nodes[1].getrawmempool()
assert_equal(priority_tx_1 in mempool_1, False)
assert_equal(priority_tx_1 in block_1['tx'], True)
if __name__ == '__main__':
PrioritiseTransactionTest().main()