Suhas Daftuar
9 years ago
committed by
Wladimir J. van der Laan
2 changed files with 609 additions and 0 deletions
@ -0,0 +1,608 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# Copyright (c) 2016 The Bitcoin Core developers |
||||||
|
# Distributed under the MIT software license, see the accompanying |
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
||||||
|
|
||||||
|
from test_framework.mininode import * |
||||||
|
from test_framework.test_framework import BitcoinTestFramework |
||||||
|
from test_framework.util import * |
||||||
|
from test_framework.blocktools import create_block, create_coinbase |
||||||
|
from test_framework.siphash import siphash256 |
||||||
|
from test_framework.script import CScript, OP_TRUE |
||||||
|
|
||||||
|
''' |
||||||
|
CompactBlocksTest -- test compact blocks (BIP 152) |
||||||
|
''' |
||||||
|
|
||||||
|
|
||||||
|
# TestNode: A peer we use to send messages to bitcoind, and store responses. |
||||||
|
class TestNode(SingleNodeConnCB): |
||||||
|
def __init__(self): |
||||||
|
SingleNodeConnCB.__init__(self) |
||||||
|
self.last_sendcmpct = None |
||||||
|
self.last_headers = None |
||||||
|
self.last_inv = None |
||||||
|
self.last_cmpctblock = None |
||||||
|
self.block_announced = False |
||||||
|
self.last_getdata = None |
||||||
|
self.last_getblocktxn = None |
||||||
|
self.last_block = None |
||||||
|
self.last_blocktxn = None |
||||||
|
|
||||||
|
def on_sendcmpct(self, conn, message): |
||||||
|
self.last_sendcmpct = message |
||||||
|
|
||||||
|
def on_block(self, conn, message): |
||||||
|
self.last_block = message |
||||||
|
|
||||||
|
def on_cmpctblock(self, conn, message): |
||||||
|
self.last_cmpctblock = message |
||||||
|
self.block_announced = True |
||||||
|
|
||||||
|
def on_headers(self, conn, message): |
||||||
|
self.last_headers = message |
||||||
|
self.block_announced = True |
||||||
|
|
||||||
|
def on_inv(self, conn, message): |
||||||
|
self.last_inv = message |
||||||
|
self.block_announced = True |
||||||
|
|
||||||
|
def on_getdata(self, conn, message): |
||||||
|
self.last_getdata = message |
||||||
|
|
||||||
|
def on_getblocktxn(self, conn, message): |
||||||
|
self.last_getblocktxn = message |
||||||
|
|
||||||
|
def on_blocktxn(self, conn, message): |
||||||
|
self.last_blocktxn = message |
||||||
|
|
||||||
|
# Requires caller to hold mininode_lock |
||||||
|
def received_block_announcement(self): |
||||||
|
return self.block_announced |
||||||
|
|
||||||
|
def clear_block_announcement(self): |
||||||
|
with mininode_lock: |
||||||
|
self.block_announced = False |
||||||
|
self.last_inv = None |
||||||
|
self.last_headers = None |
||||||
|
self.last_cmpctblock = None |
||||||
|
|
||||||
|
def get_headers(self, locator, hashstop): |
||||||
|
msg = msg_getheaders() |
||||||
|
msg.locator.vHave = locator |
||||||
|
msg.hashstop = hashstop |
||||||
|
self.connection.send_message(msg) |
||||||
|
|
||||||
|
def send_header_for_blocks(self, new_blocks): |
||||||
|
headers_message = msg_headers() |
||||||
|
headers_message.headers = [CBlockHeader(b) for b in new_blocks] |
||||||
|
self.send_message(headers_message) |
||||||
|
|
||||||
|
|
||||||
|
class CompactBlocksTest(BitcoinTestFramework): |
||||||
|
def __init__(self): |
||||||
|
super().__init__() |
||||||
|
self.setup_clean_chain = True |
||||||
|
self.num_nodes = 1 |
||||||
|
self.utxos = [] |
||||||
|
|
||||||
|
def setup_network(self): |
||||||
|
self.nodes = [] |
||||||
|
|
||||||
|
# Turn off segwit in this test, as compact blocks don't currently work |
||||||
|
# with segwit. (After BIP 152 is updated to support segwit, we can |
||||||
|
# test behavior with and without segwit enabled by adding a second node |
||||||
|
# to the test.) |
||||||
|
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [["-debug", "-logtimemicros=1", "-bip9params=segwit:0:0"]]) |
||||||
|
|
||||||
|
def build_block_on_tip(self): |
||||||
|
height = self.nodes[0].getblockcount() |
||||||
|
tip = self.nodes[0].getbestblockhash() |
||||||
|
mtp = self.nodes[0].getblockheader(tip)['mediantime'] |
||||||
|
block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1) |
||||||
|
block.solve() |
||||||
|
return block |
||||||
|
|
||||||
|
# Create 10 more anyone-can-spend utxo's for testing. |
||||||
|
def make_utxos(self): |
||||||
|
block = self.build_block_on_tip() |
||||||
|
self.test_node.send_and_ping(msg_block(block)) |
||||||
|
assert(int(self.nodes[0].getbestblockhash(), 16) == block.sha256) |
||||||
|
self.nodes[0].generate(100) |
||||||
|
|
||||||
|
total_value = block.vtx[0].vout[0].nValue |
||||||
|
out_value = total_value // 10 |
||||||
|
tx = CTransaction() |
||||||
|
tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b'')) |
||||||
|
for i in range(10): |
||||||
|
tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) |
||||||
|
tx.rehash() |
||||||
|
|
||||||
|
block2 = self.build_block_on_tip() |
||||||
|
block2.vtx.append(tx) |
||||||
|
block2.hashMerkleRoot = block2.calc_merkle_root() |
||||||
|
block2.solve() |
||||||
|
self.test_node.send_and_ping(msg_block(block2)) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) |
||||||
|
self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) |
||||||
|
return |
||||||
|
|
||||||
|
# Test "sendcmpct": |
||||||
|
# - No compact block announcements or getdata(MSG_CMPCT_BLOCK) unless |
||||||
|
# sendcmpct is sent. |
||||||
|
# - If sendcmpct is sent with version > 0, the message is ignored. |
||||||
|
# - If sendcmpct is sent with boolean 0, then block announcements are not |
||||||
|
# made with compact blocks. |
||||||
|
# - If sendcmpct is then sent with boolean 1, then new block announcements |
||||||
|
# are made with compact blocks. |
||||||
|
def test_sendcmpct(self): |
||||||
|
print("Testing SENDCMPCT p2p message... ") |
||||||
|
|
||||||
|
# Make sure we get a version 0 SENDCMPCT message from our peer |
||||||
|
def received_sendcmpct(): |
||||||
|
return (self.test_node.last_sendcmpct is not None) |
||||||
|
got_message = wait_until(received_sendcmpct, timeout=30) |
||||||
|
assert(got_message) |
||||||
|
assert_equal(self.test_node.last_sendcmpct.version, 1) |
||||||
|
|
||||||
|
tip = int(self.nodes[0].getbestblockhash(), 16) |
||||||
|
|
||||||
|
def check_announcement_of_new_block(node, peer, predicate): |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
node.generate(1) |
||||||
|
got_message = wait_until(peer.received_block_announcement, timeout=30) |
||||||
|
assert(got_message) |
||||||
|
with mininode_lock: |
||||||
|
assert(predicate) |
||||||
|
|
||||||
|
# We shouldn't get any block announcements via cmpctblock yet. |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is None) |
||||||
|
|
||||||
|
# Try one more time, this time after requesting headers. |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
self.test_node.get_headers(locator=[tip], hashstop=0) |
||||||
|
wait_until(self.test_node.received_block_announcement, timeout=30) |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
|
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is None and self.test_node.last_inv is not None) |
||||||
|
|
||||||
|
# Now try a SENDCMPCT message with too-high version |
||||||
|
sendcmpct = msg_sendcmpct() |
||||||
|
sendcmpct.version = 2 |
||||||
|
self.test_node.send_message(sendcmpct) |
||||||
|
|
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is None) |
||||||
|
|
||||||
|
# Now try a SENDCMPCT message with valid version, but announce=False |
||||||
|
self.test_node.send_message(msg_sendcmpct()) |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is None) |
||||||
|
|
||||||
|
# Finally, try a SENDCMPCT message with announce=True |
||||||
|
sendcmpct.version = 1 |
||||||
|
sendcmpct.announce = True |
||||||
|
self.test_node.send_message(sendcmpct) |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is not None) |
||||||
|
|
||||||
|
# Try one more time |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is not None) |
||||||
|
|
||||||
|
# Try one more time, after turning on sendheaders |
||||||
|
self.test_node.send_message(msg_sendheaders()) |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is not None) |
||||||
|
|
||||||
|
# Now turn off announcements |
||||||
|
sendcmpct.announce = False |
||||||
|
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda: self.test_node.last_cmpctblock is None and self.test_node.last_headers is not None) |
||||||
|
|
||||||
|
# This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. |
||||||
|
def test_invalid_cmpctblock_message(self): |
||||||
|
print("Testing invalid index in cmpctblock message...") |
||||||
|
self.nodes[0].generate(101) |
||||||
|
block = self.build_block_on_tip() |
||||||
|
|
||||||
|
cmpct_block = P2PHeaderAndShortIDs() |
||||||
|
cmpct_block.header = CBlockHeader(block) |
||||||
|
cmpct_block.prefilled_txn_length = 1 |
||||||
|
# This index will be too high |
||||||
|
prefilled_txn = PrefilledTransaction(1, block.vtx[0]) |
||||||
|
cmpct_block.prefilled_txn = [prefilled_txn] |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(cmpct_block)) |
||||||
|
assert(int(self.nodes[0].getbestblockhash(), 16) == block.hashPrevBlock) |
||||||
|
|
||||||
|
# Compare the generated shortids to what we expect based on BIP 152, given |
||||||
|
# bitcoind's choice of nonce. |
||||||
|
def test_compactblock_construction(self): |
||||||
|
print("Testing compactblock headers and shortIDs are correct...") |
||||||
|
|
||||||
|
# Generate a bunch of transactions. |
||||||
|
self.nodes[0].generate(101) |
||||||
|
num_transactions = 25 |
||||||
|
address = self.nodes[0].getnewaddress() |
||||||
|
for i in range(num_transactions): |
||||||
|
self.nodes[0].sendtoaddress(address, 0.1) |
||||||
|
|
||||||
|
# Now mine a block, and look at the resulting compact block. |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
block_hash = int(self.nodes[0].generate(1)[0], 16) |
||||||
|
|
||||||
|
# Store the raw block in our internal format. |
||||||
|
block = FromHex(CBlock(), self.nodes[0].getblock("%02x" % block_hash, False)) |
||||||
|
[tx.calc_sha256() for tx in block.vtx] |
||||||
|
block.rehash() |
||||||
|
|
||||||
|
# Don't care which type of announcement came back for this test; just |
||||||
|
# request the compact block if we didn't get one yet. |
||||||
|
wait_until(self.test_node.received_block_announcement, timeout=30) |
||||||
|
|
||||||
|
with mininode_lock: |
||||||
|
if self.test_node.last_cmpctblock is None: |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
inv = CInv(4, block_hash) # 4 == "CompactBlock" |
||||||
|
self.test_node.send_message(msg_getdata([inv])) |
||||||
|
|
||||||
|
wait_until(self.test_node.received_block_announcement, timeout=30) |
||||||
|
|
||||||
|
# Now we should have the compactblock |
||||||
|
header_and_shortids = None |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_cmpctblock is not None) |
||||||
|
# Convert the on-the-wire representation to absolute indexes |
||||||
|
header_and_shortids = HeaderAndShortIDs(self.test_node.last_cmpctblock.header_and_shortids) |
||||||
|
|
||||||
|
# Check that we got the right block! |
||||||
|
header_and_shortids.header.calc_sha256() |
||||||
|
assert_equal(header_and_shortids.header.sha256, block_hash) |
||||||
|
|
||||||
|
# Make sure the prefilled_txn appears to have included the coinbase |
||||||
|
assert(len(header_and_shortids.prefilled_txn) >= 1) |
||||||
|
assert_equal(header_and_shortids.prefilled_txn[0].index, 0) |
||||||
|
|
||||||
|
# Check that all prefilled_txn entries match what's in the block. |
||||||
|
for entry in header_and_shortids.prefilled_txn: |
||||||
|
entry.tx.calc_sha256() |
||||||
|
assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) |
||||||
|
|
||||||
|
# Check that the cmpctblock message announced all the transactions. |
||||||
|
assert_equal(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. |
||||||
|
# Determine the siphash keys to use. |
||||||
|
[k0, k1] = header_and_shortids.get_siphash_keys() |
||||||
|
|
||||||
|
index = 0 |
||||||
|
while index < len(block.vtx): |
||||||
|
if (len(header_and_shortids.prefilled_txn) > 0 and |
||||||
|
header_and_shortids.prefilled_txn[0].index == index): |
||||||
|
# Already checked prefilled transactions above |
||||||
|
header_and_shortids.prefilled_txn.pop(0) |
||||||
|
else: |
||||||
|
shortid = calculate_shortid(k0, k1, block.vtx[index].sha256) |
||||||
|
assert_equal(shortid, header_and_shortids.shortids[0]) |
||||||
|
header_and_shortids.shortids.pop(0) |
||||||
|
index += 1 |
||||||
|
|
||||||
|
# Test that bitcoind requests compact blocks when we announce new blocks |
||||||
|
# via header or inv, and that responding to getblocktxn causes the block |
||||||
|
# to be successfully reconstructed. |
||||||
|
def test_compactblock_requests(self): |
||||||
|
print("Testing compactblock requests... ") |
||||||
|
|
||||||
|
# Try announcing a block with an inv or header, expect a compactblock |
||||||
|
# request |
||||||
|
for announce in ["inv", "header"]: |
||||||
|
block = self.build_block_on_tip() |
||||||
|
with mininode_lock: |
||||||
|
self.test_node.last_getdata = None |
||||||
|
|
||||||
|
if announce == "inv": |
||||||
|
self.test_node.send_message(msg_inv([CInv(2, block.sha256)])) |
||||||
|
else: |
||||||
|
self.test_node.send_header_for_blocks([block]) |
||||||
|
success = wait_until(lambda: self.test_node.last_getdata is not None, timeout=30) |
||||||
|
assert(success) |
||||||
|
assert_equal(len(self.test_node.last_getdata.inv), 1) |
||||||
|
assert_equal(self.test_node.last_getdata.inv[0].type, 4) |
||||||
|
assert_equal(self.test_node.last_getdata.inv[0].hash, block.sha256) |
||||||
|
|
||||||
|
# Send back a compactblock message that omits the coinbase |
||||||
|
comp_block = HeaderAndShortIDs() |
||||||
|
comp_block.header = CBlockHeader(block) |
||||||
|
comp_block.nonce = 0 |
||||||
|
comp_block.shortids = [1] # this is useless, and wrong |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) |
||||||
|
# Expect a getblocktxn message. |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_getblocktxn is not None) |
||||||
|
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() |
||||||
|
assert_equal(absolute_indexes, [0]) # should be a coinbase request |
||||||
|
|
||||||
|
# Send the coinbase, and verify that the tip advances. |
||||||
|
msg = msg_blocktxn() |
||||||
|
msg.block_transactions.blockhash = block.sha256 |
||||||
|
msg.block_transactions.transactions = [block.vtx[0]] |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
# Create a chain of transactions from given utxo, and add to a new block. |
||||||
|
def build_block_with_transactions(self, utxo, num_transactions): |
||||||
|
block = self.build_block_on_tip() |
||||||
|
|
||||||
|
for i in range(num_transactions): |
||||||
|
tx = CTransaction() |
||||||
|
tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) |
||||||
|
tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) |
||||||
|
tx.rehash() |
||||||
|
utxo = [tx.sha256, 0, tx.vout[0].nValue] |
||||||
|
block.vtx.append(tx) |
||||||
|
|
||||||
|
block.hashMerkleRoot = block.calc_merkle_root() |
||||||
|
block.solve() |
||||||
|
return block |
||||||
|
|
||||||
|
# Test that we only receive getblocktxn requests for transactions that the |
||||||
|
# node needs, and that responding to them causes the block to be |
||||||
|
# reconstructed. |
||||||
|
def test_getblocktxn_requests(self): |
||||||
|
print("Testing getblocktxn requests...") |
||||||
|
|
||||||
|
# First try announcing compactblocks that won't reconstruct, and verify |
||||||
|
# that we receive getblocktxn messages back. |
||||||
|
utxo = self.utxos.pop(0) |
||||||
|
|
||||||
|
block = self.build_block_with_transactions(utxo, 5) |
||||||
|
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) |
||||||
|
|
||||||
|
comp_block = HeaderAndShortIDs() |
||||||
|
comp_block.initialize_from_block(block) |
||||||
|
|
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_getblocktxn is not None) |
||||||
|
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() |
||||||
|
assert_equal(absolute_indexes, [1, 2, 3, 4, 5]) |
||||||
|
msg = msg_blocktxn() |
||||||
|
msg.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
utxo = self.utxos.pop(0) |
||||||
|
block = self.build_block_with_transactions(utxo, 5) |
||||||
|
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) |
||||||
|
|
||||||
|
# Now try interspersing the prefilled transactions |
||||||
|
comp_block.initialize_from_block(block, prefill_list=[0, 1, 5]) |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_getblocktxn is not None) |
||||||
|
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() |
||||||
|
assert_equal(absolute_indexes, [2, 3, 4]) |
||||||
|
msg.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5]) |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
# Now try giving one transaction ahead of time. |
||||||
|
utxo = self.utxos.pop(0) |
||||||
|
block = self.build_block_with_transactions(utxo, 5) |
||||||
|
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) |
||||||
|
self.test_node.send_and_ping(msg_tx(block.vtx[1])) |
||||||
|
assert(block.vtx[1].hash in self.nodes[0].getrawmempool()) |
||||||
|
|
||||||
|
# Prefill 4 out of the 6 transactions, and verify that only the one |
||||||
|
# that was not in the mempool is requested. |
||||||
|
comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4]) |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_getblocktxn is not None) |
||||||
|
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() |
||||||
|
assert_equal(absolute_indexes, [5]) |
||||||
|
|
||||||
|
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]]) |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
# Now provide all transactions to the node before the block is |
||||||
|
# announced and verify reconstruction happens immediately. |
||||||
|
utxo = self.utxos.pop(0) |
||||||
|
block = self.build_block_with_transactions(utxo, 10) |
||||||
|
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) |
||||||
|
for tx in block.vtx[1:]: |
||||||
|
self.test_node.send_message(msg_tx(tx)) |
||||||
|
self.test_node.sync_with_ping() |
||||||
|
# Make sure all transactions were accepted. |
||||||
|
mempool = self.nodes[0].getrawmempool() |
||||||
|
for tx in block.vtx[1:]: |
||||||
|
assert(tx.hash in mempool) |
||||||
|
|
||||||
|
# Clear out last request. |
||||||
|
with mininode_lock: |
||||||
|
self.test_node.last_getblocktxn = None |
||||||
|
|
||||||
|
# Send compact block |
||||||
|
comp_block.initialize_from_block(block, prefill_list=[0]) |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
with mininode_lock: |
||||||
|
# Shouldn't have gotten a request for any transaction |
||||||
|
assert(self.test_node.last_getblocktxn is None) |
||||||
|
# Tip should have updated |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
# Incorrectly responding to a getblocktxn shouldn't cause the block to be |
||||||
|
# permanently failed. |
||||||
|
def test_incorrect_blocktxn_response(self): |
||||||
|
print("Testing handling of incorrect blocktxn responses...") |
||||||
|
|
||||||
|
if (len(self.utxos) == 0): |
||||||
|
self.make_utxos() |
||||||
|
utxo = self.utxos.pop(0) |
||||||
|
|
||||||
|
block = self.build_block_with_transactions(utxo, 10) |
||||||
|
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) |
||||||
|
# Relay the first 5 transactions from the block in advance |
||||||
|
for tx in block.vtx[1:6]: |
||||||
|
self.test_node.send_message(msg_tx(tx)) |
||||||
|
self.test_node.sync_with_ping() |
||||||
|
# Make sure all transactions were accepted. |
||||||
|
mempool = self.nodes[0].getrawmempool() |
||||||
|
for tx in block.vtx[1:6]: |
||||||
|
assert(tx.hash in mempool) |
||||||
|
|
||||||
|
# Send compact block |
||||||
|
comp_block = HeaderAndShortIDs() |
||||||
|
comp_block.initialize_from_block(block, prefill_list=[0]) |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
absolute_indexes = [] |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_getblocktxn is not None) |
||||||
|
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() |
||||||
|
assert_equal(absolute_indexes, [6, 7, 8, 9, 10]) |
||||||
|
|
||||||
|
# Now give an incorrect response. |
||||||
|
# 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 |
||||||
|
# sending, and eg disconnect us for misbehavior. If that behavior |
||||||
|
# change were made, we could just modify this test by having a |
||||||
|
# different peer provide the block further down, so that we're still |
||||||
|
# verifying that the block isn't marked bad permanently. This is good |
||||||
|
# enough for now. |
||||||
|
msg = msg_blocktxn() |
||||||
|
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
|
||||||
|
# Tip should not have updated |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) |
||||||
|
|
||||||
|
# We should receive a getdata request |
||||||
|
success = wait_until(lambda: self.test_node.last_getdata is not None, timeout=10) |
||||||
|
assert(success) |
||||||
|
assert_equal(len(self.test_node.last_getdata.inv), 1) |
||||||
|
assert_equal(self.test_node.last_getdata.inv[0].type, 2) |
||||||
|
assert_equal(self.test_node.last_getdata.inv[0].hash, block.sha256) |
||||||
|
|
||||||
|
# Deliver the block |
||||||
|
self.test_node.send_and_ping(msg_block(block)) |
||||||
|
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) |
||||||
|
|
||||||
|
def test_getblocktxn_handler(self): |
||||||
|
print("Testing getblocktxn handler...") |
||||||
|
|
||||||
|
# bitcoind won't respond for blocks whose height is more than 15 blocks |
||||||
|
# deep. |
||||||
|
MAX_GETBLOCKTXN_DEPTH = 15 |
||||||
|
chain_height = self.nodes[0].getblockcount() |
||||||
|
current_height = chain_height |
||||||
|
while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): |
||||||
|
block_hash = self.nodes[0].getblockhash(current_height) |
||||||
|
block = FromHex(CBlock(), self.nodes[0].getblock(block_hash, False)) |
||||||
|
|
||||||
|
msg = msg_getblocktxn() |
||||||
|
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), []) |
||||||
|
num_to_request = random.randint(1, len(block.vtx)) |
||||||
|
msg.block_txn_request.from_absolute(sorted(random.sample(range(len(block.vtx)), num_to_request))) |
||||||
|
self.test_node.send_message(msg) |
||||||
|
success = wait_until(lambda: self.test_node.last_blocktxn is not None, timeout=10) |
||||||
|
assert(success) |
||||||
|
|
||||||
|
[tx.calc_sha256() for tx in block.vtx] |
||||||
|
with mininode_lock: |
||||||
|
assert_equal(self.test_node.last_blocktxn.block_transactions.blockhash, int(block_hash, 16)) |
||||||
|
all_indices = msg.block_txn_request.to_absolute() |
||||||
|
for index in all_indices: |
||||||
|
tx = self.test_node.last_blocktxn.block_transactions.transactions.pop(0) |
||||||
|
tx.calc_sha256() |
||||||
|
assert_equal(tx.sha256, block.vtx[index].sha256) |
||||||
|
self.test_node.last_blocktxn = None |
||||||
|
current_height -= 1 |
||||||
|
|
||||||
|
# Next request should be ignored, as we're past the allowed depth. |
||||||
|
block_hash = self.nodes[0].getblockhash(current_height) |
||||||
|
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0]) |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
with mininode_lock: |
||||||
|
assert_equal(self.test_node.last_blocktxn, None) |
||||||
|
|
||||||
|
def test_compactblocks_not_at_tip(self): |
||||||
|
print("Testing compactblock requests/announcements not at chain tip...") |
||||||
|
|
||||||
|
# Test that requesting old compactblocks doesn't work. |
||||||
|
MAX_CMPCTBLOCK_DEPTH = 11 |
||||||
|
new_blocks = [] |
||||||
|
for i in range(MAX_CMPCTBLOCK_DEPTH): |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
new_blocks.append(self.nodes[0].generate(1)[0]) |
||||||
|
wait_until(self.test_node.received_block_announcement, timeout=30) |
||||||
|
|
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
self.test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) |
||||||
|
success = wait_until(lambda: self.test_node.last_cmpctblock is not None, timeout=30) |
||||||
|
assert(success) |
||||||
|
|
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
self.nodes[0].generate(1) |
||||||
|
wait_until(self.test_node.received_block_announcement, timeout=30) |
||||||
|
self.test_node.clear_block_announcement() |
||||||
|
self.test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) |
||||||
|
success = wait_until(lambda: self.test_node.last_block is not None, timeout=30) |
||||||
|
assert(success) |
||||||
|
with mininode_lock: |
||||||
|
self.test_node.last_block.block.calc_sha256() |
||||||
|
assert_equal(self.test_node.last_block.block.sha256, int(new_blocks[0], 16)) |
||||||
|
|
||||||
|
# Generate an old compactblock, and verify that it's not accepted. |
||||||
|
cur_height = self.nodes[0].getblockcount() |
||||||
|
hashPrevBlock = int(self.nodes[0].getblockhash(cur_height-5), 16) |
||||||
|
block = self.build_block_on_tip() |
||||||
|
block.hashPrevBlock = hashPrevBlock |
||||||
|
block.solve() |
||||||
|
|
||||||
|
comp_block = HeaderAndShortIDs() |
||||||
|
comp_block.initialize_from_block(block) |
||||||
|
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) |
||||||
|
|
||||||
|
tips = self.nodes[0].getchaintips() |
||||||
|
found = False |
||||||
|
for x in tips: |
||||||
|
if x["hash"] == block.hash: |
||||||
|
assert_equal(x["status"], "headers-only") |
||||||
|
found = True |
||||||
|
break |
||||||
|
assert(found) |
||||||
|
|
||||||
|
# Requesting this block via getblocktxn should silently fail |
||||||
|
# (to avoid fingerprinting attacks). |
||||||
|
msg = msg_getblocktxn() |
||||||
|
msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0]) |
||||||
|
with mininode_lock: |
||||||
|
self.test_node.last_blocktxn = None |
||||||
|
self.test_node.send_and_ping(msg) |
||||||
|
with mininode_lock: |
||||||
|
assert(self.test_node.last_blocktxn is None) |
||||||
|
|
||||||
|
def run_test(self): |
||||||
|
# Setup the p2p connections and start up the network thread. |
||||||
|
self.test_node = TestNode() |
||||||
|
|
||||||
|
connections = [] |
||||||
|
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.test_node)) |
||||||
|
self.test_node.add_connection(connections[0]) |
||||||
|
|
||||||
|
NetworkThread().start() # Start up network handling in another thread |
||||||
|
|
||||||
|
# Test logic begins here |
||||||
|
self.test_node.wait_for_verack() |
||||||
|
|
||||||
|
# We will need UTXOs to construct transactions in later tests. |
||||||
|
self.make_utxos() |
||||||
|
|
||||||
|
self.test_sendcmpct() |
||||||
|
self.test_compactblock_construction() |
||||||
|
self.test_compactblock_requests() |
||||||
|
self.test_getblocktxn_requests() |
||||||
|
self.test_getblocktxn_handler() |
||||||
|
self.test_compactblocks_not_at_tip() |
||||||
|
self.test_incorrect_blocktxn_response() |
||||||
|
self.test_invalid_cmpctblock_message() |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
CompactBlocksTest().main() |
Loading…
Reference in new issue