Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mempool_packages.py
Show All 10 Lines | |||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
satoshi_round, | satoshi_round, | ||||
sync_blocks, | sync_blocks, | ||||
sync_mempools, | sync_mempools, | ||||
) | ) | ||||
MAX_ANCESTORS = 25 | # TODO: The activation code can be reverted after the new policy becomes | ||||
MAX_DESCENDANTS = 25 | # active. We don't need to test the old policy anymore then. | ||||
# Phonon dummy activation time | |||||
ACTIVATION_TIME = 2000000000 | |||||
# Replay protection time needs to be moved beyond phonon activation | |||||
REPLAY_PROTECTION_TIME = ACTIVATION_TIME * 2 | |||||
class MempoolPackagesTest(BitcoinTestFramework): | class MempoolPackagesTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [["-maxorphantx=1000"], | |||||
["-maxorphantx=1000", "-limitancestorcount=5"]] | common_params = ["-phononactivationtime={}".format(ACTIVATION_TIME), | ||||
"-replayprotectionactivationtime={}".format(REPLAY_PROTECTION_TIME)] | |||||
self.extra_args = [common_params + ["-maxorphantx=1000"], | |||||
jasonbcox: maxorphantx looks like it belongs in common_params | |||||
common_params + ["-maxorphantx=1000", "-limitancestorcount=5"]] | |||||
def skip_test_if_missing_module(self): | def skip_test_if_missing_module(self): | ||||
self.skip_if_no_wallet() | self.skip_if_no_wallet() | ||||
# Build a transaction that spends parent_txid:vout | # Build a transaction that spends parent_txid:vout | ||||
# Return amount sent | # Return amount sent | ||||
def chain_transaction(self, node, parent_txid, vout, | def chain_transaction(self, node, parent_txid, vout, | ||||
value, fee, num_outputs): | value, fee, num_outputs): | ||||
send_value = satoshi_round((value - fee) / num_outputs) | send_value = satoshi_round((value - fee) / num_outputs) | ||||
inputs = [{'txid': parent_txid, 'vout': vout}] | inputs = [{'txid': parent_txid, 'vout': vout}] | ||||
outputs = {} | outputs = {} | ||||
for i in range(num_outputs): | for i in range(num_outputs): | ||||
outputs[node.getnewaddress()] = send_value | outputs[node.getnewaddress()] = send_value | ||||
rawtx = node.createrawtransaction(inputs, outputs) | rawtx = node.createrawtransaction(inputs, outputs) | ||||
signedtx = node.signrawtransactionwithwallet(rawtx) | signedtx = node.signrawtransactionwithwallet(rawtx) | ||||
txid = node.sendrawtransaction(signedtx['hex']) | txid = node.sendrawtransaction(signedtx['hex']) | ||||
fulltx = node.getrawtransaction(txid, 1) | fulltx = node.getrawtransaction(txid, 1) | ||||
# make sure we didn't generate a change output | # make sure we didn't generate a change output | ||||
assert len(fulltx['vout']) == num_outputs | assert len(fulltx['vout']) == num_outputs | ||||
return (txid, send_value) | return (txid, send_value) | ||||
def run_test(self): | def run_test(self): | ||||
# Pre-phonon test | |||||
[n.setmocktime(ACTIVATION_TIME - 10000) for n in self.nodes] | |||||
self.run_mempool_test(max_ancestors=25, max_descendants=25) | |||||
# Post-phonon test | |||||
[n.setmocktime(ACTIVATION_TIME) for n in self.nodes] | |||||
self.run_mempool_test(max_ancestors=50, max_descendants=50) | |||||
def run_mempool_test(self, *, max_ancestors, max_descendants): | |||||
# Mine some blocks and have them mature. | # Mine some blocks and have them mature. | ||||
self.nodes[0].generate(101) | self.nodes[0].generate(101) | ||||
utxo = self.nodes[0].listunspent(10) | utxo = self.nodes[0].listunspent( | ||||
minconf=10, query_options={ | |||||
'minimumAmount': 50}) | |||||
jasonbcoxUnsubmitted Done Inline ActionsWhy is minimumAmount necessary? jasonbcox: Why is minimumAmount necessary? | |||||
dagurvalAuthorUnsubmitted Done Inline ActionsThis ensures that inputs with enough coins are selected. Without this, the second time the tests runs (post-activation), the test randomly fails, because it would randomly select coins from the first run. When it selected inputs with too few coins, the test would create transaction with negative values in outputs. dagurval: This ensures that inputs with enough coins are selected.
Without this, the second time the… | |||||
txid = utxo[0]['txid'] | txid = utxo[0]['txid'] | ||||
vout = utxo[0]['vout'] | vout = utxo[0]['vout'] | ||||
value = utxo[0]['amount'] | value = utxo[0]['amount'] | ||||
fee = Decimal("0.0001") | fee = Decimal("0.0001") | ||||
# MAX_ANCESTORS transactions off a confirmed tx should be fine | # max_ancestors transactions off a confirmed tx should be fine | ||||
chain = [] | chain = [] | ||||
for i in range(MAX_ANCESTORS): | for i in range(max_ancestors): | ||||
(txid, sent_value) = self.chain_transaction( | (txid, sent_value) = self.chain_transaction( | ||||
self.nodes[0], txid, 0, value, fee, 1) | self.nodes[0], txid, 0, value, fee, 1) | ||||
value = sent_value | value = sent_value | ||||
chain.append(txid) | chain.append(txid) | ||||
# Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor | # Check mempool has max_ancestors transactions in it, and descendant and ancestor | ||||
# count and fees should look correct | # count and fees should look correct | ||||
mempool = self.nodes[0].getrawmempool(True) | mempool = self.nodes[0].getrawmempool(True) | ||||
assert_equal(len(mempool), MAX_ANCESTORS) | assert_equal(len(mempool), max_ancestors) | ||||
descendant_count = 1 | descendant_count = 1 | ||||
descendant_fees = 0 | descendant_fees = 0 | ||||
descendant_size = 0 | descendant_size = 0 | ||||
ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) | ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) | ||||
ancestor_count = MAX_ANCESTORS | ancestor_count = max_ancestors | ||||
ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) | ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) | ||||
descendants = [] | descendants = [] | ||||
ancestors = list(chain) | ancestors = list(chain) | ||||
for x in reversed(chain): | for x in reversed(chain): | ||||
# Check that getmempoolentry is consistent with getrawmempool | # Check that getmempoolentry is consistent with getrawmempool | ||||
entry = self.nodes[0].getmempoolentry(x) | entry = self.nodes[0].getmempoolentry(x) | ||||
assert_equal(entry, mempool[x]) | assert_equal(entry, mempool[x]) | ||||
▲ Show 20 Lines • Show All 146 Lines • ▼ Show 20 Lines | def run_mempool_test(self, *, max_ancestors, max_descendants): | ||||
self.nodes[0], txid, vout, value, fee, 10) | self.nodes[0], txid, vout, value, fee, 10) | ||||
parent_transaction = txid | parent_transaction = txid | ||||
for i in range(10): | for i in range(10): | ||||
transaction_package.append( | transaction_package.append( | ||||
{'txid': txid, 'vout': i, 'amount': sent_value}) | {'txid': txid, 'vout': i, 'amount': sent_value}) | ||||
# Sign and send up to MAX_DESCENDANT transactions chained off the | # Sign and send up to MAX_DESCENDANT transactions chained off the | ||||
# parent tx | # parent tx | ||||
for i in range(MAX_DESCENDANTS - 1): | for i in range(max_descendants - 1): | ||||
utxo = transaction_package.pop(0) | utxo = transaction_package.pop(0) | ||||
(txid, sent_value) = self.chain_transaction( | (txid, sent_value) = self.chain_transaction( | ||||
self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) | self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) | ||||
if utxo['txid'] is parent_transaction: | if utxo['txid'] is parent_transaction: | ||||
tx_children.append(txid) | tx_children.append(txid) | ||||
for j in range(10): | for j in range(10): | ||||
transaction_package.append( | transaction_package.append( | ||||
{'txid': txid, 'vout': j, 'amount': sent_value}) | {'txid': txid, 'vout': j, 'amount': sent_value}) | ||||
mempool = self.nodes[0].getrawmempool(True) | mempool = self.nodes[0].getrawmempool(True) | ||||
assert_equal(mempool[parent_transaction] | assert_equal(mempool[parent_transaction] | ||||
['descendantcount'], MAX_DESCENDANTS) | ['descendantcount'], max_descendants) | ||||
assert_equal(sorted(mempool[parent_transaction] | assert_equal(sorted(mempool[parent_transaction] | ||||
['spentby']), sorted(tx_children)) | ['spentby']), sorted(tx_children)) | ||||
for child in tx_children: | for child in tx_children: | ||||
assert_equal(mempool[child]['depends'], [parent_transaction]) | assert_equal(mempool[child]['depends'], [parent_transaction]) | ||||
# Sending one more chained transaction will fail | # Sending one more chained transaction will fail | ||||
utxo = transaction_package.pop(0) | utxo = transaction_package.pop(0) | ||||
Show All 20 Lines | def run_mempool_test(self, *, max_ancestors, max_descendants): | ||||
# Tx0 -> Tx1 (vout0) | # Tx0 -> Tx1 (vout0) | ||||
# \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 | # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 | ||||
# | # | ||||
# Mine them in the next block, then generate a new tx8 that spends | # Mine them in the next block, then generate a new tx8 that spends | ||||
# Tx1 and Tx7, and add to node1's mempool, then disconnect the | # Tx1 and Tx7, and add to node1's mempool, then disconnect the | ||||
# last block. | # last block. | ||||
# Create tx0 with 2 outputs | # Create tx0 with 2 outputs | ||||
utxo = self.nodes[0].listunspent() | utxo = self.nodes[0].listunspent(query_options={'minimumAmount': 50}) | ||||
txid = utxo[0]['txid'] | txid = utxo[0]['txid'] | ||||
value = utxo[0]['amount'] | value = utxo[0]['amount'] | ||||
vout = utxo[0]['vout'] | vout = utxo[0]['vout'] | ||||
send_value = satoshi_round((value - fee) / 2) | send_value = satoshi_round((value - fee) / 2) | ||||
inputs = [{'txid': txid, 'vout': vout}] | inputs = [{'txid': txid, 'vout': vout}] | ||||
outputs = {} | outputs = {} | ||||
for i in range(2): | for i in range(2): | ||||
Show All 40 Lines |
maxorphantx looks like it belongs in common_params