Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_invalid_tx.py
Show All 12 Lines | |||||
from test_framework.p2p import P2PDataStore | from test_framework.p2p import P2PDataStore | ||||
from test_framework.script import OP_TRUE, CScript | from test_framework.script import OP_TRUE, CScript | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.txtools import pad_tx | from test_framework.txtools import pad_tx | ||||
from test_framework.util import assert_equal | from test_framework.util import assert_equal | ||||
class InvalidTxRequestTest(BitcoinTestFramework): | class InvalidTxRequestTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.extra_args = [ | self.extra_args = [ | ||||
["-acceptnonstdtxn=1", ] | [ | ||||
"-acceptnonstdtxn=1", | |||||
] | |||||
] | ] | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
def bootstrap_p2p(self, *, num_connections=1): | def bootstrap_p2p(self, *, num_connections=1): | ||||
"""Add a P2P connection to the node. | """Add a P2P connection to the node. | ||||
Helper to connect and wait for version handshake.""" | Helper to connect and wait for version handshake.""" | ||||
for _ in range(num_connections): | for _ in range(num_connections): | ||||
Show All 9 Lines | class InvalidTxRequestTest(BitcoinTestFramework): | ||||
def run_test(self): | def run_test(self): | ||||
node = self.nodes[0] # convenience reference to the node | node = self.nodes[0] # convenience reference to the node | ||||
self.bootstrap_p2p() # Add one p2p connection to the node | self.bootstrap_p2p() # Add one p2p connection to the node | ||||
best_block = self.nodes[0].getbestblockhash() | best_block = self.nodes[0].getbestblockhash() | ||||
tip = int(best_block, 16) | tip = int(best_block, 16) | ||||
best_block_time = self.nodes[0].getblock(best_block)['time'] | best_block_time = self.nodes[0].getblock(best_block)["time"] | ||||
block_time = best_block_time + 1 | block_time = best_block_time + 1 | ||||
self.log.info("Create a new block with an anyone-can-spend coinbase.") | self.log.info("Create a new block with an anyone-can-spend coinbase.") | ||||
height = 1 | height = 1 | ||||
block = create_block(tip, create_coinbase(height), block_time) | block = create_block(tip, create_coinbase(height), block_time) | ||||
block.solve() | block.solve() | ||||
# Save the coinbase for later | # Save the coinbase for later | ||||
block1 = block | block1 = block | ||||
tip = block.sha256 | tip = block.sha256 | ||||
node.p2ps[0].send_blocks_and_test([block], node, success=True) | node.p2ps[0].send_blocks_and_test([block], node, success=True) | ||||
self.log.info("Mature the block.") | self.log.info("Mature the block.") | ||||
self.generatetoaddress(self.nodes[0], | self.generatetoaddress( | ||||
100, self.nodes[0].get_deterministic_priv_key().address) | self.nodes[0], 100, self.nodes[0].get_deterministic_priv_key().address | ||||
) | |||||
# Iterate through a list of known invalid transaction types, ensuring each is | # Iterate through a list of known invalid transaction types, ensuring each is | ||||
# rejected. Some are consensus invalid and some just violate policy. | # rejected. Some are consensus invalid and some just violate policy. | ||||
for BadTxTemplate in invalid_txs.iter_all_templates(): | for BadTxTemplate in invalid_txs.iter_all_templates(): | ||||
self.log.info( | self.log.info("Testing invalid transaction: %s", BadTxTemplate.__name__) | ||||
"Testing invalid transaction: %s", | |||||
BadTxTemplate.__name__) | |||||
template = BadTxTemplate(spend_block=block1) | template = BadTxTemplate(spend_block=block1) | ||||
tx = template.get_tx() | tx = template.get_tx() | ||||
node.p2ps[0].send_txs_and_test( | node.p2ps[0].send_txs_and_test( | ||||
[tx], node, success=False, | [tx], | ||||
node, | |||||
success=False, | |||||
expect_disconnect=template.expect_disconnect, | expect_disconnect=template.expect_disconnect, | ||||
reject_reason=template.reject_reason, | reject_reason=template.reject_reason, | ||||
) | ) | ||||
if template.expect_disconnect: | if template.expect_disconnect: | ||||
self.log.info("Reconnecting to peer") | self.log.info("Reconnecting to peer") | ||||
self.reconnect_p2p() | self.reconnect_p2p() | ||||
# Make two p2p connections to provide the node with orphans | # Make two p2p connections to provide the node with orphans | ||||
# * p2ps[0] will send valid orphan txs (one with low fee) | # * p2ps[0] will send valid orphan txs (one with low fee) | ||||
# * p2ps[1] will send an invalid orphan tx (and is later disconnected for that) | # * p2ps[1] will send an invalid orphan tx (and is later disconnected for that) | ||||
self.reconnect_p2p(num_connections=2) | self.reconnect_p2p(num_connections=2) | ||||
self.log.info('Test orphan transaction handling ... ') | self.log.info("Test orphan transaction handling ... ") | ||||
# Create a root transaction that we withold until all dependend transactions | # Create a root transaction that we withold until all dependend transactions | ||||
# are sent out and in the orphan cache | # are sent out and in the orphan cache | ||||
SCRIPT_PUB_KEY_OP_TRUE = CScript([OP_TRUE]) | SCRIPT_PUB_KEY_OP_TRUE = CScript([OP_TRUE]) | ||||
tx_withhold = CTransaction() | tx_withhold = CTransaction() | ||||
tx_withhold.vin.append( | tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) | ||||
CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) | |||||
tx_withhold.vout.append( | tx_withhold.vout.append( | ||||
CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
) | |||||
pad_tx(tx_withhold) | pad_tx(tx_withhold) | ||||
tx_withhold.calc_sha256() | tx_withhold.calc_sha256() | ||||
# Our first orphan tx with some outputs to create further orphan txs | # Our first orphan tx with some outputs to create further orphan txs | ||||
tx_orphan_1 = CTransaction() | tx_orphan_1 = CTransaction() | ||||
tx_orphan_1.vin.append( | tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) | ||||
CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) | |||||
tx_orphan_1.vout = [ | tx_orphan_1.vout = [ | ||||
CTxOut( | CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
nValue=10 * COIN, | ] * 3 | ||||
scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 | |||||
pad_tx(tx_orphan_1) | pad_tx(tx_orphan_1) | ||||
tx_orphan_1.calc_sha256() | tx_orphan_1.calc_sha256() | ||||
# A valid transaction with low fee | # A valid transaction with low fee | ||||
tx_orphan_2_no_fee = CTransaction() | tx_orphan_2_no_fee = CTransaction() | ||||
tx_orphan_2_no_fee.vin.append( | tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) | ||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) | |||||
tx_orphan_2_no_fee.vout.append( | tx_orphan_2_no_fee.vout.append( | ||||
CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
) | |||||
pad_tx(tx_orphan_2_no_fee) | pad_tx(tx_orphan_2_no_fee) | ||||
# A valid transaction with sufficient fee | # A valid transaction with sufficient fee | ||||
tx_orphan_2_valid = CTransaction() | tx_orphan_2_valid = CTransaction() | ||||
tx_orphan_2_valid.vin.append( | tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) | ||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) | |||||
tx_orphan_2_valid.vout.append( | tx_orphan_2_valid.vout.append( | ||||
CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
) | |||||
tx_orphan_2_valid.calc_sha256() | tx_orphan_2_valid.calc_sha256() | ||||
pad_tx(tx_orphan_2_valid) | pad_tx(tx_orphan_2_valid) | ||||
# An invalid transaction with negative fee | # An invalid transaction with negative fee | ||||
tx_orphan_2_invalid = CTransaction() | tx_orphan_2_invalid = CTransaction() | ||||
tx_orphan_2_invalid.vin.append( | tx_orphan_2_invalid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) | ||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) | |||||
tx_orphan_2_invalid.vout.append( | tx_orphan_2_invalid.vout.append( | ||||
CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
) | |||||
pad_tx(tx_orphan_2_invalid) | pad_tx(tx_orphan_2_invalid) | ||||
tx_orphan_2_invalid.calc_sha256() | tx_orphan_2_invalid.calc_sha256() | ||||
self.log.info('Send the orphans ... ') | self.log.info("Send the orphans ... ") | ||||
# Send valid orphan txs from p2ps[0] | # Send valid orphan txs from p2ps[0] | ||||
node.p2ps[0].send_txs_and_test( | node.p2ps[0].send_txs_and_test( | ||||
[tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) | [tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False | ||||
) | |||||
# Send invalid tx from p2ps[1] | # Send invalid tx from p2ps[1] | ||||
node.p2ps[1].send_txs_and_test( | node.p2ps[1].send_txs_and_test([tx_orphan_2_invalid], node, success=False) | ||||
[tx_orphan_2_invalid], node, success=False) | |||||
# Mempool should be empty | # Mempool should be empty | ||||
assert_equal(0, node.getmempoolinfo()['size']) | assert_equal(0, node.getmempoolinfo()["size"]) | ||||
# p2ps[1] is still connected | # p2ps[1] is still connected | ||||
assert_equal(2, len(node.getpeerinfo())) | assert_equal(2, len(node.getpeerinfo())) | ||||
self.log.info('Send the withhold tx ... ') | self.log.info("Send the withhold tx ... ") | ||||
with node.assert_debug_log(expected_msgs=["bad-txns-in-belowout"]): | with node.assert_debug_log(expected_msgs=["bad-txns-in-belowout"]): | ||||
node.p2ps[0].send_txs_and_test([tx_withhold], node, success=True) | node.p2ps[0].send_txs_and_test([tx_withhold], node, success=True) | ||||
# Transactions that should end up in the mempool | # Transactions that should end up in the mempool | ||||
expected_mempool = { | expected_mempool = { | ||||
t.hash | t.hash | ||||
for t in [ | for t in [ | ||||
tx_withhold, # The transaction that is the root for all orphans | tx_withhold, # The transaction that is the root for all orphans | ||||
tx_orphan_1, # The orphan transaction that splits the coins | tx_orphan_1, # The orphan transaction that splits the coins | ||||
# The valid transaction (with sufficient fee) | # The valid transaction (with sufficient fee) | ||||
tx_orphan_2_valid, | tx_orphan_2_valid, | ||||
] | ] | ||||
} | } | ||||
# Transactions that do not end up in the mempool | # Transactions that do not end up in the mempool | ||||
# tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx) | # tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx) | ||||
# tx_orphan_invaid, because it has negative fee (p2ps[1] is | # tx_orphan_invaid, because it has negative fee (p2ps[1] is | ||||
# disconnected for relaying that tx) | # disconnected for relaying that tx) | ||||
# p2ps[1] is no longer connected | # p2ps[1] is no longer connected | ||||
self.wait_until(lambda: 1 == len(node.getpeerinfo()), | self.wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) | ||||
timeout=12) | |||||
assert_equal(expected_mempool, set(node.getrawmempool())) | assert_equal(expected_mempool, set(node.getrawmempool())) | ||||
self.log.info('Test orphan pool overflow') | self.log.info("Test orphan pool overflow") | ||||
orphan_tx_pool = [CTransaction() for _ in range(101)] | orphan_tx_pool = [CTransaction() for _ in range(101)] | ||||
for i in range(len(orphan_tx_pool)): | for i in range(len(orphan_tx_pool)): | ||||
orphan_tx_pool[i].vin.append(CTxIn(outpoint=COutPoint(i, 333))) | orphan_tx_pool[i].vin.append(CTxIn(outpoint=COutPoint(i, 333))) | ||||
orphan_tx_pool[i].vout.append( | orphan_tx_pool[i].vout.append( | ||||
CTxOut( | CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
nValue=11 * COIN, | ) | ||||
scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | |||||
pad_tx(orphan_tx_pool[i]) | pad_tx(orphan_tx_pool[i]) | ||||
with node.assert_debug_log(['orphanage overflow, removed 1 tx']): | with node.assert_debug_log(["orphanage overflow, removed 1 tx"]): | ||||
node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) | node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) | ||||
rejected_parent = CTransaction() | rejected_parent = CTransaction() | ||||
rejected_parent.vin.append( | rejected_parent.vin.append( | ||||
CTxIn( | CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0)) | ||||
outpoint=COutPoint( | ) | ||||
tx_orphan_2_invalid.sha256, | |||||
0))) | |||||
rejected_parent.vout.append( | rejected_parent.vout.append( | ||||
CTxOut( | CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) | ||||
nValue=11 * COIN, | ) | ||||
scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) | |||||
pad_tx(rejected_parent) | pad_tx(rejected_parent) | ||||
rejected_parent.rehash() | rejected_parent.rehash() | ||||
with node.assert_debug_log([f'not keeping orphan with rejected parents {rejected_parent.hash}']): | with node.assert_debug_log( | ||||
node.p2ps[0].send_txs_and_test( | [f"not keeping orphan with rejected parents {rejected_parent.hash}"] | ||||
[rejected_parent], node, success=False) | ): | ||||
node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False) | |||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
InvalidTxRequestTest().main() | InvalidTxRequestTest().main() |