Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p-compactblocks.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2016 The Bitcoin Core developers | # Copyright (c) 2016 The Bitcoin Core developers | ||||
# Copyright (c) 2017 The Bitcoin developers | # Copyright (c) 2017 The Bitcoin 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. | ||||
from test_framework.mininode import * | from test_framework.mininode import * | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import * | from test_framework.util import * | ||||
from test_framework.blocktools import create_block, create_coinbase | from test_framework.blocktools import create_block, create_coinbase | ||||
from test_framework.script import CScript, OP_TRUE | from test_framework.script import CScript, OP_TRUE | ||||
from test_framework.txtools import pad_tx | |||||
''' | ''' | ||||
CompactBlocksTest -- test compact blocks (BIP 152) | CompactBlocksTest -- test compact blocks (BIP 152) | ||||
Only testing Version 1 compact blocks (txids) | Only testing Version 1 compact blocks (txids) | ||||
''' | ''' | ||||
# TestNode: A peer we use to send messages to bitcoind, and store responses. | # TestNode: A peer we use to send messages to bitcoind, and store responses. | ||||
▲ Show 20 Lines • Show All 389 Lines • ▼ Show 20 Lines | def test_compactblock_requests(self, node, test_node, version): | ||||
# Send the coinbase, and verify that the tip advances. | # Send the coinbase, and verify that the tip advances. | ||||
msg = msg_blocktxn() | msg = msg_blocktxn() | ||||
msg.block_transactions.blockhash = block.sha256 | msg.block_transactions.blockhash = block.sha256 | ||||
msg.block_transactions.transactions = [block.vtx[0]] | msg.block_transactions.transactions = [block.vtx[0]] | ||||
test_node.send_and_ping(msg) | test_node.send_and_ping(msg) | ||||
assert_equal(int(node.getbestblockhash(), 16), block.sha256) | assert_equal(int(node.getbestblockhash(), 16), block.sha256) | ||||
# Create a chain of transactions from given utxo, and add to a new block. | # Create a chain of transactions from given utxo, and add to a new block. | ||||
# Note that num_transactions is number of transactions not including the | |||||
# coinbase. | |||||
def build_block_with_transactions(self, node, utxo, num_transactions): | def build_block_with_transactions(self, node, utxo, num_transactions): | ||||
block = self.build_block_on_tip(node) | block = self.build_block_on_tip(node) | ||||
for i in range(num_transactions): | for i in range(num_transactions): | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) | tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) | ||||
tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) | tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) | ||||
pad_tx(tx) | |||||
tx.rehash() | tx.rehash() | ||||
utxo = [tx.sha256, 0, tx.vout[0].nValue] | utxo = [tx.sha256, 0, tx.vout[0].nValue] | ||||
block.vtx.append(tx) | block.vtx.append(tx) | ||||
ordered_txs = block.vtx | |||||
block.vtx = [block.vtx[0]] + \ | |||||
sorted(block.vtx[1:], key=lambda tx: tx.get_id()) | |||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
return block | return block, ordered_txs | ||||
# Test that we only receive getblocktxn requests for transactions that the | # Test that we only receive getblocktxn requests for transactions that the | ||||
# node needs, and that responding to them causes the block to be | # node needs, and that responding to them causes the block to be | ||||
# reconstructed. | # reconstructed. | ||||
def test_getblocktxn_requests(self, node, test_node, version): | def test_getblocktxn_requests(self, node, test_node, version): | ||||
def test_getblocktxn_response(compact_block, peer, expected_result): | def test_getblocktxn_response(compact_block, peer, expected_result): | ||||
msg = msg_cmpctblock(compact_block.to_p2p()) | msg = msg_cmpctblock(compact_block.to_p2p()) | ||||
peer.send_and_ping(msg) | peer.send_and_ping(msg) | ||||
with mininode_lock: | with mininode_lock: | ||||
assert("getblocktxn" in peer.last_message) | assert("getblocktxn" in peer.last_message) | ||||
absolute_indexes = peer.last_message["getblocktxn"].block_txn_request.to_absolute( | absolute_indexes = peer.last_message["getblocktxn"].block_txn_request.to_absolute( | ||||
) | ) | ||||
assert_equal(absolute_indexes, expected_result) | assert_equal(absolute_indexes, expected_result) | ||||
def test_tip_after_message(node, peer, msg, tip): | def test_tip_after_message(node, peer, msg, tip): | ||||
peer.send_and_ping(msg) | peer.send_and_ping(msg) | ||||
assert_equal(int(node.getbestblockhash(), 16), tip) | assert_equal(int(node.getbestblockhash(), 16), tip) | ||||
# First try announcing compactblocks that won't reconstruct, and verify | # First try announcing compactblocks that won't reconstruct, and verify | ||||
# that we receive getblocktxn messages back. | # that we receive getblocktxn messages back. | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 5) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | ||||
self.utxos.append( | self.utxos.append( | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
comp_block = HeaderAndShortIDs() | comp_block = HeaderAndShortIDs() | ||||
comp_block.initialize_from_block(block) | comp_block.initialize_from_block(block) | ||||
test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) | test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) | ||||
msg_bt = msg_blocktxn() | msg_bt = msg_blocktxn() | ||||
msg_bt.block_transactions = BlockTransactions( | msg_bt.block_transactions = BlockTransactions( | ||||
block.sha256, block.vtx[1:]) | block.sha256, block.vtx[1:]) | ||||
test_tip_after_message(node, test_node, msg_bt, block.sha256) | test_tip_after_message(node, test_node, msg_bt, block.sha256) | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 5) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | ||||
self.utxos.append( | self.utxos.append( | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
# Now try interspersing the prefilled transactions | # Now try interspersing the prefilled transactions | ||||
comp_block.initialize_from_block( | comp_block.initialize_from_block( | ||||
block, prefill_list=[0, 1, 5]) | block, prefill_list=[0, 1, 5]) | ||||
test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) | test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) | ||||
msg_bt.block_transactions = BlockTransactions( | msg_bt.block_transactions = BlockTransactions( | ||||
block.sha256, block.vtx[2:5]) | block.sha256, block.vtx[2:5]) | ||||
test_tip_after_message(node, test_node, msg_bt, block.sha256) | test_tip_after_message(node, test_node, msg_bt, block.sha256) | ||||
# Now try giving one transaction ahead of time. | # Now try giving one transaction ahead of time. | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 5) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | ||||
self.utxos.append( | self.utxos.append( | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
test_node.send_and_ping(msg_tx(block.vtx[1])) | test_node.send_and_ping(msg_tx(ordered_txs[1])) | ||||
assert(block.vtx[1].hash in node.getrawmempool()) | assert(ordered_txs[1].hash in node.getrawmempool()) | ||||
test_node.send_and_ping(msg_tx(ordered_txs[1])) | |||||
# Prefill 4 out of the 6 transactions, and verify that only the one | # Prefill 4 out of the 6 transactions, and verify that only the one | ||||
# that was not in the mempool is requested. | # that was not in the mempool is requested. | ||||
comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4]) | prefill_list = [0, 1, 2, 3, 4, 5] | ||||
test_getblocktxn_response(comp_block, test_node, [5]) | prefill_list.remove(block.vtx.index(ordered_txs[1])) | ||||
expected_index = block.vtx.index(ordered_txs[-1]) | |||||
prefill_list.remove(expected_index) | |||||
comp_block.initialize_from_block(block, prefill_list=prefill_list) | |||||
test_getblocktxn_response(comp_block, test_node, [expected_index]) | |||||
msg_bt.block_transactions = BlockTransactions( | msg_bt.block_transactions = BlockTransactions( | ||||
block.sha256, [block.vtx[5]]) | block.sha256, [ordered_txs[5]]) | ||||
test_tip_after_message(node, test_node, msg_bt, block.sha256) | test_tip_after_message(node, test_node, msg_bt, block.sha256) | ||||
# Now provide all transactions to the node before the block is | # Now provide all transactions to the node before the block is | ||||
# announced and verify reconstruction happens immediately. | # announced and verify reconstruction happens immediately. | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 10) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 10) | ||||
self.utxos.append( | self.utxos.append( | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
for tx in block.vtx[1:]: | for tx in block.vtx[1:]: | ||||
test_node.send_message(msg_tx(tx)) | test_node.send_message(msg_tx(tx)) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
# Make sure all transactions were accepted. | # Make sure all transactions were accepted. | ||||
mempool = node.getrawmempool() | mempool = node.getrawmempool() | ||||
for tx in block.vtx[1:]: | for tx in block.vtx[1:]: | ||||
assert(tx.hash in mempool) | assert(tx.hash in mempool) | ||||
Show All 11 Lines | class CompactBlocksTest(BitcoinTestFramework): | ||||
# Incorrectly responding to a getblocktxn shouldn't cause the block to be | # Incorrectly responding to a getblocktxn shouldn't cause the block to be | ||||
# permanently failed. | # permanently failed. | ||||
def test_incorrect_blocktxn_response(self, node, test_node, version): | def test_incorrect_blocktxn_response(self, node, test_node, version): | ||||
if (len(self.utxos) == 0): | if (len(self.utxos) == 0): | ||||
self.make_utxos() | self.make_utxos() | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 10) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 10) | ||||
self.utxos.append( | self.utxos.append( | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
# Relay the first 5 transactions from the block in advance | # Relay the first 5 transactions from the block in advance | ||||
for tx in block.vtx[1:6]: | for tx in ordered_txs[1:6]: | ||||
test_node.send_message(msg_tx(tx)) | test_node.send_message(msg_tx(tx)) | ||||
test_node.sync_with_ping() | test_node.sync_with_ping() | ||||
# Make sure all transactions were accepted. | # Make sure all transactions were accepted. | ||||
mempool = node.getrawmempool() | mempool = node.getrawmempool() | ||||
for tx in block.vtx[1:6]: | for tx in ordered_txs[1:6]: | ||||
assert(tx.hash in mempool) | assert(tx.hash in mempool) | ||||
# Send compact block | # Send compact block | ||||
comp_block = HeaderAndShortIDs() | comp_block = HeaderAndShortIDs() | ||||
comp_block.initialize_from_block(block, prefill_list=[0]) | comp_block.initialize_from_block(block, prefill_list=[0]) | ||||
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | ||||
absolute_indexes = [] | absolute_indices = [] | ||||
with mininode_lock: | with mininode_lock: | ||||
assert("getblocktxn" in test_node.last_message) | assert("getblocktxn" in test_node.last_message) | ||||
absolute_indexes = test_node.last_message["getblocktxn"].block_txn_request.to_absolute( | absolute_indices = test_node.last_message["getblocktxn"].block_txn_request.to_absolute( | ||||
) | ) | ||||
assert_equal(absolute_indexes, [6, 7, 8, 9, 10]) | expected_indices = [] | ||||
for i in [6, 7, 8, 9, 10]: | |||||
expected_indices.append(block.vtx.index(ordered_txs[i])) | |||||
assert_equal(absolute_indices, sorted(expected_indices)) | |||||
# Now give an incorrect response. | # Now give an incorrect response. | ||||
# Note that it's possible for bitcoind to be smart enough to know we're | # Note that it's possible for bitcoind to be smart enough to know we're | ||||
# lying, since it could check to see if the shortid matches what we're | # lying, since it could check to see if the shortid matches what we're | ||||
# sending, and eg disconnect us for misbehavior. If that behavior | # sending, and eg disconnect us for misbehavior. If that behavior | ||||
# change were made, we could just modify this test by having a | # change were made, we could just modify this test by having a | ||||
# different peer provide the block further down, so that we're still | # different peer provide the block further down, so that we're still | ||||
# verifying that the block isn't marked bad permanently. This is good | # verifying that the block isn't marked bad permanently. This is good | ||||
# enough for now. | # enough for now. | ||||
msg = msg_blocktxn() | msg = msg_blocktxn() | ||||
msg.block_transactions = BlockTransactions( | msg.block_transactions = BlockTransactions( | ||||
block.sha256, [block.vtx[5]] + block.vtx[7:]) | block.sha256, [ordered_txs[5]] + ordered_txs[7:]) | ||||
test_node.send_and_ping(msg) | test_node.send_and_ping(msg) | ||||
# Tip should not have updated | # Tip should not have updated | ||||
assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) | assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) | ||||
# We should receive a getdata request | # We should receive a getdata request | ||||
wait_until(lambda: "getdata" in test_node.last_message, | wait_until(lambda: "getdata" in test_node.last_message, | ||||
timeout=10, lock=mininode_lock) | timeout=10, lock=mininode_lock) | ||||
▲ Show 20 Lines • Show All 112 Lines • ▼ Show 20 Lines | def test_compactblocks_not_at_tip(self, node, test_node): | ||||
test_node.last_message.pop("blocktxn", None) | test_node.last_message.pop("blocktxn", None) | ||||
test_node.send_and_ping(msg) | test_node.send_and_ping(msg) | ||||
with mininode_lock: | with mininode_lock: | ||||
assert "blocktxn" not in test_node.last_message | assert "blocktxn" not in test_node.last_message | ||||
def test_end_to_end_block_relay(self, node, listeners): | def test_end_to_end_block_relay(self, node, listeners): | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 10) | block, _ = self.build_block_with_transactions(node, utxo, 10) | ||||
[l.clear_block_announcement() for l in listeners] | [l.clear_block_announcement() for l in listeners] | ||||
node.submitblock(ToHex(block)) | node.submitblock(ToHex(block)) | ||||
for l in listeners: | for l in listeners: | ||||
wait_until(lambda: l.received_block_announcement(), | wait_until(lambda: l.received_block_announcement(), | ||||
timeout=30, lock=mininode_lock) | timeout=30, lock=mininode_lock) | ||||
with mininode_lock: | with mininode_lock: | ||||
for l in listeners: | for l in listeners: | ||||
assert "cmpctblock" in l.last_message | assert "cmpctblock" in l.last_message | ||||
l.last_message["cmpctblock"].header_and_shortids.header.calc_sha256( | l.last_message["cmpctblock"].header_and_shortids.header.calc_sha256( | ||||
) | ) | ||||
assert_equal( | assert_equal( | ||||
l.last_message["cmpctblock"].header_and_shortids.header.sha256, block.sha256) | l.last_message["cmpctblock"].header_and_shortids.header.sha256, block.sha256) | ||||
# Test that we don't get disconnected if we relay a compact block with valid header, | # Test that we don't get disconnected if we relay a compact block with valid header, | ||||
# but invalid transactions. | # but invalid transactions. | ||||
def test_invalid_tx_in_compactblock(self, node, test_node): | def test_invalid_tx_in_compactblock(self, node, test_node): | ||||
assert(len(self.utxos)) | assert(len(self.utxos)) | ||||
utxo = self.utxos[0] | utxo = self.utxos[0] | ||||
block = self.build_block_with_transactions(node, utxo, 5) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | ||||
del block.vtx[3] | block.vtx.remove(ordered_txs[3]) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
# Now send the compact block with all transactions prefilled, and | # Now send the compact block with all transactions prefilled, and | ||||
# verify that we don't get disconnected. | # verify that we don't get disconnected. | ||||
comp_block = HeaderAndShortIDs() | comp_block = HeaderAndShortIDs() | ||||
comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4]) | comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4]) | ||||
msg = msg_cmpctblock(comp_block.to_p2p()) | msg = msg_cmpctblock(comp_block.to_p2p()) | ||||
Show All 14 Lines | def request_cb_announcements(self, peer, node, version=1): | ||||
msg.announce = True | msg.announce = True | ||||
peer.send_and_ping(msg) | peer.send_and_ping(msg) | ||||
def test_compactblock_reconstruction_multiple_peers(self, node, stalling_peer, delivery_peer): | def test_compactblock_reconstruction_multiple_peers(self, node, stalling_peer, delivery_peer): | ||||
assert(len(self.utxos)) | assert(len(self.utxos)) | ||||
def announce_cmpct_block(node, peer): | def announce_cmpct_block(node, peer): | ||||
utxo = self.utxos.pop(0) | utxo = self.utxos.pop(0) | ||||
block = self.build_block_with_transactions(node, utxo, 5) | block, _ = self.build_block_with_transactions(node, utxo, 5) | ||||
cmpct_block = HeaderAndShortIDs() | cmpct_block = HeaderAndShortIDs() | ||||
cmpct_block.initialize_from_block(block) | cmpct_block.initialize_from_block(block) | ||||
msg = msg_cmpctblock(cmpct_block.to_p2p()) | msg = msg_cmpctblock(cmpct_block.to_p2p()) | ||||
peer.send_and_ping(msg) | peer.send_and_ping(msg) | ||||
with mininode_lock: | with mininode_lock: | ||||
assert "getblocktxn" in peer.last_message | assert "getblocktxn" in peer.last_message | ||||
return block, cmpct_block | return block, cmpct_block | ||||
▲ Show 20 Lines • Show All 139 Lines • Show Last 20 Lines |