2016-05-06 11:23:48 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2015-2016 The Bitcoin Core developers
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
2015-12-07 14:47:58 +01:00
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
2019-01-07 10:55:35 +01:00
|
|
|
"""Test node responses to invalid transactions.
|
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
In this test we connect to one node over p2p, and test tx requests."""
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
from test_framework.blocktools import create_block, create_coinbase, create_transaction
|
2018-04-26 19:32:17 +02:00
|
|
|
from test_framework.mininode import (
|
|
|
|
COIN,
|
|
|
|
COutPoint,
|
|
|
|
CTransaction,
|
|
|
|
CTxIn,
|
|
|
|
CTxOut,
|
|
|
|
)
|
|
|
|
from test_framework.mininode import network_thread_start, P2PDataStore, network_thread_join
|
2018-02-13 10:30:41 +01:00
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
2018-04-26 19:32:17 +02:00
|
|
|
from test_framework.util import (
|
|
|
|
assert_equal,
|
|
|
|
wait_until,
|
|
|
|
)
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2020-04-19 13:04:31 +02:00
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
class InvalidTxRequestTest(BitcoinTestFramework):
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2017-09-01 18:47:13 +02:00
|
|
|
def set_test_params(self):
|
2015-12-07 14:47:58 +01:00
|
|
|
self.num_nodes = 1
|
2017-09-01 18:47:13 +02:00
|
|
|
self.setup_clean_chain = True
|
2018-04-26 19:32:17 +02:00
|
|
|
|
|
|
|
def bootstrap_p2p(self, *, num_connections=1):
|
|
|
|
"""Add a P2P connection to the node.
|
|
|
|
|
|
|
|
Helper to connect and wait for version handshake."""
|
|
|
|
for _ in range(num_connections):
|
|
|
|
self.nodes[0].add_p2p_connection(P2PDataStore())
|
|
|
|
network_thread_start()
|
|
|
|
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)
|
2015-12-07 14:47:58 +01:00
|
|
|
|
|
|
|
def run_test(self):
|
2018-02-13 10:30:41 +01:00
|
|
|
node = self.nodes[0] # convenience reference to the node
|
|
|
|
|
2018-04-26 19:32:17 +02:00
|
|
|
self.bootstrap_p2p() # Add one p2p connection to the node
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
best_block = self.nodes[0].getbestblockhash()
|
|
|
|
tip = int(best_block, 16)
|
|
|
|
best_block_time = self.nodes[0].getblock(best_block)['time']
|
|
|
|
block_time = best_block_time + 1
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
self.log.info("Create a new block with an anyone-can-spend coinbase.")
|
2015-12-07 14:47:58 +01:00
|
|
|
height = 1
|
2018-02-13 10:30:41 +01:00
|
|
|
block = create_block(tip, create_coinbase(height), block_time)
|
2015-12-07 14:47:58 +01:00
|
|
|
block.solve()
|
|
|
|
# Save the coinbase for later
|
2018-02-13 10:30:41 +01:00
|
|
|
block1 = block
|
|
|
|
tip = block.sha256
|
2019-10-04 13:36:18 +02:00
|
|
|
|
|
|
|
# Create a second one to test orphan resolution via block receival
|
|
|
|
height += 1
|
|
|
|
block_time += 1
|
|
|
|
block = create_block(tip, create_coinbase(height), block_time)
|
|
|
|
block.solve()
|
|
|
|
# Save the coinbase for later
|
|
|
|
block2 = block
|
|
|
|
tip = block.sha256
|
|
|
|
node.p2p.send_blocks_and_test([block1, block2], node, success=True)
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2018-02-13 10:30:41 +01:00
|
|
|
self.log.info("Mature the block.")
|
|
|
|
self.nodes[0].generate(100)
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2016-04-10 16:54:28 +02:00
|
|
|
# b'\x64' is OP_NOTIF
|
2015-12-07 14:47:58 +01:00
|
|
|
# Transaction will be rejected with code 16 (REJECT_INVALID)
|
2018-04-26 19:32:17 +02:00
|
|
|
# and we get disconnected immediately
|
|
|
|
self.log.info('Test a transaction that is rejected')
|
2018-02-13 10:30:41 +01:00
|
|
|
tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN)
|
2018-06-01 14:02:16 +02:00
|
|
|
node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True)
|
2018-04-26 19:32:17 +02:00
|
|
|
|
|
|
|
# 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 ... ')
|
2019-10-04 13:36:18 +02:00
|
|
|
self.test_orphan_tx_handling(block1.vtx[0].sha256, False)
|
2020-04-19 13:04:31 +02:00
|
|
|
|
|
|
|
# restart node with sending BIP61 messages disabled, check that it disconnects without sending the reject message
|
|
|
|
self.log.info('Test a transaction that is rejected, with BIP61 disabled')
|
2020-06-13 06:50:03 +02:00
|
|
|
self.restart_node(0, ['-enablebip61=0', '-persistmempool=0'])
|
2020-04-19 13:04:31 +02:00
|
|
|
self.reconnect_p2p(num_connections=1)
|
2020-06-13 06:50:03 +02:00
|
|
|
with node.assert_debug_log(expected_msgs=[
|
|
|
|
"{} from peer=0 was not accepted: mandatory-script-verify-flag-failed (Invalid OP_IF construction) (code 16)".format(tx1.hash),
|
|
|
|
"disconnecting peer=0",
|
|
|
|
]):
|
|
|
|
node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True)
|
2020-04-19 13:04:31 +02:00
|
|
|
# send_txs_and_test will have waited for disconnect, so we can safely check that no reject has been received
|
|
|
|
assert_equal(node.p2p.reject_code_received, None)
|
|
|
|
|
|
|
|
self.log.info('Test orphan transaction handling, resolve via block')
|
|
|
|
self.restart_node(0, ['-persistmempool=0'])
|
2019-10-04 13:36:18 +02:00
|
|
|
self.reconnect_p2p(num_connections=2)
|
|
|
|
self.test_orphan_tx_handling(block2.vtx[0].sha256, True)
|
|
|
|
|
|
|
|
def test_orphan_tx_handling(self, base_tx, resolve_via_block):
|
|
|
|
node = self.nodes[0] # convenience reference to the node
|
|
|
|
|
2018-04-26 19:32:17 +02:00
|
|
|
# Create a root transaction that we withold until all dependend transactions
|
|
|
|
# are sent out and in the orphan cache
|
|
|
|
tx_withhold = CTransaction()
|
2019-10-04 13:36:18 +02:00
|
|
|
tx_withhold.vin.append(CTxIn(outpoint=COutPoint(base_tx, 0)))
|
2018-04-26 19:32:17 +02:00
|
|
|
tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=b'\x51'))
|
|
|
|
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
|
|
|
|
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'))
|
|
|
|
|
|
|
|
# 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()
|
|
|
|
|
|
|
|
# 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'))
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
assert_equal(0, node.getmempoolinfo()['size']) # Mempool should be empty
|
|
|
|
assert_equal(2, len(node.getpeerinfo())) # p2ps[1] is still connected
|
|
|
|
|
|
|
|
self.log.info('Send the withhold tx ... ')
|
2019-10-04 13:36:18 +02:00
|
|
|
if resolve_via_block:
|
|
|
|
# Test orphan handling/resolution by publishing the withhold TX via a mined block
|
|
|
|
prev_block = node.getblockheader(node.getbestblockhash())
|
|
|
|
block = create_block(int(prev_block['hash'], 16), create_coinbase(prev_block['height'] + 1), prev_block["time"] + 1)
|
|
|
|
block.vtx.append(tx_withhold)
|
|
|
|
block.hashMerkleRoot = block.calc_merkle_root()
|
|
|
|
block.solve()
|
|
|
|
node.p2p.send_blocks_and_test([block], node, success=True)
|
|
|
|
else:
|
|
|
|
# Test orphan handling/resolution by publishing the withhold TX via the mempool
|
|
|
|
node.p2p.send_txs_and_test([tx_withhold], node, success=True)
|
2018-04-26 19:32:17 +02:00
|
|
|
|
|
|
|
# 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
|
|
|
|
tx_orphan_2_valid, # The valid transaction (with sufficient fee)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
# 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)
|
2019-10-04 13:36:18 +02:00
|
|
|
if resolve_via_block:
|
|
|
|
# This TX has appeared in a block instead of being broadcasted via the mempool
|
|
|
|
expected_mempool.remove(tx_withhold.hash)
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2018-04-26 19:32:17 +02:00
|
|
|
wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) # p2ps[1] is no longer connected
|
|
|
|
assert_equal(expected_mempool, set(node.getrawmempool()))
|
2018-03-13 22:09:43 +01:00
|
|
|
|
2015-12-07 14:47:58 +01:00
|
|
|
|
2020-06-13 06:50:03 +02:00
|
|
|
|
2015-12-07 14:47:58 +01:00
|
|
|
if __name__ == '__main__':
|
|
|
|
InvalidTxRequestTest().main()
|