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]) | |||||
deadalnix: Why do something so complex ? It only matter that you you prefix 4 out of 6. | |||||
jasonbcoxAuthorUnsubmitted Done Inline ActionsThe indices need to be determined to verify that the tx other than the one put in the mempool (see code block above) is the only remaining tx. jasonbcox: The indices need to be determined to verify that the tx other than the one put in the mempool… | |||||
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 |
Why do something so complex ? It only matter that you you prefix 4 out of 6.