Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_compactblocks.py
Show First 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | class TestP2PConn(P2PInterface): | ||||
def on_sendcmpct(self, message): | def on_sendcmpct(self, message): | ||||
self.last_sendcmpct.append(message) | self.last_sendcmpct.append(message) | ||||
def on_cmpctblock(self, message): | def on_cmpctblock(self, message): | ||||
self.block_announced = True | self.block_announced = True | ||||
self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256() | self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256() | ||||
self.announced_blockhashes.add( | self.announced_blockhashes.add( | ||||
self.last_message["cmpctblock"].header_and_shortids.header.sha256) | self.last_message["cmpctblock"].header_and_shortids.header.sha256 | ||||
) | |||||
def on_headers(self, message): | def on_headers(self, message): | ||||
self.block_announced = True | self.block_announced = True | ||||
for x in self.last_message["headers"].headers: | for x in self.last_message["headers"].headers: | ||||
x.calc_sha256() | x.calc_sha256() | ||||
self.announced_blockhashes.add(x.sha256) | self.announced_blockhashes.add(x.sha256) | ||||
def on_inv(self, message): | def on_inv(self, message): | ||||
Show All 29 Lines | def request_headers_and_sync(self, locator, hashstop=0): | ||||
self.get_headers(locator, hashstop) | self.get_headers(locator, hashstop) | ||||
self.wait_until(self.received_block_announcement, timeout=30) | self.wait_until(self.received_block_announcement, timeout=30) | ||||
self.clear_block_announcement() | self.clear_block_announcement() | ||||
# Block until a block announcement for a particular block hash is | # Block until a block announcement for a particular block hash is | ||||
# received. | # received. | ||||
def wait_for_block_announcement(self, block_hash, timeout=30): | def wait_for_block_announcement(self, block_hash, timeout=30): | ||||
def received_hash(): | def received_hash(): | ||||
return (block_hash in self.announced_blockhashes) | return block_hash in self.announced_blockhashes | ||||
self.wait_until(received_hash, timeout=timeout) | self.wait_until(received_hash, timeout=timeout) | ||||
def send_await_disconnect(self, message, timeout=30): | def send_await_disconnect(self, message, timeout=30): | ||||
"""Sends a message to the node and wait for disconnect. | """Sends a message to the node and wait for disconnect. | ||||
This is used when we want to send a message into the node that we expect | This is used when we want to send a message into the node that we expect | ||||
will get us disconnected, eg an invalid block.""" | will get us disconnected, eg an invalid block.""" | ||||
self.send_message(message) | self.send_message(message) | ||||
self.wait_for_disconnect(timeout) | self.wait_for_disconnect(timeout) | ||||
class CompactBlocksTest(BitcoinTestFramework): | class CompactBlocksTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [["-acceptnonstdtxn=1"], | self.extra_args = [["-acceptnonstdtxn=1"], ["-txindex", "-acceptnonstdtxn=1"]] | ||||
["-txindex", "-acceptnonstdtxn=1"]] | |||||
self.utxos = [] | self.utxos = [] | ||||
def skip_test_if_missing_module(self): | def skip_test_if_missing_module(self): | ||||
self.skip_if_no_wallet() | self.skip_if_no_wallet() | ||||
def build_block_on_tip(self, node): | def build_block_on_tip(self, node): | ||||
block = create_block(tmpl=node.getblocktemplate()) | block = create_block(tmpl=node.getblocktemplate()) | ||||
block.solve() | block.solve() | ||||
return block | return block | ||||
# Create 10 more anyone-can-spend utxo's for testing. | # Create 10 more anyone-can-spend utxo's for testing. | ||||
def make_utxos(self): | def make_utxos(self): | ||||
# Doesn't matter which node we use, just use node0. | # Doesn't matter which node we use, just use node0. | ||||
block = self.build_block_on_tip(self.nodes[0]) | block = self.build_block_on_tip(self.nodes[0]) | ||||
self.test_node.send_and_ping(msg_block(block)) | self.test_node.send_and_ping(msg_block(block)) | ||||
assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 | assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 | ||||
self.generate(self.nodes[0], 100) | self.generate(self.nodes[0], 100) | ||||
total_value = block.vtx[0].vout[0].nValue | total_value = block.vtx[0].vout[0].nValue | ||||
out_value = total_value // 10 | out_value = total_value // 10 | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b'')) | tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b"")) | ||||
for _ in range(10): | for _ in range(10): | ||||
tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) | tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) | ||||
tx.rehash() | tx.rehash() | ||||
block2 = self.build_block_on_tip(self.nodes[0]) | block2 = self.build_block_on_tip(self.nodes[0]) | ||||
block2.vtx.append(tx) | block2.vtx.append(tx) | ||||
block2.hashMerkleRoot = block2.calc_merkle_root() | block2.hashMerkleRoot = block2.calc_merkle_root() | ||||
block2.solve() | block2.solve() | ||||
self.test_node.send_and_ping(msg_block(block2)) | self.test_node.send_and_ping(msg_block(block2)) | ||||
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) | assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) | ||||
self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) | self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) | ||||
return | return | ||||
# Test "sendcmpct" (between peers preferring the same version): | # Test "sendcmpct" (between peers preferring the same version): | ||||
# - No compact block announcements unless sendcmpct is sent. | # - No compact block announcements unless sendcmpct is sent. | ||||
# - If sendcmpct is sent with version > preferred_version, the message is ignored. | # - If sendcmpct is sent with version > preferred_version, the message is ignored. | ||||
# - If sendcmpct is sent with boolean 0, then block announcements are not | # - If sendcmpct is sent with boolean 0, then block announcements are not | ||||
# made with compact blocks. | # made with compact blocks. | ||||
# - If sendcmpct is then sent with boolean 1, then new block announcements | # - If sendcmpct is then sent with boolean 1, then new block announcements | ||||
# are made with compact blocks. | # are made with compact blocks. | ||||
# If old_node is passed in, request compact blocks with version=preferred-1 | # If old_node is passed in, request compact blocks with version=preferred-1 | ||||
# and verify that it receives block announcements via compact block. | # and verify that it receives block announcements via compact block. | ||||
def test_sendcmpct(self, node, test_node, | def test_sendcmpct(self, node, test_node, preferred_version, old_node=None): | ||||
preferred_version, old_node=None): | |||||
# Make sure we get a SENDCMPCT message from our peer | # Make sure we get a SENDCMPCT message from our peer | ||||
def received_sendcmpct(): | def received_sendcmpct(): | ||||
return (len(test_node.last_sendcmpct) > 0) | return len(test_node.last_sendcmpct) > 0 | ||||
test_node.wait_until(received_sendcmpct, timeout=30) | test_node.wait_until(received_sendcmpct, timeout=30) | ||||
with p2p_lock: | with p2p_lock: | ||||
# Check that the first version received is the preferred one | # Check that the first version received is the preferred one | ||||
assert_equal( | assert_equal(test_node.last_sendcmpct[0].version, preferred_version) | ||||
test_node.last_sendcmpct[0].version, preferred_version) | |||||
# And that we receive versions down to 1. | # And that we receive versions down to 1. | ||||
assert_equal(test_node.last_sendcmpct[-1].version, 1) | assert_equal(test_node.last_sendcmpct[-1].version, 1) | ||||
test_node.last_sendcmpct = [] | test_node.last_sendcmpct = [] | ||||
tip = int(node.getbestblockhash(), 16) | tip = int(node.getbestblockhash(), 16) | ||||
def check_announcement_of_new_block(node, peer, predicate): | def check_announcement_of_new_block(node, peer, predicate): | ||||
peer.clear_block_announcement() | peer.clear_block_announcement() | ||||
block_hash = int(self.generate(node, 1)[0], 16) | block_hash = int(self.generate(node, 1)[0], 16) | ||||
peer.wait_for_block_announcement(block_hash, timeout=30) | peer.wait_for_block_announcement(block_hash, timeout=30) | ||||
assert peer.block_announced | assert peer.block_announced | ||||
with p2p_lock: | with p2p_lock: | ||||
assert predicate(peer), ( | assert predicate(peer), ( | ||||
f"block_hash={block_hash!r}, " | f"block_hash={block_hash!r}, " | ||||
f"cmpctblock={peer.last_message.get('cmpctblock', None)!r}, " | f"cmpctblock={peer.last_message.get('cmpctblock', None)!r}, " | ||||
f"inv={peer.last_message.get('inv', None)!r}" | f"inv={peer.last_message.get('inv', None)!r}" | ||||
) | ) | ||||
# We shouldn't get any block announcements via cmpctblock yet. | # We shouldn't get any block announcements via cmpctblock yet. | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" not in p.last_message) | node, test_node, lambda p: "cmpctblock" not in p.last_message | ||||
) | |||||
# Try one more time, this time after requesting headers. | # Try one more time, this time after requesting headers. | ||||
test_node.request_headers_and_sync(locator=[tip]) | test_node.request_headers_and_sync(locator=[tip]) | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" not in p.last_message and "inv" in p.last_message) | node, | ||||
test_node, | |||||
lambda p: "cmpctblock" not in p.last_message and "inv" in p.last_message, | |||||
) | |||||
# Test a few ways of using sendcmpct that should NOT | # Test a few ways of using sendcmpct that should NOT | ||||
# result in compact block announcements. | # result in compact block announcements. | ||||
# Before each test, sync the headers chain. | # Before each test, sync the headers chain. | ||||
test_node.request_headers_and_sync(locator=[tip]) | test_node.request_headers_and_sync(locator=[tip]) | ||||
# Now try a SENDCMPCT message with too-high version | # Now try a SENDCMPCT message with too-high version | ||||
test_node.send_and_ping(msg_sendcmpct(announce=True, version=999)) | test_node.send_and_ping(msg_sendcmpct(announce=True, version=999)) | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" not in p.last_message) | node, test_node, lambda p: "cmpctblock" not in p.last_message | ||||
) | |||||
# Headers sync before next test. | # Headers sync before next test. | ||||
test_node.request_headers_and_sync(locator=[tip]) | test_node.request_headers_and_sync(locator=[tip]) | ||||
# Now try a SENDCMPCT message with valid version, but announce=False | # Now try a SENDCMPCT message with valid version, but announce=False | ||||
test_node.send_and_ping(msg_sendcmpct(announce=False, | test_node.send_and_ping( | ||||
version=preferred_version)) | msg_sendcmpct(announce=False, version=preferred_version) | ||||
) | |||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" not in p.last_message) | node, test_node, lambda p: "cmpctblock" not in p.last_message | ||||
) | |||||
# Headers sync before next test. | # Headers sync before next test. | ||||
test_node.request_headers_and_sync(locator=[tip]) | test_node.request_headers_and_sync(locator=[tip]) | ||||
# Finally, try a SENDCMPCT message with announce=True | # Finally, try a SENDCMPCT message with announce=True | ||||
test_node.send_and_ping(msg_sendcmpct(announce=True, | test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version)) | ||||
version=preferred_version)) | |||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" in p.last_message) | node, test_node, lambda p: "cmpctblock" in p.last_message | ||||
) | |||||
# Try one more time (no headers sync should be needed!) | # Try one more time (no headers sync should be needed!) | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" in p.last_message) | node, test_node, lambda p: "cmpctblock" in p.last_message | ||||
) | |||||
# Try one more time, after turning on sendheaders | # Try one more time, after turning on sendheaders | ||||
test_node.send_and_ping(msg_sendheaders()) | test_node.send_and_ping(msg_sendheaders()) | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" in p.last_message) | node, test_node, lambda p: "cmpctblock" in p.last_message | ||||
) | |||||
# Try one more time, after sending a version-1, announce=false message. | # Try one more time, after sending a version-1, announce=false message. | ||||
test_node.send_and_ping(msg_sendcmpct(announce=False, | test_node.send_and_ping( | ||||
version=preferred_version - 1)) | msg_sendcmpct(announce=False, version=preferred_version - 1) | ||||
) | |||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" in p.last_message) | node, test_node, lambda p: "cmpctblock" in p.last_message | ||||
) | |||||
# Now turn off announcements | # Now turn off announcements | ||||
test_node.send_and_ping(msg_sendcmpct(announce=False, | test_node.send_and_ping( | ||||
version=preferred_version)) | msg_sendcmpct(announce=False, version=preferred_version) | ||||
) | |||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message) | node, | ||||
test_node, | |||||
lambda p: "cmpctblock" not in p.last_message | |||||
and "headers" in p.last_message, | |||||
) | |||||
if old_node is not None: | if old_node is not None: | ||||
# Verify that a peer using an older protocol version can receive | # Verify that a peer using an older protocol version can receive | ||||
# announcements from this node. | # announcements from this node. | ||||
old_node.send_and_ping(msg_sendcmpct(announce=True, version=1)) | old_node.send_and_ping(msg_sendcmpct(announce=True, version=1)) | ||||
# Header sync | # Header sync | ||||
old_node.request_headers_and_sync(locator=[tip]) | old_node.request_headers_and_sync(locator=[tip]) | ||||
check_announcement_of_new_block( | check_announcement_of_new_block( | ||||
node, old_node, lambda p: "cmpctblock" in p.last_message) | node, old_node, lambda p: "cmpctblock" in p.last_message | ||||
) | |||||
# This test actually causes bitcoind to (reasonably!) disconnect us, so do | # This test actually causes bitcoind to (reasonably!) disconnect us, so do | ||||
# this last. | # this last. | ||||
def test_invalid_cmpctblock_message(self): | def test_invalid_cmpctblock_message(self): | ||||
self.generate(self.nodes[0], 101) | self.generate(self.nodes[0], 101) | ||||
block = self.build_block_on_tip(self.nodes[0]) | block = self.build_block_on_tip(self.nodes[0]) | ||||
cmpct_block = P2PHeaderAndShortIDs() | cmpct_block = P2PHeaderAndShortIDs() | ||||
cmpct_block.header = CBlockHeader(block) | cmpct_block.header = CBlockHeader(block) | ||||
cmpct_block.prefilled_txn_length = 1 | cmpct_block.prefilled_txn_length = 1 | ||||
# This index will be too high | # This index will be too high | ||||
prefilled_txn = PrefilledTransaction(1, block.vtx[0]) | prefilled_txn = PrefilledTransaction(1, block.vtx[0]) | ||||
cmpct_block.prefilled_txn = [prefilled_txn] | cmpct_block.prefilled_txn = [prefilled_txn] | ||||
self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block)) | self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block)) | ||||
assert_equal( | assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) | ||||
int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) | |||||
# Compare the generated shortids to what we expect based on BIP 152, given | # Compare the generated shortids to what we expect based on BIP 152, given | ||||
# bitcoind's choice of nonce. | # bitcoind's choice of nonce. | ||||
def test_compactblock_construction(self, node, test_node): | def test_compactblock_construction(self, node, test_node): | ||||
# Generate a bunch of transactions. | # Generate a bunch of transactions. | ||||
self.generate(node, 101) | self.generate(node, 101) | ||||
num_transactions = 25 | num_transactions = 25 | ||||
address = node.getnewaddress() | address = node.getnewaddress() | ||||
Show All 10 Lines | def test_compactblock_construction(self, node, test_node): | ||||
# Make sure we will receive a fast-announce compact block | # Make sure we will receive a fast-announce compact block | ||||
self.request_cb_announcements(test_node, node) | self.request_cb_announcements(test_node, node) | ||||
# Now mine a block, and look at the resulting compact block. | # Now mine a block, and look at the resulting compact block. | ||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
block_hash = int(self.generate(node, 1)[0], 16) | block_hash = int(self.generate(node, 1)[0], 16) | ||||
# Store the raw block in our internal format. | # Store the raw block in our internal format. | ||||
block = FromHex(CBlock(), | block = FromHex(CBlock(), node.getblock(uint256_hex(block_hash), False)) | ||||
node.getblock(uint256_hex(block_hash), False)) | |||||
for tx in block.vtx: | for tx in block.vtx: | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
block.rehash() | block.rehash() | ||||
# Wait until the block was announced (via compact blocks) | # Wait until the block was announced (via compact blocks) | ||||
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, | test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30) | ||||
timeout=30) | |||||
# Now fetch and check the compact block | # Now fetch and check the compact block | ||||
header_and_shortids = None | header_and_shortids = None | ||||
with p2p_lock: | with p2p_lock: | ||||
# Convert the on-the-wire representation to absolute indexes | # Convert the on-the-wire representation to absolute indexes | ||||
header_and_shortids = HeaderAndShortIDs( | header_and_shortids = HeaderAndShortIDs( | ||||
test_node.last_message["cmpctblock"].header_and_shortids) | test_node.last_message["cmpctblock"].header_and_shortids | ||||
) | |||||
self.check_compactblock_construction_from_block( | self.check_compactblock_construction_from_block( | ||||
header_and_shortids, block_hash, block) | header_and_shortids, block_hash, block | ||||
) | |||||
# Now fetch the compact block using a normal non-announce getdata | # Now fetch the compact block using a normal non-announce getdata | ||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
inv = CInv(MSG_CMPCT_BLOCK, block_hash) | inv = CInv(MSG_CMPCT_BLOCK, block_hash) | ||||
test_node.send_message(msg_getdata([inv])) | test_node.send_message(msg_getdata([inv])) | ||||
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, | test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30) | ||||
timeout=30) | |||||
# Now fetch and check the compact block | # Now fetch and check the compact block | ||||
header_and_shortids = None | header_and_shortids = None | ||||
with p2p_lock: | with p2p_lock: | ||||
# Convert the on-the-wire representation to absolute indexes | # Convert the on-the-wire representation to absolute indexes | ||||
header_and_shortids = HeaderAndShortIDs( | header_and_shortids = HeaderAndShortIDs( | ||||
test_node.last_message["cmpctblock"].header_and_shortids) | test_node.last_message["cmpctblock"].header_and_shortids | ||||
) | |||||
self.check_compactblock_construction_from_block( | self.check_compactblock_construction_from_block( | ||||
header_and_shortids, block_hash, block) | header_and_shortids, block_hash, block | ||||
) | |||||
def check_compactblock_construction_from_block( | def check_compactblock_construction_from_block( | ||||
self, header_and_shortids, block_hash, block): | self, header_and_shortids, block_hash, block | ||||
): | |||||
# Check that we got the right block! | # Check that we got the right block! | ||||
header_and_shortids.header.calc_sha256() | header_and_shortids.header.calc_sha256() | ||||
assert_equal(header_and_shortids.header.sha256, block_hash) | assert_equal(header_and_shortids.header.sha256, block_hash) | ||||
# Make sure the prefilled_txn appears to have included the coinbase | # Make sure the prefilled_txn appears to have included the coinbase | ||||
assert len(header_and_shortids.prefilled_txn) >= 1 | assert len(header_and_shortids.prefilled_txn) >= 1 | ||||
assert_equal(header_and_shortids.prefilled_txn[0].index, 0) | assert_equal(header_and_shortids.prefilled_txn[0].index, 0) | ||||
# Check that all prefilled_txn entries match what's in the block. | # Check that all prefilled_txn entries match what's in the block. | ||||
for entry in header_and_shortids.prefilled_txn: | for entry in header_and_shortids.prefilled_txn: | ||||
entry.tx.calc_sha256() | entry.tx.calc_sha256() | ||||
# This checks the tx agree | # This checks the tx agree | ||||
assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) | assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) | ||||
# Check that the cmpctblock message announced all the transactions. | # Check that the cmpctblock message announced all the transactions. | ||||
assert_equal(len(header_and_shortids.prefilled_txn) | assert_equal( | ||||
+ len(header_and_shortids.shortids), len(block.vtx)) | len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), | ||||
len(block.vtx), | |||||
) | |||||
# And now check that all the shortids are as expected as well. | # And now check that all the shortids are as expected as well. | ||||
# Determine the siphash keys to use. | # Determine the siphash keys to use. | ||||
[k0, k1] = header_and_shortids.get_siphash_keys() | [k0, k1] = header_and_shortids.get_siphash_keys() | ||||
index = 0 | index = 0 | ||||
while index < len(block.vtx): | while index < len(block.vtx): | ||||
if (len(header_and_shortids.prefilled_txn) > 0 and | if ( | ||||
header_and_shortids.prefilled_txn[0].index == index): | len(header_and_shortids.prefilled_txn) > 0 | ||||
and header_and_shortids.prefilled_txn[0].index == index | |||||
): | |||||
# Already checked prefilled transactions above | # Already checked prefilled transactions above | ||||
header_and_shortids.prefilled_txn.pop(0) | header_and_shortids.prefilled_txn.pop(0) | ||||
else: | else: | ||||
tx_hash = block.vtx[index].sha256 | tx_hash = block.vtx[index].sha256 | ||||
shortid = calculate_shortid(k0, k1, tx_hash) | shortid = calculate_shortid(k0, k1, tx_hash) | ||||
assert_equal(shortid, header_and_shortids.shortids[0]) | assert_equal(shortid, header_and_shortids.shortids[0]) | ||||
header_and_shortids.shortids.pop(0) | header_and_shortids.shortids.pop(0) | ||||
index += 1 | index += 1 | ||||
# Test that bitcoind requests compact blocks when we announce new blocks | # Test that bitcoind requests compact blocks when we announce new blocks | ||||
# via header or inv, and that responding to getblocktxn causes the block | # via header or inv, and that responding to getblocktxn causes the block | ||||
# to be successfully reconstructed. | # to be successfully reconstructed. | ||||
def test_compactblock_requests(self, node, test_node, version): | def test_compactblock_requests(self, node, test_node, version): | ||||
# Try announcing a block with an inv or header, expect a compactblock | # Try announcing a block with an inv or header, expect a compactblock | ||||
# request | # request | ||||
for announce in ["inv", "header"]: | for announce in ["inv", "header"]: | ||||
block = self.build_block_on_tip(node) | block = self.build_block_on_tip(node) | ||||
if announce == "inv": | if announce == "inv": | ||||
test_node.send_message( | test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)])) | ||||
msg_inv([CInv(MSG_BLOCK, block.sha256)])) | self.wait_until( | ||||
self.wait_until(lambda: "getheaders" in test_node.last_message, | lambda: "getheaders" in test_node.last_message, timeout=30 | ||||
timeout=30) | ) | ||||
test_node.send_header_for_blocks([block]) | test_node.send_header_for_blocks([block]) | ||||
else: | else: | ||||
test_node.send_header_for_blocks([block]) | test_node.send_header_for_blocks([block]) | ||||
test_node.wait_for_getdata([block.sha256], timeout=30) | test_node.wait_for_getdata([block.sha256], timeout=30) | ||||
assert_equal(test_node.last_message["getdata"].inv[0].type, 4) | assert_equal(test_node.last_message["getdata"].inv[0].type, 4) | ||||
# Send back a compactblock message that omits the coinbase | # Send back a compactblock message that omits the coinbase | ||||
comp_block = HeaderAndShortIDs() | comp_block = HeaderAndShortIDs() | ||||
comp_block.header = CBlockHeader(block) | comp_block.header = CBlockHeader(block) | ||||
comp_block.nonce = 0 | comp_block.nonce = 0 | ||||
[k0, k1] = comp_block.get_siphash_keys() | [k0, k1] = comp_block.get_siphash_keys() | ||||
coinbase_hash = block.vtx[0].sha256 | coinbase_hash = block.vtx[0].sha256 | ||||
comp_block.shortids = [ | comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)] | ||||
calculate_shortid(k0, k1, coinbase_hash)] | |||||
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | ||||
assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) | assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) | ||||
# Expect a getblocktxn message. | # Expect a getblocktxn message. | ||||
with p2p_lock: | with p2p_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_indexes = test_node.last_message[ | ||||
) | "getblocktxn" | ||||
].block_txn_request.to_absolute() | |||||
assert_equal(absolute_indexes, [0]) # should be a coinbase request | assert_equal(absolute_indexes, [0]) # should be a coinbase request | ||||
# 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 | # Note that num_transactions is number of transactions not including the | ||||
# coinbase. | # 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 _ in range(num_transactions): | for _ 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) | 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 | ordered_txs = block.vtx | ||||
block.vtx = [block.vtx[0]] + \ | block.vtx = [block.vtx[0]] + sorted(block.vtx[1:], key=lambda tx: tx.get_id()) | ||||
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, ordered_txs | 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 p2p_lock: | with p2p_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, ordered_txs = 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([ordered_txs[-1].sha256, 0, ordered_txs[-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, ordered_txs = 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([ordered_txs[-1].sha256, 0, ordered_txs[-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, ordered_txs = 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([ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
[ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | |||||
test_node.send_and_ping(msg_tx(ordered_txs[1])) | test_node.send_and_ping(msg_tx(ordered_txs[1])) | ||||
assert ordered_txs[1].hash in node.getrawmempool() | assert ordered_txs[1].hash in node.getrawmempool() | ||||
test_node.send_and_ping(msg_tx(ordered_txs[1])) | 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. | ||||
prefill_list = [0, 1, 2, 3, 4, 5] | prefill_list = [0, 1, 2, 3, 4, 5] | ||||
prefill_list.remove(block.vtx.index(ordered_txs[1])) | prefill_list.remove(block.vtx.index(ordered_txs[1])) | ||||
expected_index = block.vtx.index(ordered_txs[-1]) | expected_index = block.vtx.index(ordered_txs[-1]) | ||||
prefill_list.remove(expected_index) | prefill_list.remove(expected_index) | ||||
comp_block.initialize_from_block(block, prefill_list=prefill_list) | comp_block.initialize_from_block(block, prefill_list=prefill_list) | ||||
test_getblocktxn_response(comp_block, test_node, [expected_index]) | test_getblocktxn_response(comp_block, test_node, [expected_index]) | ||||
msg_bt.block_transactions = BlockTransactions( | msg_bt.block_transactions = BlockTransactions(block.sha256, [ordered_txs[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, ordered_txs = 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([ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | ||||
[ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) | |||||
for tx in ordered_txs[1:]: | for tx in ordered_txs[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 | ||||
# Clear out last request. | # Clear out last request. | ||||
with p2p_lock: | with p2p_lock: | ||||
test_node.last_message.pop("getblocktxn", None) | test_node.last_message.pop("getblocktxn", None) | ||||
# Send compact block | # Send compact block | ||||
comp_block.initialize_from_block(block, prefill_list=[0]) | comp_block.initialize_from_block(block, prefill_list=[0]) | ||||
test_tip_after_message( | test_tip_after_message( | ||||
node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256) | node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256 | ||||
) | |||||
with p2p_lock: | with p2p_lock: | ||||
# Shouldn't have gotten a request for any transaction | # Shouldn't have gotten a request for any transaction | ||||
assert "getblocktxn" not in test_node.last_message | assert "getblocktxn" not in test_node.last_message | ||||
# 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, ordered_txs = 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([ordered_txs[-1].sha256, 0, ordered_txs[-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 ordered_txs[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 ordered_txs[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_indices = [] | absolute_indices = [] | ||||
with p2p_lock: | with p2p_lock: | ||||
assert "getblocktxn" in test_node.last_message | assert "getblocktxn" in test_node.last_message | ||||
absolute_indices = test_node.last_message["getblocktxn"].block_txn_request.to_absolute( | absolute_indices = test_node.last_message[ | ||||
) | "getblocktxn" | ||||
].block_txn_request.to_absolute() | |||||
expected_indices = [] | expected_indices = [] | ||||
for i in [6, 7, 8, 9, 10]: | for i in [6, 7, 8, 9, 10]: | ||||
expected_indices.append(block.vtx.index(ordered_txs[i])) | expected_indices.append(block.vtx.index(ordered_txs[i])) | ||||
assert_equal(absolute_indices, sorted(expected_indices)) | 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 was made, we could just modify this test by having a | # change was 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, [ordered_txs[5]] + ordered_txs[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 | ||||
test_node.wait_for_getdata([block.sha256], timeout=10) | test_node.wait_for_getdata([block.sha256], timeout=10) | ||||
assert test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | assert test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | ||||
# Deliver the block | # Deliver the block | ||||
test_node.send_and_ping(msg_block(block)) | test_node.send_and_ping(msg_block(block)) | ||||
assert_equal(int(node.getbestblockhash(), 16), block.sha256) | assert_equal(int(node.getbestblockhash(), 16), block.sha256) | ||||
def test_getblocktxn_handler(self, node, test_node, version): | def test_getblocktxn_handler(self, node, test_node, version): | ||||
# bitcoind will not send blocktxn responses for blocks whose height is | # bitcoind will not send blocktxn responses for blocks whose height is | ||||
# more than 10 blocks deep. | # more than 10 blocks deep. | ||||
MAX_GETBLOCKTXN_DEPTH = 10 | MAX_GETBLOCKTXN_DEPTH = 10 | ||||
chain_height = node.getblockcount() | chain_height = node.getblockcount() | ||||
current_height = chain_height | current_height = chain_height | ||||
while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): | while current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH: | ||||
block_hash = node.getblockhash(current_height) | block_hash = node.getblockhash(current_height) | ||||
block = FromHex(CBlock(), node.getblock(block_hash, False)) | block = FromHex(CBlock(), node.getblock(block_hash, False)) | ||||
msg = msg_getblocktxn() | msg = msg_getblocktxn() | ||||
msg.block_txn_request = BlockTransactionsRequest( | msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), []) | ||||
int(block_hash, 16), []) | |||||
num_to_request = random.randint(1, len(block.vtx)) | num_to_request = random.randint(1, len(block.vtx)) | ||||
msg.block_txn_request.from_absolute( | msg.block_txn_request.from_absolute( | ||||
sorted(random.sample(range(len(block.vtx)), num_to_request))) | sorted(random.sample(range(len(block.vtx)), num_to_request)) | ||||
) | |||||
test_node.send_message(msg) | test_node.send_message(msg) | ||||
test_node.wait_until(lambda: "blocktxn" in test_node.last_message, | test_node.wait_until( | ||||
timeout=10) | lambda: "blocktxn" in test_node.last_message, timeout=10 | ||||
) | |||||
[tx.calc_sha256() for tx in block.vtx] | [tx.calc_sha256() for tx in block.vtx] | ||||
with p2p_lock: | with p2p_lock: | ||||
assert_equal(test_node.last_message["blocktxn"].block_transactions.blockhash, int( | assert_equal( | ||||
block_hash, 16)) | test_node.last_message["blocktxn"].block_transactions.blockhash, | ||||
int(block_hash, 16), | |||||
) | |||||
all_indices = msg.block_txn_request.to_absolute() | all_indices = msg.block_txn_request.to_absolute() | ||||
for index in all_indices: | for index in all_indices: | ||||
tx = test_node.last_message["blocktxn"].block_transactions.transactions.pop( | tx = test_node.last_message[ | ||||
0) | "blocktxn" | ||||
].block_transactions.transactions.pop(0) | |||||
tx.calc_sha256() | tx.calc_sha256() | ||||
assert_equal(tx.sha256, block.vtx[index].sha256) | assert_equal(tx.sha256, block.vtx[index].sha256) | ||||
test_node.last_message.pop("blocktxn", None) | test_node.last_message.pop("blocktxn", None) | ||||
current_height -= 1 | current_height -= 1 | ||||
# Next request should send a full block response, as we're past the | # Next request should send a full block response, as we're past the | ||||
# allowed depth for a blocktxn response. | # allowed depth for a blocktxn response. | ||||
block_hash = node.getblockhash(current_height) | block_hash = node.getblockhash(current_height) | ||||
msg.block_txn_request = BlockTransactionsRequest( | msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0]) | ||||
int(block_hash, 16), [0]) | |||||
with p2p_lock: | with p2p_lock: | ||||
test_node.last_message.pop("block", None) | test_node.last_message.pop("block", None) | ||||
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 p2p_lock: | with p2p_lock: | ||||
test_node.last_message["block"].block.calc_sha256() | test_node.last_message["block"].block.calc_sha256() | ||||
assert_equal( | assert_equal( | ||||
test_node.last_message["block"].block.sha256, int(block_hash, 16)) | test_node.last_message["block"].block.sha256, int(block_hash, 16) | ||||
) | |||||
assert "blocktxn" not in test_node.last_message | assert "blocktxn" not in test_node.last_message | ||||
def test_compactblocks_not_at_tip(self, node, test_node): | def test_compactblocks_not_at_tip(self, node, test_node): | ||||
# Test that requesting old compactblocks doesn't work. | # Test that requesting old compactblocks doesn't work. | ||||
MAX_CMPCTBLOCK_DEPTH = 5 | MAX_CMPCTBLOCK_DEPTH = 5 | ||||
new_blocks = [] | new_blocks = [] | ||||
for _ in range(MAX_CMPCTBLOCK_DEPTH + 1): | for _ in range(MAX_CMPCTBLOCK_DEPTH + 1): | ||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
new_blocks.append(self.generate(node, 1)[0]) | new_blocks.append(self.generate(node, 1)[0]) | ||||
test_node.wait_until(test_node.received_block_announcement, | test_node.wait_until(test_node.received_block_announcement, timeout=30) | ||||
timeout=30) | |||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
test_node.send_message(msg_getdata( | test_node.send_message( | ||||
[CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) | msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))]) | ||||
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, | ) | ||||
timeout=30) | test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30) | ||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
self.generate(node, 1) | self.generate(node, 1) | ||||
test_node.wait_until(test_node.received_block_announcement, | test_node.wait_until(test_node.received_block_announcement, timeout=30) | ||||
timeout=30) | |||||
test_node.clear_block_announcement() | test_node.clear_block_announcement() | ||||
with p2p_lock: | with p2p_lock: | ||||
test_node.last_message.pop("block", None) | test_node.last_message.pop("block", None) | ||||
test_node.send_message(msg_getdata( | test_node.send_message( | ||||
[CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) | msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))]) | ||||
test_node.wait_until(lambda: "block" in test_node.last_message, | ) | ||||
timeout=30) | test_node.wait_until(lambda: "block" in test_node.last_message, timeout=30) | ||||
with p2p_lock: | with p2p_lock: | ||||
test_node.last_message["block"].block.calc_sha256() | test_node.last_message["block"].block.calc_sha256() | ||||
assert_equal( | assert_equal( | ||||
test_node.last_message["block"].block.sha256, int(new_blocks[0], 16)) | test_node.last_message["block"].block.sha256, int(new_blocks[0], 16) | ||||
) | |||||
# Generate an old compactblock, and verify that it's not accepted. | # Generate an old compactblock, and verify that it's not accepted. | ||||
cur_height = node.getblockcount() | cur_height = node.getblockcount() | ||||
hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) | hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) | ||||
block = self.build_block_on_tip(node) | block = self.build_block_on_tip(node) | ||||
block.hashPrevBlock = hashPrevBlock | block.hashPrevBlock = hashPrevBlock | ||||
block.solve() | block.solve() | ||||
Show All 25 Lines | def test_end_to_end_block_relay(self, node, listeners): | ||||
block, _ = self.build_block_with_transactions(node, utxo, 10) | block, _ = self.build_block_with_transactions(node, utxo, 10) | ||||
[listener.clear_block_announcement() for listener in listeners] | [listener.clear_block_announcement() for listener in listeners] | ||||
node.submitblock(ToHex(block)) | node.submitblock(ToHex(block)) | ||||
for listener in listeners: | for listener in listeners: | ||||
listener.wait_until(lambda: "cmpctblock" in listener.last_message, | listener.wait_until( | ||||
timeout=30) | lambda: "cmpctblock" in listener.last_message, timeout=30 | ||||
) | |||||
with p2p_lock: | with p2p_lock: | ||||
for listener in listeners: | for listener in listeners: | ||||
listener.last_message["cmpctblock"].header_and_shortids.header.calc_sha256( | listener.last_message[ | ||||
) | "cmpctblock" | ||||
].header_and_shortids.header.calc_sha256() | |||||
assert_equal( | assert_equal( | ||||
listener.last_message["cmpctblock"].header_and_shortids.header.sha256, block.sha256) | listener.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, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) | ||||
Show All 15 Lines | class CompactBlocksTest(BitcoinTestFramework): | ||||
# Helper for enabling cb announcements | # Helper for enabling cb announcements | ||||
# Send the sendcmpct request and sync headers | # Send the sendcmpct request and sync headers | ||||
def request_cb_announcements(self, peer, node, version=1): | def request_cb_announcements(self, peer, node, version=1): | ||||
tip = node.getbestblockhash() | tip = node.getbestblockhash() | ||||
peer.get_headers(locator=[int(tip, 16)], hashstop=0) | peer.get_headers(locator=[int(tip, 16)], hashstop=0) | ||||
peer.send_and_ping(msg_sendcmpct(announce=True, version=version)) | peer.send_and_ping(msg_sendcmpct(announce=True, version=version)) | ||||
def test_compactblock_reconstruction_multiple_peers( | def test_compactblock_reconstruction_multiple_peers( | ||||
self, node, stalling_peer, delivery_peer): | 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) | ||||
Show All 10 Lines | ): | ||||
delivery_peer.sync_with_ping() | delivery_peer.sync_with_ping() | ||||
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 | ||||
delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) | delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) | ||||
assert_equal(int(node.getbestblockhash(), 16), block.sha256) | assert_equal(int(node.getbestblockhash(), 16), block.sha256) | ||||
self.utxos.append( | self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | ||||
[block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) | |||||
# Now test that delivering an invalid compact block won't break relay | # Now test that delivering an invalid compact block won't break relay | ||||
block, cmpct_block = announce_cmpct_block(node, stalling_peer) | block, cmpct_block = announce_cmpct_block(node, stalling_peer) | ||||
for tx in block.vtx[1:]: | for tx in block.vtx[1:]: | ||||
delivery_peer.send_message(msg_tx(tx)) | delivery_peer.send_message(msg_tx(tx)) | ||||
delivery_peer.sync_with_ping() | delivery_peer.sync_with_ping() | ||||
# TODO: modify txhash in a way that doesn't impact txid. | # TODO: modify txhash in a way that doesn't impact txid. | ||||
Show All 11 Lines | def test_highbandwidth_mode_states_via_getpeerinfo(self): | ||||
# create new p2p connection for a fresh state w/o any prior sendcmpct | # create new p2p connection for a fresh state w/o any prior sendcmpct | ||||
# messages sent | # messages sent | ||||
hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) | hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) | ||||
# assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}` | # assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}` | ||||
# match the given parameters for the last peer of a given node | # match the given parameters for the last peer of a given node | ||||
def assert_highbandwidth_states(node, hb_to, hb_from): | def assert_highbandwidth_states(node, hb_to, hb_from): | ||||
peerinfo = node.getpeerinfo()[-1] | peerinfo = node.getpeerinfo()[-1] | ||||
assert_equal(peerinfo['bip152_hb_to'], hb_to) | assert_equal(peerinfo["bip152_hb_to"], hb_to) | ||||
assert_equal(peerinfo['bip152_hb_from'], hb_from) | assert_equal(peerinfo["bip152_hb_from"], hb_from) | ||||
# initially, neither node has selected the other peer as high-bandwidth | # initially, neither node has selected the other peer as high-bandwidth | ||||
# yet | # yet | ||||
assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=False) | assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=False) | ||||
# peer requests high-bandwidth mode by sending sendcmpct(1) | # peer requests high-bandwidth mode by sending sendcmpct(1) | ||||
hb_test_node.send_and_ping(msg_sendcmpct(announce=True, version=1)) | hb_test_node.send_and_ping(msg_sendcmpct(announce=True, version=1)) | ||||
assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=True) | assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=True) | ||||
Show All 11 Lines | class CompactBlocksTest(BitcoinTestFramework): | ||||
def run_test(self): | def run_test(self): | ||||
# Get the nodes out of IBD | # Get the nodes out of IBD | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
# Setup the p2p connections | # Setup the p2p connections | ||||
self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) | self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) | ||||
self.ex_softfork_node = self.nodes[1].add_p2p_connection( | self.ex_softfork_node = self.nodes[1].add_p2p_connection( | ||||
TestP2PConn(), services=NODE_NETWORK) | TestP2PConn(), services=NODE_NETWORK | ||||
) | |||||
self.old_node = self.nodes[1].add_p2p_connection( | self.old_node = self.nodes[1].add_p2p_connection( | ||||
TestP2PConn(), services=NODE_NETWORK) | TestP2PConn(), services=NODE_NETWORK | ||||
) | |||||
# We will need UTXOs to construct transactions in later tests. | # We will need UTXOs to construct transactions in later tests. | ||||
self.make_utxos() | self.make_utxos() | ||||
self.log.info("Running tests:") | self.log.info("Running tests:") | ||||
self.log.info("\tTesting SENDCMPCT p2p message... ") | self.log.info("\tTesting SENDCMPCT p2p message... ") | ||||
self.test_sendcmpct(self.nodes[0], self.test_node, 1) | self.test_sendcmpct(self.nodes[0], self.test_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_sendcmpct( | self.test_sendcmpct( | ||||
self.nodes[1], self.ex_softfork_node, 1, old_node=self.old_node) | self.nodes[1], self.ex_softfork_node, 1, old_node=self.old_node | ||||
) | |||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting compactblock construction...") | self.log.info("\tTesting compactblock construction...") | ||||
self.test_compactblock_construction(self.nodes[0], self.test_node) | self.test_compactblock_construction(self.nodes[0], self.test_node) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_compactblock_construction( | self.test_compactblock_construction(self.nodes[1], self.ex_softfork_node) | ||||
self.nodes[1], self.ex_softfork_node) | |||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting compactblock requests... ") | self.log.info("\tTesting compactblock requests... ") | ||||
self.test_compactblock_requests(self.nodes[0], self.test_node, 1) | self.test_compactblock_requests(self.nodes[0], self.test_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_compactblock_requests( | self.test_compactblock_requests(self.nodes[1], self.ex_softfork_node, 2) | ||||
self.nodes[1], self.ex_softfork_node, 2) | |||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting getblocktxn requests...") | self.log.info("\tTesting getblocktxn requests...") | ||||
self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) | self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_getblocktxn_requests(self.nodes[1], self.ex_softfork_node, 2) | self.test_getblocktxn_requests(self.nodes[1], self.ex_softfork_node, 2) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting getblocktxn handler...") | self.log.info("\tTesting getblocktxn handler...") | ||||
self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1) | self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_getblocktxn_handler(self.nodes[1], self.ex_softfork_node, 2) | self.test_getblocktxn_handler(self.nodes[1], self.ex_softfork_node, 2) | ||||
self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) | self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info( | self.log.info( | ||||
"\tTesting compactblock requests/announcements not at chain tip...") | "\tTesting compactblock requests/announcements not at chain tip..." | ||||
) | |||||
self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node) | self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_compactblocks_not_at_tip( | self.test_compactblocks_not_at_tip(self.nodes[1], self.ex_softfork_node) | ||||
self.nodes[1], self.ex_softfork_node) | |||||
self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node) | self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting handling of incorrect blocktxn responses...") | self.log.info("\tTesting handling of incorrect blocktxn responses...") | ||||
self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1) | self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1) | ||||
self.sync_blocks() | self.sync_blocks() | ||||
self.test_incorrect_blocktxn_response( | self.test_incorrect_blocktxn_response(self.nodes[1], self.ex_softfork_node, 2) | ||||
self.nodes[1], self.ex_softfork_node, 2) | |||||
self.sync_blocks() | self.sync_blocks() | ||||
# End-to-end block relay tests | # End-to-end block relay tests | ||||
self.log.info("\tTesting end-to-end block relay...") | self.log.info("\tTesting end-to-end block relay...") | ||||
self.request_cb_announcements(self.test_node, self.nodes[0]) | self.request_cb_announcements(self.test_node, self.nodes[0]) | ||||
self.request_cb_announcements(self.old_node, self.nodes[1]) | self.request_cb_announcements(self.old_node, self.nodes[1]) | ||||
self.request_cb_announcements( | self.request_cb_announcements(self.ex_softfork_node, self.nodes[1], version=2) | ||||
self.ex_softfork_node, self.nodes[1], version=2) | |||||
self.test_end_to_end_block_relay( | self.test_end_to_end_block_relay( | ||||
self.nodes[0], [self.ex_softfork_node, self.test_node, self.old_node]) | self.nodes[0], [self.ex_softfork_node, self.test_node, self.old_node] | ||||
) | |||||
self.test_end_to_end_block_relay( | self.test_end_to_end_block_relay( | ||||
self.nodes[1], [self.ex_softfork_node, self.test_node, self.old_node]) | self.nodes[1], [self.ex_softfork_node, self.test_node, self.old_node] | ||||
) | |||||
self.log.info("\tTesting handling of invalid compact blocks...") | self.log.info("\tTesting handling of invalid compact blocks...") | ||||
self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node) | self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node) | ||||
self.test_invalid_tx_in_compactblock( | self.test_invalid_tx_in_compactblock(self.nodes[1], self.ex_softfork_node) | ||||
self.nodes[1], self.ex_softfork_node) | |||||
self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node) | self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node) | ||||
self.log.info( | self.log.info("\tTesting reconstructing compact blocks from all peers...") | ||||
"\tTesting reconstructing compact blocks from all peers...") | |||||
self.test_compactblock_reconstruction_multiple_peers( | self.test_compactblock_reconstruction_multiple_peers( | ||||
self.nodes[1], self.ex_softfork_node, self.old_node) | self.nodes[1], self.ex_softfork_node, self.old_node | ||||
) | |||||
self.sync_blocks() | self.sync_blocks() | ||||
self.log.info("\tTesting invalid index in cmpctblock message...") | self.log.info("\tTesting invalid index in cmpctblock message...") | ||||
self.test_invalid_cmpctblock_message() | self.test_invalid_cmpctblock_message() | ||||
self.log.info("Testing high-bandwidth mode states via getpeerinfo...") | self.log.info("Testing high-bandwidth mode states via getpeerinfo...") | ||||
self.test_highbandwidth_mode_states_via_getpeerinfo() | self.test_highbandwidth_mode_states_via_getpeerinfo() | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
CompactBlocksTest().main() | CompactBlocksTest().main() |