[qa] Update p2p-compactblocks.py for compactblocks v2

This commit is contained in:
Suhas Daftuar 2016-09-16 20:48:23 -04:00 committed by Pieter Wuille
parent 422fac649f
commit 27acfc1d2e

View File

@ -12,14 +12,16 @@ from test_framework.script import CScript, OP_TRUE
''' '''
CompactBlocksTest -- test compact blocks (BIP 152) CompactBlocksTest -- test compact blocks (BIP 152)
'''
Version 1 compact blocks are pre-segwit (txids)
Version 2 compact blocks are post-segwit (wtxids)
'''
# TestNode: A peer we use to send messages to bitcoind, and store responses. # TestNode: A peer we use to send messages to bitcoind, and store responses.
class TestNode(SingleNodeConnCB): class TestNode(SingleNodeConnCB):
def __init__(self): def __init__(self):
SingleNodeConnCB.__init__(self) SingleNodeConnCB.__init__(self)
self.last_sendcmpct = None self.last_sendcmpct = []
self.last_headers = None self.last_headers = None
self.last_inv = None self.last_inv = None
self.last_cmpctblock = None self.last_cmpctblock = None
@ -30,7 +32,7 @@ class TestNode(SingleNodeConnCB):
self.last_blocktxn = None self.last_blocktxn = None
def on_sendcmpct(self, conn, message): def on_sendcmpct(self, conn, message):
self.last_sendcmpct = message self.last_sendcmpct.append(message)
def on_block(self, conn, message): def on_block(self, conn, message):
self.last_block = message self.last_block = message
@ -90,29 +92,31 @@ class CompactBlocksTest(BitcoinTestFramework):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setup_clean_chain = True self.setup_clean_chain = True
self.num_nodes = 1 # Node0 = pre-segwit, node1 = segwit-aware
self.num_nodes = 2
self.utxos = [] self.utxos = []
def setup_network(self): def setup_network(self):
self.nodes = [] self.nodes = []
# Turn off segwit in this test, as compact blocks don't currently work # Start up node0 to be a version 1, pre-segwit node.
# with segwit. (After BIP 152 is updated to support segwit, we can self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
# test behavior with and without segwit enabled by adding a second node [["-debug", "-logtimemicros=1", "-bip9params=segwit:0:0"],
# to the test.) ["-debug", "-logtimemicros", "-txindex"]])
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [["-debug", "-logtimemicros=1", "-bip9params=segwit:0:0"]]) connect_nodes(self.nodes[0], 1)
def build_block_on_tip(self): def build_block_on_tip(self, node):
height = self.nodes[0].getblockcount() height = node.getblockcount()
tip = self.nodes[0].getbestblockhash() tip = node.getbestblockhash()
mtp = self.nodes[0].getblockheader(tip)['mediantime'] mtp = node.getblockheader(tip)['mediantime']
block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1) block = create_block(int(tip, 16), create_coinbase(height + 1), mtp + 1)
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):
block = self.build_block_on_tip() # Doesn't matter which node we use, just use node0.
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.nodes[0].generate(100) self.nodes[0].generate(100)
@ -125,7 +129,7 @@ class CompactBlocksTest(BitcoinTestFramework):
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() 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()
@ -134,26 +138,30 @@ class CompactBlocksTest(BitcoinTestFramework):
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": # Test "sendcmpct" (between peers preferring the same version):
# - No compact block announcements or getdata(MSG_CMPCT_BLOCK) unless # - No compact block announcements unless sendcmpct is sent.
# sendcmpct is sent. # - If sendcmpct is sent with version > preferred_version, the message is ignored.
# - If sendcmpct is sent with version > 1, 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.
def test_sendcmpct(self): # If old_node is passed in, request compact blocks with version=preferred-1
print("Testing SENDCMPCT p2p message... ") # and verify that it receives block announcements via compact block.
def test_sendcmpct(self, node, test_node, preferred_version, old_node=None):
# Make sure we get a version 0 SENDCMPCT message from our peer # Make sure we get a SENDCMPCT message from our peer
def received_sendcmpct(): def received_sendcmpct():
return (self.test_node.last_sendcmpct is not None) return (len(test_node.last_sendcmpct) > 0)
got_message = wait_until(received_sendcmpct, timeout=30) got_message = wait_until(received_sendcmpct, timeout=30)
assert(received_sendcmpct()) assert(received_sendcmpct())
assert(got_message) assert(got_message)
assert_equal(self.test_node.last_sendcmpct.version, 1) with mininode_lock:
# Check that the first version received is the preferred one
assert_equal(test_node.last_sendcmpct[0].version, preferred_version)
# And that we receive versions down to 1.
assert_equal(test_node.last_sendcmpct[-1].version, 1)
test_node.last_sendcmpct = []
tip = int(self.nodes[0].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()
@ -165,56 +173,75 @@ class CompactBlocksTest(BitcoinTestFramework):
assert(predicate(peer)) assert(predicate(peer))
# 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(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None)
# Try one more time, this time after requesting headers. # Try one more time, this time after requesting headers.
self.test_node.request_headers_and_sync(locator=[tip]) test_node.request_headers_and_sync(locator=[tip])
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is None and p.last_inv is not None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None and p.last_inv is not None)
# 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.
self.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
sendcmpct = msg_sendcmpct() sendcmpct = msg_sendcmpct()
sendcmpct.version = 2 sendcmpct.version = preferred_version+1
self.test_node.send_and_ping(sendcmpct) sendcmpct.announce = True
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is None) test_node.send_and_ping(sendcmpct)
check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None)
# Headers sync before next test. # Headers sync before next test.
self.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
self.test_node.send_and_ping(msg_sendcmpct()) sendcmpct.version = preferred_version
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is None) sendcmpct.announce = False
test_node.send_and_ping(sendcmpct)
check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None)
# Headers sync before next test. # Headers sync before next test.
self.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
sendcmpct.version = 1 sendcmpct.version = preferred_version
sendcmpct.announce = True sendcmpct.announce = True
self.test_node.send_and_ping(sendcmpct) test_node.send_and_ping(sendcmpct)
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is not None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None)
# 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(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is not None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None)
# Try one more time, after turning on sendheaders # Try one more time, after turning on sendheaders
self.test_node.send_and_ping(msg_sendheaders()) test_node.send_and_ping(msg_sendheaders())
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is not None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None)
# Try one more time, after sending a version-1, announce=false message.
sendcmpct.version = preferred_version-1
sendcmpct.announce = False
test_node.send_and_ping(sendcmpct)
check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is not None)
# Now turn off announcements # Now turn off announcements
sendcmpct.version = preferred_version
sendcmpct.announce = False sendcmpct.announce = False
self.test_node.send_and_ping(sendcmpct) test_node.send_and_ping(sendcmpct)
check_announcement_of_new_block(self.nodes[0], self.test_node, lambda p: p.last_cmpctblock is None and p.last_headers is not None) check_announcement_of_new_block(node, test_node, lambda p: p.last_cmpctblock is None and p.last_headers is not None)
if old_node is not None:
# Verify that a peer using an older protocol version can receive
# announcements from this node.
sendcmpct.version = preferred_version-1
sendcmpct.announce = True
old_node.send_and_ping(sendcmpct)
# Header sync
old_node.request_headers_and_sync(locator=[tip])
check_announcement_of_new_block(node, old_node, lambda p: p.last_cmpctblock is not None)
# This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. # This test actually causes bitcoind to (reasonably!) disconnect us, so do this last.
def test_invalid_cmpctblock_message(self): def test_invalid_cmpctblock_message(self):
print("Testing invalid index in cmpctblock message...")
self.nodes[0].generate(101) self.nodes[0].generate(101)
block = self.build_block_on_tip() 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)
@ -227,45 +254,61 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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): def test_compactblock_construction(self, node, test_node, version, use_witness_address):
print("Testing compactblock headers and shortIDs are correct...")
# Generate a bunch of transactions. # Generate a bunch of transactions.
self.nodes[0].generate(101) node.generate(101)
num_transactions = 25 num_transactions = 25
address = self.nodes[0].getnewaddress() address = node.getnewaddress()
if use_witness_address:
# Want at least one segwit spend, so move all funds to
# a witness address.
address = node.addwitnessaddress(address)
value_to_send = node.getbalance()
node.sendtoaddress(address, satoshi_round(value_to_send-Decimal(0.1)))
node.generate(1)
segwit_tx_generated = False
for i in range(num_transactions): for i in range(num_transactions):
self.nodes[0].sendtoaddress(address, 0.1) txid = node.sendtoaddress(address, 0.1)
hex_tx = node.gettransaction(txid)["hex"]
tx = FromHex(CTransaction(), hex_tx)
if not tx.wit.is_null():
segwit_tx_generated = True
if use_witness_address:
assert(segwit_tx_generated) # check that our test is not broken
self.test_node.sync_with_ping() self.test_node.sync_with_ping()
# Now mine a block, and look at the resulting compact block. # Now mine a block, and look at the resulting compact block.
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
block_hash = int(self.nodes[0].generate(1)[0], 16) block_hash = int(node.generate(1)[0], 16)
# Store the raw block in our internal format. # Store the raw block in our internal format.
block = FromHex(CBlock(), self.nodes[0].getblock("%02x" % block_hash, False)) block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False))
[tx.calc_sha256() for tx in block.vtx] [tx.calc_sha256() for tx in block.vtx]
block.rehash() block.rehash()
# Don't care which type of announcement came back for this test; just # Don't care which type of announcement came back for this test; just
# request the compact block if we didn't get one yet. # request the compact block if we didn't get one yet.
wait_until(self.test_node.received_block_announcement, timeout=30) wait_until(test_node.received_block_announcement, timeout=30)
assert(test_node.received_block_announcement())
with mininode_lock: with mininode_lock:
if self.test_node.last_cmpctblock is None: if test_node.last_cmpctblock is None:
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
inv = CInv(4, block_hash) # 4 == "CompactBlock" inv = CInv(4, block_hash) # 4 == "CompactBlock"
self.test_node.send_message(msg_getdata([inv])) test_node.send_message(msg_getdata([inv]))
wait_until(self.test_node.received_block_announcement, timeout=30) wait_until(test_node.received_block_announcement, timeout=30)
assert(test_node.received_block_announcement())
# Now we should have the compactblock # Now we should have the compactblock
header_and_shortids = None header_and_shortids = None
with mininode_lock: with mininode_lock:
assert(self.test_node.last_cmpctblock is not None) assert(test_node.last_cmpctblock is not None)
# Convert the on-the-wire representation to absolute indexes # Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(self.test_node.last_cmpctblock.header_and_shortids) header_and_shortids = HeaderAndShortIDs(test_node.last_cmpctblock.header_and_shortids)
# 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()
@ -278,8 +321,17 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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 non-witness parts of the tx agree
assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256)
# And this checks the witness
wtxid = entry.tx.calc_sha256(True)
if version == 2:
assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True))
else:
# Shouldn't have received a witness
assert(entry.tx.wit.is_null())
# 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) + len(header_and_shortids.shortids), len(block.vtx)) assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx))
@ -294,7 +346,10 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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:
shortid = calculate_shortid(k0, k1, block.vtx[index].sha256) tx_hash = block.vtx[index].sha256
if version == 2:
tx_hash = block.vtx[index].calc_sha256(True)
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
@ -302,49 +357,50 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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): # Post-segwit: upgraded nodes would only make this request of cb-version-2,
print("Testing compactblock requests... ") # NODE_WITNESS peers. Unupgraded nodes would still make this request of
# any cb-version-1-supporting peer.
def test_compactblock_requests(self, node, test_node):
# 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() block = self.build_block_on_tip(node)
with mininode_lock: with mininode_lock:
self.test_node.last_getdata = None test_node.last_getdata = None
if announce == "inv": if announce == "inv":
self.test_node.send_message(msg_inv([CInv(2, block.sha256)])) test_node.send_message(msg_inv([CInv(2, block.sha256)]))
else: else:
self.test_node.send_header_for_blocks([block]) test_node.send_header_for_blocks([block])
success = wait_until(lambda: self.test_node.last_getdata is not None, timeout=30) success = wait_until(lambda: test_node.last_getdata is not None, timeout=30)
assert(success) assert(success)
assert_equal(len(self.test_node.last_getdata.inv), 1) assert_equal(len(test_node.last_getdata.inv), 1)
assert_equal(self.test_node.last_getdata.inv[0].type, 4) assert_equal(test_node.last_getdata.inv[0].type, 4)
assert_equal(self.test_node.last_getdata.inv[0].hash, block.sha256) assert_equal(test_node.last_getdata.inv[0].hash, block.sha256)
# 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
comp_block.shortids = [1] # this is useless, and wrong comp_block.shortids = [1] # this is useless, and wrong
self.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(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock)
# Expect a getblocktxn message. # Expect a getblocktxn message.
with mininode_lock: with mininode_lock:
assert(self.test_node.last_getblocktxn is not None) assert(test_node.last_getblocktxn is not None)
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() absolute_indexes = test_node.last_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]]
self.test_node.send_and_ping(msg) test_node.send_and_ping(msg)
assert_equal(int(self.nodes[0].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.
def build_block_with_transactions(self, utxo, num_transactions): def build_block_with_transactions(self, node, utxo, num_transactions):
block = self.build_block_on_tip() block = self.build_block_on_tip(node)
for i in range(num_transactions): for i in range(num_transactions):
tx = CTransaction() tx = CTransaction()
@ -361,118 +417,113 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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): def test_getblocktxn_requests(self, node, test_node, version):
print("Testing getblocktxn requests...") with_witness = (version==2)
def test_getblocktxn_response(compact_block, peer, expected_result):
msg = msg_cmpctblock(compact_block.to_p2p())
peer.send_and_ping(msg)
with mininode_lock:
assert(peer.last_getblocktxn is not None)
absolute_indexes = peer.last_getblocktxn.block_txn_request.to_absolute()
assert_equal(absolute_indexes, expected_result)
def test_tip_after_message(node, peer, msg, tip):
peer.send_and_ping(msg)
assert_equal(int(node.getbestblockhash(), 16), tip)
# First try announcing compactblocks that won't reconstruct, and verify # First try announcing compactblocks that won't reconstruct, and verify
# that we receive getblocktxn messages back. # that we receive getblocktxn messages back.
utxo = self.utxos.pop(0) utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(utxo, 5) block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
comp_block = HeaderAndShortIDs() comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block) comp_block.initialize_from_block(block, use_witness=with_witness)
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5])
with mininode_lock:
assert(self.test_node.last_getblocktxn is not None) msg_bt = msg_blocktxn()
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() if with_witness:
assert_equal(absolute_indexes, [1, 2, 3, 4, 5]) msg_bt = msg_witness_blocktxn() # serialize with witnesses
msg = msg_blocktxn() msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:])
msg.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256)
self.test_node.send_and_ping(msg)
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
utxo = self.utxos.pop(0) utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(utxo, 5) block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Now try interspersing the prefilled transactions # Now try interspersing the prefilled transactions
comp_block.initialize_from_block(block, prefill_list=[0, 1, 5]) comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness)
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_getblocktxn_response(comp_block, test_node, [2, 3, 4])
with mininode_lock: msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5])
assert(self.test_node.last_getblocktxn is not None) test_tip_after_message(node, test_node, msg_bt, block.sha256)
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. # Now try giving one transaction ahead of time.
utxo = self.utxos.pop(0) utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(utxo, 5) block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) 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])) test_node.send_and_ping(msg_tx(block.vtx[1]))
assert(block.vtx[1].hash in self.nodes[0].getrawmempool()) assert(block.vtx[1].hash in node.getrawmempool())
# Prefill 4 out of the 6 transactions, and verify that only the one # Prefill 4 out of the 6 transactions, and verify that only the one
# that was not in the mempool is requested. # that was not in the mempool is requested.
comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4]) comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness)
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_getblocktxn_response(comp_block, test_node, [5])
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]]) msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]])
self.test_node.send_and_ping(msg) test_tip_after_message(node, test_node, msg_bt, block.sha256)
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
# Now provide all transactions to the node before the block is # Now provide all transactions to the node before the block is
# announced and verify reconstruction happens immediately. # announced and verify reconstruction happens immediately.
utxo = self.utxos.pop(0) utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(utxo, 10) block = self.build_block_with_transactions(node, utxo, 10)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
for tx in block.vtx[1:]: for tx in block.vtx[1:]:
self.test_node.send_message(msg_tx(tx)) test_node.send_message(msg_tx(tx))
self.test_node.sync_with_ping() test_node.sync_with_ping()
# Make sure all transactions were accepted. # Make sure all transactions were accepted.
mempool = self.nodes[0].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 mininode_lock: with mininode_lock:
self.test_node.last_getblocktxn = None test_node.last_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], use_witness=with_witness)
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256)
with mininode_lock: with mininode_lock:
# Shouldn't have gotten a request for any transaction # Shouldn't have gotten a request for any transaction
assert(self.test_node.last_getblocktxn is None) assert(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 # Incorrectly responding to a getblocktxn shouldn't cause the block to be
# permanently failed. # permanently failed.
def test_incorrect_blocktxn_response(self): def test_incorrect_blocktxn_response(self, node, test_node, version):
print("Testing handling of incorrect blocktxn responses...")
if (len(self.utxos) == 0): if (len(self.utxos) == 0):
self.make_utxos() self.make_utxos()
utxo = self.utxos.pop(0) utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(utxo, 10) block = self.build_block_with_transactions(node, utxo, 10)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Relay the first 5 transactions from the block in advance # Relay the first 5 transactions from the block in advance
for tx in block.vtx[1:6]: for tx in block.vtx[1:6]:
self.test_node.send_message(msg_tx(tx)) test_node.send_message(msg_tx(tx))
self.test_node.sync_with_ping() test_node.sync_with_ping()
# Make sure all transactions were accepted. # Make sure all transactions were accepted.
mempool = self.nodes[0].getrawmempool() mempool = node.getrawmempool()
for tx in block.vtx[1:6]: for tx in block.vtx[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], use_witness=(version == 2))
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
absolute_indexes = [] absolute_indexes = []
with mininode_lock: with mininode_lock:
assert(self.test_node.last_getblocktxn is not None) assert(test_node.last_getblocktxn is not None)
absolute_indexes = self.test_node.last_getblocktxn.block_txn_request.to_absolute() absolute_indexes = test_node.last_getblocktxn.block_txn_request.to_absolute()
assert_equal(absolute_indexes, [6, 7, 8, 9, 10]) assert_equal(absolute_indexes, [6, 7, 8, 9, 10])
# Now give an incorrect response. # Now give an incorrect response.
@ -484,100 +535,107 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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()
if version==2:
msg = msg_witness_blocktxn()
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:])
self.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(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock)
# We should receive a getdata request # We should receive a getdata request
success = wait_until(lambda: self.test_node.last_getdata is not None, timeout=10) success = wait_until(lambda: test_node.last_getdata is not None, timeout=10)
assert(success) assert(success)
assert_equal(len(self.test_node.last_getdata.inv), 1) assert_equal(len(test_node.last_getdata.inv), 1)
assert_equal(self.test_node.last_getdata.inv[0].type, 2) assert(test_node.last_getdata.inv[0].type == 2 or test_node.last_getdata.inv[0].type == 2|MSG_WITNESS_FLAG)
assert_equal(self.test_node.last_getdata.inv[0].hash, block.sha256) assert_equal(test_node.last_getdata.inv[0].hash, block.sha256)
# Deliver the block # Deliver the block
self.test_node.send_and_ping(msg_block(block)) if version==2:
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) test_node.send_and_ping(msg_witness_block(block))
else:
def test_getblocktxn_handler(self): test_node.send_and_ping(msg_block(block))
print("Testing getblocktxn handler...") assert_equal(int(node.getbestblockhash(), 16), block.sha256)
def test_getblocktxn_handler(self, node, test_node, version):
# bitcoind won't respond for blocks whose height is more than 15 blocks # bitcoind won't respond for blocks whose height is more than 15 blocks
# deep. # deep.
MAX_GETBLOCKTXN_DEPTH = 15 MAX_GETBLOCKTXN_DEPTH = 15
chain_height = self.nodes[0].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 = self.nodes[0].getblockhash(current_height) block_hash = node.getblockhash(current_height)
block = FromHex(CBlock(), self.nodes[0].getblock(block_hash, False)) block = FromHex(CBlock(), node.getblock(block_hash, False))
msg = msg_getblocktxn() msg = msg_getblocktxn()
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), []) msg.block_txn_request = BlockTransactionsRequest(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(sorted(random.sample(range(len(block.vtx)), num_to_request))) msg.block_txn_request.from_absolute(sorted(random.sample(range(len(block.vtx)), num_to_request)))
self.test_node.send_message(msg) test_node.send_message(msg)
success = wait_until(lambda: self.test_node.last_blocktxn is not None, timeout=10) success = wait_until(lambda: test_node.last_blocktxn is not None, timeout=10)
assert(success) assert(success)
[tx.calc_sha256() for tx in block.vtx] [tx.calc_sha256() for tx in block.vtx]
with mininode_lock: with mininode_lock:
assert_equal(self.test_node.last_blocktxn.block_transactions.blockhash, int(block_hash, 16)) assert_equal(test_node.last_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 = self.test_node.last_blocktxn.block_transactions.transactions.pop(0) tx = test_node.last_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)
self.test_node.last_blocktxn = None if version == 1:
# Witnesses should have been stripped
assert(tx.wit.is_null())
else:
# Check that the witness matches
assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True))
test_node.last_blocktxn = None
current_height -= 1 current_height -= 1
# Next request should be ignored, as we're past the allowed depth. # Next request should be ignored, as we're past the allowed depth.
block_hash = self.nodes[0].getblockhash(current_height) block_hash = node.getblockhash(current_height)
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0]) msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0])
self.test_node.send_and_ping(msg) test_node.send_and_ping(msg)
with mininode_lock: with mininode_lock:
assert_equal(self.test_node.last_blocktxn, None) assert_equal(test_node.last_blocktxn, None)
def test_compactblocks_not_at_tip(self):
print("Testing compactblock requests/announcements not at chain tip...")
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 = 11 MAX_CMPCTBLOCK_DEPTH = 11
new_blocks = [] new_blocks = []
for i in range(MAX_CMPCTBLOCK_DEPTH): for i in range(MAX_CMPCTBLOCK_DEPTH):
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
new_blocks.append(self.nodes[0].generate(1)[0]) new_blocks.append(node.generate(1)[0])
wait_until(self.test_node.received_block_announcement, timeout=30) wait_until(test_node.received_block_announcement, timeout=30)
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
self.test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) 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) success = wait_until(lambda: test_node.last_cmpctblock is not None, timeout=30)
assert(success) assert(success)
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
self.nodes[0].generate(1) node.generate(1)
wait_until(self.test_node.received_block_announcement, timeout=30) wait_until(test_node.received_block_announcement, timeout=30)
self.test_node.clear_block_announcement() test_node.clear_block_announcement()
self.test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) 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) success = wait_until(lambda: test_node.last_block is not None, timeout=30)
assert(success) assert(success)
with mininode_lock: with mininode_lock:
self.test_node.last_block.block.calc_sha256() test_node.last_block.block.calc_sha256()
assert_equal(self.test_node.last_block.block.sha256, int(new_blocks[0], 16)) assert_equal(test_node.last_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 = self.nodes[0].getblockcount() cur_height = node.getblockcount()
hashPrevBlock = int(self.nodes[0].getblockhash(cur_height-5), 16) hashPrevBlock = int(node.getblockhash(cur_height-5), 16)
block = self.build_block_on_tip() block = self.build_block_on_tip(node)
block.hashPrevBlock = hashPrevBlock block.hashPrevBlock = hashPrevBlock
block.solve() block.solve()
comp_block = HeaderAndShortIDs() comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block) comp_block.initialize_from_block(block)
self.test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
tips = self.nodes[0].getchaintips() tips = node.getchaintips()
found = False found = False
for x in tips: for x in tips:
if x["hash"] == block.hash: if x["hash"] == block.hash:
@ -591,18 +649,61 @@ class CompactBlocksTest(BitcoinTestFramework):
msg = msg_getblocktxn() msg = msg_getblocktxn()
msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0]) msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0])
with mininode_lock: with mininode_lock:
self.test_node.last_blocktxn = None test_node.last_blocktxn = None
self.test_node.send_and_ping(msg) test_node.send_and_ping(msg)
with mininode_lock: with mininode_lock:
assert(self.test_node.last_blocktxn is None) assert(test_node.last_blocktxn is None)
def activate_segwit(self, node):
node.generate(144*3)
assert_equal(get_bip9_status(node, "segwit")["status"], 'active')
def test_end_to_end_block_relay(self, node, listeners):
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
[l.clear_block_announcement() for l in listeners]
# ToHex() won't serialize with witness, but this block has no witnesses
# anyway. TODO: repeat this test with witness tx's to a segwit node.
node.submitblock(ToHex(block))
for l in listeners:
wait_until(lambda: l.received_block_announcement(), timeout=30)
with mininode_lock:
for l in listeners:
assert(l.last_cmpctblock is not None)
l.last_cmpctblock.header_and_shortids.header.calc_sha256()
assert_equal(l.last_cmpctblock.header_and_shortids.header.sha256, block.sha256)
# Helper for enabling cb announcements
# Send the sendcmpct request and sync headers
def request_cb_announcements(self, peer, node, version):
tip = node.getbestblockhash()
peer.get_headers(locator=[int(tip, 16)], hashstop=0)
msg = msg_sendcmpct()
msg.version = version
msg.announce = True
peer.send_and_ping(msg)
def run_test(self): def run_test(self):
# Setup the p2p connections and start up the network thread. # Setup the p2p connections and start up the network thread.
self.test_node = TestNode() self.test_node = TestNode()
self.segwit_node = TestNode()
self.old_node = TestNode() # version 1 peer <--> segwit node
connections = [] connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.test_node)) connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.test_node))
connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1],
self.segwit_node, services=NODE_NETWORK|NODE_WITNESS))
connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1],
self.old_node, services=NODE_NETWORK))
self.test_node.add_connection(connections[0]) self.test_node.add_connection(connections[0])
self.segwit_node.add_connection(connections[1])
self.old_node.add_connection(connections[2])
NetworkThread().start() # Start up network handling in another thread NetworkThread().start() # Start up network handling in another thread
@ -612,13 +713,107 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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.test_sendcmpct() print("Running tests, pre-segwit activation:")
self.test_compactblock_construction()
self.test_compactblock_requests() print("\tTesting SENDCMPCT p2p message... ")
self.test_getblocktxn_requests() self.test_sendcmpct(self.nodes[0], self.test_node, 1)
self.test_getblocktxn_handler() sync_blocks(self.nodes)
self.test_compactblocks_not_at_tip() self.test_sendcmpct(self.nodes[1], self.segwit_node, 2, old_node=self.old_node)
self.test_incorrect_blocktxn_response() sync_blocks(self.nodes)
print("\tTesting compactblock construction...")
self.test_compactblock_construction(self.nodes[0], self.test_node, 1, False)
sync_blocks(self.nodes)
self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, False)
sync_blocks(self.nodes)
print("\tTesting compactblock requests... ")
self.test_compactblock_requests(self.nodes[0], self.test_node)
sync_blocks(self.nodes)
self.test_compactblock_requests(self.nodes[1], self.segwit_node)
sync_blocks(self.nodes)
print("\tTesting getblocktxn requests...")
self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1)
sync_blocks(self.nodes)
self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2)
sync_blocks(self.nodes)
print("\tTesting getblocktxn handler...")
self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1)
sync_blocks(self.nodes)
self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2)
self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1)
sync_blocks(self.nodes)
print("\tTesting compactblock requests/announcements not at chain tip...")
self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node)
sync_blocks(self.nodes)
self.test_compactblocks_not_at_tip(self.nodes[1], self.segwit_node)
self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node)
sync_blocks(self.nodes)
print("\tTesting handling of incorrect blocktxn responses...")
self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1)
sync_blocks(self.nodes)
self.test_incorrect_blocktxn_response(self.nodes[1], self.segwit_node, 2)
sync_blocks(self.nodes)
# End-to-end block relay tests
print("\tTesting end-to-end block relay...")
self.request_cb_announcements(self.test_node, self.nodes[0], 1)
self.request_cb_announcements(self.old_node, self.nodes[1], 1)
self.request_cb_announcements(self.segwit_node, self.nodes[1], 2)
self.test_end_to_end_block_relay(self.nodes[0], [self.segwit_node, self.test_node, self.old_node])
self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node])
# Advance to segwit activation
print ("\nAdvancing to segwit activation\n")
self.activate_segwit(self.nodes[1])
print ("Running tests, post-segwit activation...")
print("\tTesting compactblock construction...")
self.test_compactblock_construction(self.nodes[1], self.old_node, 1, True)
self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, True)
sync_blocks(self.nodes)
print("\tTesting compactblock requests (unupgraded node)... ")
self.test_compactblock_requests(self.nodes[0], self.test_node)
print("\tTesting getblocktxn requests (unupgraded node)...")
self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1)
# Need to manually sync node0 and node1, because post-segwit activation,
# node1 will not download blocks from node0.
print("\tSyncing nodes...")
assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash())
while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()):
block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount()+1)
self.nodes[1].submitblock(self.nodes[0].getblock(block_hash, False))
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
print("\tTesting compactblock requests (segwit node)... ")
self.test_compactblock_requests(self.nodes[1], self.segwit_node)
print("\tTesting getblocktxn requests (segwit node)...")
self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2)
sync_blocks(self.nodes)
print("\tTesting getblocktxn handler (segwit node should return witnesses)...")
self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2)
self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1)
# Test that if we submitblock to node1, we'll get a compact block
# announcement to all peers.
# (Post-segwit activation, blocks won't propagate from node0 to node1
# automatically, so don't bother testing a block announced to node0.)
print("\tTesting end-to-end block relay...")
self.request_cb_announcements(self.test_node, self.nodes[0], 1)
self.request_cb_announcements(self.old_node, self.nodes[1], 1)
self.request_cb_announcements(self.segwit_node, self.nodes[1], 2)
self.test_end_to_end_block_relay(self.nodes[1], [self.segwit_node, self.test_node, self.old_node])
print("\tTesting invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message() self.test_invalid_cmpctblock_message()