@ -12,14 +12,16 @@ from test_framework.script import CScript, OP_TRUE
@@ -12,14 +12,16 @@ from test_framework.script import CScript, OP_TRUE
'''
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.
class TestNode ( SingleNodeConnCB ) :
def __init__ ( self ) :
SingleNodeConnCB . __init__ ( self )
self . last_sendcmpct = None
self . last_sendcmpct = [ ]
self . last_headers = None
self . last_inv = None
self . last_cmpctblock = None
@ -30,7 +32,7 @@ class TestNode(SingleNodeConnCB):
@@ -30,7 +32,7 @@ class TestNode(SingleNodeConnCB):
self . last_blocktxn = None
def on_sendcmpct ( self , conn , message ) :
self . last_sendcmpct = message
self . last_sendcmpct . append ( message )
def on_block ( self , conn , message ) :
self . last_block = message
@ -90,29 +92,31 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -90,29 +92,31 @@ class CompactBlocksTest(BitcoinTestFramework):
def __init__ ( self ) :
super ( ) . __init__ ( )
self . setup_clean_chain = True
self . num_nodes = 1
# Node0 = pre-segwit, node1 = segwit-aware
self . num_nodes = 2
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 = star t_nodes( self . num_nodes , self . options . tmpdir , [ [ " -debug " , " -logtimemicros=1 " , " -bip9params=segwit:0:0 " ] ] )
# Start up node0 to be a version 1, pre-segwit node.
self . nodes = start_nodes ( self . num_nodes , self . options . tmpdir ,
[ [ " -debug " , " -logtimemicros=1 " , " -bip9params=segwit:0:0 " ] ,
[ " -debug " , " -logtimemicros " , " -txindex " ] ] )
connec t_nodes( self . nodes [ 0 ] , 1 )
def build_block_on_tip ( self ) :
height = self . nodes [ 0 ] . getblockcount ( )
tip = self . nodes [ 0 ] . getbestblockhash ( )
mtp = self . nodes [ 0 ] . getblockheader ( tip ) [ ' mediantime ' ]
def build_block_on_tip ( self , node ) :
height = node . getblockcount ( )
tip = node . getbestblockhash ( )
mtp = node . 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 ( )
# 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 ) )
assert ( int ( self . nodes [ 0 ] . getbestblockhash ( ) , 16 ) == block . sha256 )
self . nodes [ 0 ] . generate ( 100 )
@ -125,7 +129,7 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -125,7 +129,7 @@ class CompactBlocksTest(BitcoinTestFramework):
tx . vout . append ( CTxOut ( out_value , CScript ( [ OP_TRUE ] ) ) )
tx . rehash ( )
block2 = self . build_block_on_tip ( )
block2 = self . build_block_on_tip ( self . nodes [ 0 ] )
block2 . vtx . append ( tx )
block2 . hashMerkleRoot = block2 . calc_merkle_root ( )
block2 . solve ( )
@ -134,26 +138,30 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -134,26 +138,30 @@ class CompactBlocksTest(BitcoinTestFramework):
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 > 1, the message is ignored.
# Test "sendcmpct" (between peers preferring the same version):
# - 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 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
# If old_node is passed in, request compact blocks with version=preferred-1
# 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 SENDCMPCT message from our peer
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 )
assert ( received_sendcmpct ( ) )
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 ) :
peer . clear_block_announcement ( )
@ -165,56 +173,75 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -165,56 +173,75 @@ class CompactBlocksTest(BitcoinTestFramework):
assert ( predicate ( peer ) )
# 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.
self . 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 )
test_node . request_headers_and_sync ( locator = [ tip ] )
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
# result in compact block announcements.
# 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
sendcmpct = msg_sendcmpct ( )
sendcmpct . version = 2
self . test_node . send_and_ping ( sendcmpct )
check_announcement_of_new_block ( self . nodes [ 0 ] , self . test_node , lambda p : p . last_cmpctblock is None )
sendcmpct . version = preferred_version + 1
sendcmpct . announce = True
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.
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
self . test_node . send_and_ping ( msg_sendcmpct ( ) )
check_announcement_of_new_block ( self . nodes [ 0 ] , self . test_node , lambda p : p . last_cmpctblock is None )
sendcmpct . version = preferred_version
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.
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
sendcmpct . version = 1
sendcmpct . version = preferred_version
sendcmpct . announce = True
self . 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 )
test_node . send_and_ping ( sendcmpct )
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!)
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
self . 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 )
test_node . send_and_ping ( msg_sendheaders ( ) )
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
sendcmpct . version = preferred_version
sendcmpct . announce = False
self . 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 )
test_node . send_and_ping ( sendcmpct )
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.
def test_invalid_cmpctblock_message ( self ) :
print ( " Testing invalid index in cmpctblock message... " )
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 . header = CBlockHeader ( block )
@ -227,45 +254,61 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -227,45 +254,61 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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... " )
def test_compactblock_construction ( self , node , test_node , version , use_witness_address ) :
# Generate a bunch of transactions.
self . nodes [ 0 ] . generate ( 101 )
node . generate ( 101 )
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 ) :
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 ( )
# 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 )
test_node . clear_block_announcement ( )
block_hash = int ( node . generate ( 1 ) [ 0 ] , 16 )
# 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 ]
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 )
wait_until ( test_node . received_block_announcement , timeout = 30 )
assert ( test_node . received_block_announcement ( ) )
with mininode_lock :
if self . test_node . last_cmpctblock is None :
self . test_node . clear_block_announcement ( )
if test_node . last_cmpctblock is None :
test_node . clear_block_announcement ( )
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
header_and_shortids = None
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
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!
header_and_shortids . header . calc_sha256 ( )
@ -278,8 +321,17 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -278,8 +321,17 @@ class CompactBlocksTest(BitcoinTestFramework):
# Check that all prefilled_txn entries match what's in the block.
for entry in header_and_shortids . prefilled_txn :
entry . tx . calc_sha256 ( )
# This checks the non-witness parts of the tx agree
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.
assert_equal ( len ( header_and_shortids . prefilled_txn ) + len ( header_and_shortids . shortids ) , len ( block . vtx ) )
@ -294,7 +346,10 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -294,7 +346,10 @@ class CompactBlocksTest(BitcoinTestFramework):
# Already checked prefilled transactions above
header_and_shortids . prefilled_txn . pop ( 0 )
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 ] )
header_and_shortids . shortids . pop ( 0 )
index + = 1
@ -302,49 +357,50 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -302,49 +357,50 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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... " )
# Post-segwit: upgraded nodes would only make this request of cb-version-2,
# 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
# request
for announce in [ " inv " , " header " ] :
block = self . build_block_on_tip ( )
block = self . build_block_on_tip ( node )
with mininode_lock :
self . test_node . last_getdata = None
test_node . last_getdata = None
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 :
self . test_node . send_header_for_blocks ( [ block ] )
success = wait_until ( lambda : self . test_node . last_getdata is not None , timeout = 30 )
test_node . send_header_for_blocks ( [ block ] )
success = wait_until ( lambda : 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 )
assert_equal ( len ( test_node . last_getdata . inv ) , 1 )
assert_equal ( test_node . last_getdata . inv [ 0 ] . type , 4 )
assert_equal ( 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 )
test_node . send_and_ping ( msg_cmpctblock ( comp_block . to_p2p ( ) ) )
assert_equal ( int ( node . 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 ( test_node . last_getblocktxn is not None )
absolute_indexes = 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 )
test_node . send_and_ping ( msg )
assert_equal ( int ( node . 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 ( )
def build_block_with_transactions ( self , node , utxo , num_transactions ) :
block = self . build_block_on_tip ( node )
for i in range ( num_transactions ) :
tx = CTransaction ( )
@ -361,118 +417,113 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -361,118 +417,113 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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... " )
def test_getblocktxn_requests ( self , node , test_node , version ) :
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
# that we receive getblocktxn messages back.
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 ] )
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 ( ) ) )
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 )
test_getblocktxn_response ( comp_block , test_node , [ 1 , 2 , 3 , 4 , 5 ] )
msg_bt = msg_blocktxn ( )
if with_witness :
msg_bt = msg_witness_blocktxn ( ) # serialize with witnesses
msg_bt . block_transactions = BlockTransactions ( block . sha256 , block . vtx [ 1 : ] )
test_tip_after_message ( node , test_node , msg_bt , block . sha256 )
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 ] )
# 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 )
comp_block . initialize_from_block ( block , prefill_list = [ 0 , 1 , 5 ] , use_witness = with_witness )
test_getblocktxn_response ( comp_block , test_node , [ 2 , 3 , 4 ] )
msg_bt . block_transactions = BlockTransactions ( block . sha256 , block . vtx [ 2 : 5 ] )
test_tip_after_message ( node , test_node , msg_bt , block . sha256 )
# Now try giving one transaction ahead of time.
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 . test_node . send_and_ping ( msg_tx ( block . vtx [ 1 ] ) )
assert ( block . vtx [ 1 ] . hash in self . nodes [ 0 ] . getrawmempool ( ) )
test_node . send_and_ping ( msg_tx ( block . vtx [ 1 ] ) )
assert ( block . vtx [ 1 ] . hash in node . 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 ] )
comp_block . initialize_from_block ( block , prefill_list = [ 0 , 2 , 3 , 4 ] , use_witness = with_witness )
test_getblocktxn_response ( comp_block , test_node , [ 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 )
msg_bt . block_transactions = BlockTransactions ( block . sha256 , [ block . vtx [ 5 ] ] )
test_tip_after_message ( node , test_node , msg_bt , 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 )
block = self . build_block_with_transactions ( node , 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 ( )
test_node . send_message ( msg_tx ( tx ) )
test_node . sync_with_ping ( )
# Make sure all transactions were accepted.
mempool = self . nodes [ 0 ] . getrawmempool ( )
mempool = node . 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
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 ( ) ) )
comp_block . initialize_from_block ( block , prefill_list = [ 0 ] , use_witness = with_witness )
test_tip_after_message ( node , test_node , msg_cmpctblock ( comp_block . to_p2p ( ) ) , block . sha256 )
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 )
assert ( test_node . last_getblocktxn is None )
# 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... " )
def test_incorrect_blocktxn_response ( self , node , test_node , version ) :
if ( len ( self . utxos ) == 0 ) :
self . make_utxos ( )
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 ] )
# 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 ( )
test_node . send_message ( msg_tx ( tx ) )
test_node . sync_with_ping ( )
# Make sure all transactions were accepted.
mempool = self . nodes [ 0 ] . getrawmempool ( )
mempool = node . 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 ( ) ) )
comp_block . initialize_from_block ( block , prefill_list = [ 0 ] , use_witness = ( version == 2 ) )
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 ( test_node . last_getblocktxn is not None )
absolute_indexes = test_node . last_getblocktxn . block_txn_request . to_absolute ( )
assert_equal ( absolute_indexes , [ 6 , 7 , 8 , 9 , 10 ] )
# Now give an incorrect response.
@ -484,100 +535,107 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -484,100 +535,107 @@ class CompactBlocksTest(BitcoinTestFramework):
# verifying that the block isn't marked bad permanently. This is good
# enough for now.
msg = msg_blocktxn ( )
if version == 2 :
msg = msg_witness_blocktxn ( )
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
assert_equal ( int ( self . nodes [ 0 ] . getbestblockhash ( ) , 16 ) , block . hashPrevBlock )
assert_equal ( int ( node . getbestblockhash ( ) , 16 ) , block . hashPrevBlock )
# 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_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 )
assert_equal ( len ( test_node . last_getdata . inv ) , 1 )
assert ( test_node . last_getdata . inv [ 0 ] . type == 2 or test_node . last_getdata . inv [ 0 ] . type == 2 | MSG_WITNESS_FLAG )
assert_equal ( 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... " )
if version == 2 :
test_node . send_and_ping ( msg_witness_block ( block ) )
else :
test_node . send_and_ping ( msg_block ( block ) )
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
# deep.
MAX_GETBLOCKTXN_DEPTH = 15
chain_height = self . nodes [ 0 ] . getblockcount ( )
chain_height = node . 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 ) )
block_hash = node . getblockhash ( current_height )
block = FromHex ( CBlock ( ) , node . 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 )
test_node . send_message ( msg )
success = wait_until ( lambda : 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 ) )
assert_equal ( 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 = 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
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
# 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 ] )
self . test_node . send_and_ping ( msg )
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... " )
assert_equal ( test_node . last_blocktxn , None )
def test_compactblocks_not_at_tip ( self , node , test_node ) :
# 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 )
test_node . clear_block_announcement ( )
new_blocks . append ( node . generate ( 1 ) [ 0 ] )
wait_until ( 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 )
test_node . clear_block_announcement ( )
test_node . send_message ( msg_getdata ( [ CInv ( 4 , int ( new_blocks [ 0 ] , 16 ) ) ] ) )
success = wait_until ( lambda : 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 )
test_node . clear_block_announcement ( )
node . generate ( 1 )
wait_until ( test_node . received_block_announcement , timeout = 30 )
test_node . clear_block_announcement ( )
test_node . send_message ( msg_getdata ( [ CInv ( 4 , int ( new_blocks [ 0 ] , 16 ) ) ] ) )
success = wait_until ( lambda : 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 ) )
test_node . last_block . block . calc_sha256 ( )
assert_equal ( 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 ( )
cur_height = node . getblockcount ( )
hashPrevBlock = int ( node . getblockhash ( cur_height - 5 ) , 16 )
block = self . build_block_on_tip ( node )
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 ( ) ) )
test_node . send_and_ping ( msg_cmpctblock ( comp_block . to_p2p ( ) ) )
tips = self . nodes [ 0 ] . getchaintips ( )
tips = node . getchaintips ( )
found = False
for x in tips :
if x [ " hash " ] == block . hash :
@ -591,18 +649,61 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -591,18 +649,61 @@ class CompactBlocksTest(BitcoinTestFramework):
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 )
test_node . last_blocktxn = None
test_node . send_and_ping ( msg )
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 ) :
# Setup the p2p connections and start up the network thread.
self . test_node = TestNode ( )
self . segwit_node = TestNode ( )
self . old_node = TestNode ( ) # version 1 peer <--> segwit node
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 ( 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 . segwit_node . add_connection ( connections [ 1 ] )
self . old_node . add_connection ( connections [ 2 ] )
NetworkThread ( ) . start ( ) # Start up network handling in another thread
@ -612,13 +713,107 @@ class CompactBlocksTest(BitcoinTestFramework):
@@ -612,13 +713,107 @@ class CompactBlocksTest(BitcoinTestFramework):
# 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 ( )
print ( " Running tests, pre-segwit activation: " )
print ( " \t Testing SENDCMPCT p2p message... " )
self . test_sendcmpct ( self . nodes [ 0 ] , self . test_node , 1 )
sync_blocks ( self . nodes )
self . test_sendcmpct ( self . nodes [ 1 ] , self . segwit_node , 2 , old_node = self . old_node )
sync_blocks ( self . nodes )
print ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing 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 ( " \n Advancing to segwit activation \n " )
self . activate_segwit ( self . nodes [ 1 ] )
print ( " Running tests, post-segwit activation... " )
print ( " \t Testing 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 ( " \t Testing compactblock requests (unupgraded node)... " )
self . test_compactblock_requests ( self . nodes [ 0 ] , self . test_node )
print ( " \t Testing 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 ( " \t Syncing 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 ( " \t Testing compactblock requests (segwit node)... " )
self . test_compactblock_requests ( self . nodes [ 1 ] , self . segwit_node )
print ( " \t Testing getblocktxn requests (segwit node)... " )
self . test_getblocktxn_requests ( self . nodes [ 1 ] , self . segwit_node , 2 )
sync_blocks ( self . nodes )
print ( " \t Testing 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 ( " \t Testing 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 ( " \t Testing invalid index in cmpctblock message... " )
self . test_invalid_cmpctblock_message ( )