Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_invalid_tx.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2017 The Bitcoin Core developers | # Copyright (c) 2015-2017 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Test node responses to invalid transactions. | """Test node responses to invalid transactions. | ||||
In this test we connect to one node over p2p, and test tx requests. | In this test we connect to one node over p2p, and test tx requests. | ||||
""" | """ | ||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
create_transaction, | create_transaction, | ||||
) | ) | ||||
from test_framework.messages import COIN | from test_framework.txtools import pad_tx | ||||
from test_framework.mininode import network_thread_start, P2PDataStore | from test_framework.messages import ( | ||||
COIN, | |||||
COutPoint, | |||||
CTransaction, | |||||
CTxIn, | |||||
CTxOut, | |||||
) | |||||
from test_framework.mininode import network_thread_start, P2PDataStore, network_thread_join | |||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | |||||
assert_equal, | |||||
wait_until, | |||||
) | |||||
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.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.extra_args = [["-whitelist=127.0.0.1"]] | |||||
def run_test(self): | def bootstrap_p2p(self, *, num_connections=1): | ||||
# Add p2p connection to node0 | """Add a P2P connection to the node. | ||||
# convenience reference to the node | |||||
node = self.nodes[0] | |||||
node.add_p2p_connection(P2PDataStore()) | |||||
Helper to connect and wait for version handshake.""" | |||||
for _ in range(num_connections): | |||||
self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
network_thread_start() | network_thread_start() | ||||
node.p2p.wait_for_verack() | self.nodes[0].p2p.wait_for_verack() | ||||
def reconnect_p2p(self, **kwargs): | |||||
"""Tear down and bootstrap the P2P connection to the node. | |||||
The node gets disconnected several times in this test. This helper | |||||
method reconnects the p2p and restarts the network thread.""" | |||||
self.nodes[0].disconnect_p2ps() | |||||
network_thread_join() | |||||
self.bootstrap_p2p(**kwargs) | |||||
def run_test(self): | |||||
node = self.nodes[0] # convenience reference 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_time += 1 | block_time += 1 | ||||
block.solve() | block.solve() | ||||
# Save the coinbase for later | # Save the coinbase for later | ||||
block1 = block | block1 = block | ||||
tip = block.sha256 | tip = block.sha256 | ||||
height += 1 | height += 1 | ||||
node.p2p.send_blocks_and_test([block], node, success=True) | node.p2p.send_blocks_and_test([block], node, success=True) | ||||
self.log.info("Mature the block.") | self.log.info("Mature the block.") | ||||
self.nodes[0].generate(100) | self.nodes[0].generate(100) | ||||
# b'\x64' is OP_NOTIF | # b'\x64' is OP_NOTIF | ||||
# Transaction will be rejected with code 16 (REJECT_INVALID) | # Transaction will be rejected with code 16 (REJECT_INVALID) | ||||
# and we get disconnected immediately | |||||
self.log.info('Test a transaction that is rejected') | |||||
tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN - 12000) | tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN - 12000) | ||||
node.p2p.send_txs_and_test([tx1], node, success=False, reject_code=16, | node.p2p.send_txs_and_test( | ||||
reject_reason=b'mandatory-script-verify-flag-failed (Only push operators allowed in signature scripts)') | [tx1], node, success=False, expect_disconnect=True) | ||||
# TODO: test further transactions... | # Make two p2p connections to provide the node with orphans | ||||
# * 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) | |||||
self.reconnect_p2p(num_connections=2) | |||||
self.log.info('Test orphan transaction handling ... ') | |||||
# Create a root transaction that we withold until all dependend transactions | |||||
# are sent out and in the orphan cache | |||||
tx_withhold = CTransaction() | |||||
tx_withhold.vin.append( | |||||
CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) | |||||
tx_withhold.vout.append( | |||||
CTxOut(nValue=50 * COIN - 12000, scriptPubKey=b'\x51')) | |||||
pad_tx(tx_withhold) | |||||
tx_withhold.calc_sha256() | |||||
# Our first orphan tx with some outputs to create further orphan txs | |||||
tx_orphan_1 = CTransaction() | |||||
tx_orphan_1.vin.append( | |||||
CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) | |||||
tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=b'\x51')] * 3 | |||||
pad_tx(tx_orphan_1) | |||||
tx_orphan_1.calc_sha256() | |||||
# A valid transaction with low fee | |||||
tx_orphan_2_no_fee = CTransaction() | |||||
tx_orphan_2_no_fee.vin.append( | |||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) | |||||
tx_orphan_2_no_fee.vout.append( | |||||
CTxOut(nValue=10 * COIN, scriptPubKey=b'\x51')) | |||||
pad_tx(tx_orphan_2_no_fee) | |||||
# A valid transaction with sufficient fee | |||||
tx_orphan_2_valid = CTransaction() | |||||
tx_orphan_2_valid.vin.append( | |||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) | |||||
tx_orphan_2_valid.vout.append( | |||||
CTxOut(nValue=10 * COIN - 12000, scriptPubKey=b'\x51')) | |||||
tx_orphan_2_valid.calc_sha256() | |||||
pad_tx(tx_orphan_2_valid) | |||||
# An invalid transaction with negative fee | |||||
tx_orphan_2_invalid = CTransaction() | |||||
tx_orphan_2_invalid.vin.append( | |||||
CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) | |||||
tx_orphan_2_invalid.vout.append( | |||||
CTxOut(nValue=11 * COIN, scriptPubKey=b'\x51')) | |||||
pad_tx(tx_orphan_2_invalid) | |||||
self.log.info('Send the orphans ... ') | |||||
# Send valid orphan txs from p2ps[0] | |||||
node.p2p.send_txs_and_test( | |||||
[tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) | |||||
# Send invalid tx from p2ps[1] | |||||
node.p2ps[1].send_txs_and_test( | |||||
[tx_orphan_2_invalid], node, success=False) | |||||
# Mempool should be empty | |||||
assert_equal(0, node.getmempoolinfo()['size']) | |||||
assert_equal(2, len(node.getpeerinfo())) # p2ps[1] is still connected | |||||
self.log.info('Send the withhold tx ... ') | |||||
node.p2p.send_txs_and_test([tx_withhold], node, success=True) | |||||
# Transactions that should end up in the mempool | |||||
expected_mempool = { | |||||
t.hash | |||||
for t in [ | |||||
tx_withhold, # The transaction that is the root for all orphans | |||||
tx_orphan_1, # The orphan transaction that splits the coins | |||||
# The valid transaction (with sufficient fee) | |||||
tx_orphan_2_valid, | |||||
] | |||||
} | |||||
# 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_invaid, because it has negative fee (p2ps[1] is disconnected for relaying that tx) | |||||
# p2ps[1] is no longer connected | |||||
wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) | |||||
assert_equal(expected_mempool, set(node.getrawmempool())) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
InvalidTxRequestTest().main() | InvalidTxRequestTest().main() |