@ -43,7 +43,6 @@ class TestNode(NodeConnCB):
def __init__ ( self , block_store , tx_store ) :
def __init__ ( self , block_store , tx_store ) :
super ( ) . __init__ ( )
super ( ) . __init__ ( )
self . conn = None
self . bestblockhash = None
self . bestblockhash = None
self . block_store = block_store
self . block_store = block_store
self . block_request_map = { }
self . block_request_map = { }
@ -58,26 +57,23 @@ class TestNode(NodeConnCB):
self . lastInv = [ ]
self . lastInv = [ ]
self . closed = False
self . closed = False
def on_close ( self , conn ) :
def on_close ( self ) :
self . closed = True
self . closed = True
def add_connection ( self , conn ) :
def on_headers ( self , message ) :
self . conn = conn
def on_headers ( self , conn , message ) :
if len ( message . headers ) > 0 :
if len ( message . headers ) > 0 :
best_header = message . headers [ - 1 ]
best_header = message . headers [ - 1 ]
best_header . calc_sha256 ( )
best_header . calc_sha256 ( )
self . bestblockhash = best_header . sha256
self . bestblockhash = best_header . sha256
def on_getheaders ( self , conn , message ) :
def on_getheaders ( self , message ) :
response = self . block_store . headers_for ( message . locator , message . hashstop )
response = self . block_store . headers_for ( message . locator , message . hashstop )
if response is not None :
if response is not None :
conn . send_message ( response )
self . send_message ( response )
def on_getdata ( self , conn , message ) :
def on_getdata ( self , message ) :
[ conn . send_message ( r ) for r in self . block_store . get_blocks ( message . inv ) ]
[ self . send_message ( r ) for r in self . block_store . get_blocks ( message . inv ) ]
[ conn . send_message ( r ) for r in self . tx_store . get_transactions ( message . inv ) ]
[ self . send_message ( r ) for r in self . tx_store . get_transactions ( message . inv ) ]
for i in message . inv :
for i in message . inv :
if i . type == 1 or i . type == 1 | ( 1 << 30 ) : # MSG_TX or MSG_WITNESS_TX
if i . type == 1 or i . type == 1 | ( 1 << 30 ) : # MSG_TX or MSG_WITNESS_TX
@ -85,16 +81,16 @@ class TestNode(NodeConnCB):
elif i . type == 2 or i . type == 2 | ( 1 << 30 ) : # MSG_BLOCK or MSG_WITNESS_BLOCK
elif i . type == 2 or i . type == 2 | ( 1 << 30 ) : # MSG_BLOCK or MSG_WITNESS_BLOCK
self . block_request_map [ i . hash ] = True
self . block_request_map [ i . hash ] = True
def on_inv ( self , conn , message ) :
def on_inv ( self , message ) :
self . lastInv = [ x . hash for x in message . inv ]
self . lastInv = [ x . hash for x in message . inv ]
def on_pong ( self , conn , message ) :
def on_pong ( self , message ) :
try :
try :
del self . pingMap [ message . nonce ]
del self . pingMap [ message . nonce ]
except KeyError :
except KeyError :
raise AssertionError ( " Got pong for unknown ping [ %s ] " % repr ( message ) )
raise AssertionError ( " Got pong for unknown ping [ %s ] " % repr ( message ) )
def on_reject ( self , conn , message ) :
def on_reject ( self , message ) :
if message . message == b ' tx ' :
if message . message == b ' tx ' :
self . tx_reject_map [ message . data ] = RejectResult ( message . code , message . reason )
self . tx_reject_map [ message . data ] = RejectResult ( message . code , message . reason )
if message . message == b ' block ' :
if message . message == b ' block ' :
@ -102,30 +98,30 @@ class TestNode(NodeConnCB):
def send_inv ( self , obj ) :
def send_inv ( self , obj ) :
mtype = 2 if isinstance ( obj , CBlock ) else 1
mtype = 2 if isinstance ( obj , CBlock ) else 1
self . conn . send_message ( msg_inv ( [ CInv ( mtype , obj . sha256 ) ] ) )
self . send_message ( msg_inv ( [ CInv ( mtype , obj . sha256 ) ] ) )
def send_getheaders ( self ) :
def send_getheaders ( self ) :
# We ask for headers from their last tip.
# We ask for headers from their last tip.
m = msg_getheaders ( )
m = msg_getheaders ( )
m . locator = self . block_store . get_locator ( self . bestblockhash )
m . locator = self . block_store . get_locator ( self . bestblockhash )
self . conn . send_message ( m )
self . send_message ( m )
def send_header ( self , header ) :
def send_header ( self , header ) :
m = msg_headers ( )
m = msg_headers ( )
m . headers . append ( header )
m . headers . append ( header )
self . conn . send_message ( m )
self . send_message ( m )
# This assumes BIP31
# This assumes BIP31
def send_ping ( self , nonce ) :
def send_ping ( self , nonce ) :
self . pingMap [ nonce ] = True
self . pingMap [ nonce ] = True
self . conn . send_message ( msg_ping ( nonce ) )
self . send_message ( msg_ping ( nonce ) )
def received_ping_response ( self , nonce ) :
def received_ping_response ( self , nonce ) :
return nonce not in self . pingMap
return nonce not in self . pingMap
def send_mempool ( self ) :
def send_mempool ( self ) :
self . lastInv = [ ]
self . lastInv = [ ]
self . conn . send_message ( msg_mempool ( ) )
self . send_message ( msg_mempool ( ) )
# TestInstance:
# TestInstance:
#
#
@ -166,8 +162,7 @@ class TestManager():
def __init__ ( self , testgen , datadir ) :
def __init__ ( self , testgen , datadir ) :
self . test_generator = testgen
self . test_generator = testgen
self . connections = [ ]
self . p2p_connections = [ ]
self . test_nodes = [ ]
self . block_store = BlockStore ( datadir )
self . block_store = BlockStore ( datadir )
self . tx_store = TxStore ( datadir )
self . tx_store = TxStore ( datadir )
self . ping_counter = 1
self . ping_counter = 1
@ -175,28 +170,24 @@ class TestManager():
def add_all_connections ( self , nodes ) :
def add_all_connections ( self , nodes ) :
for i in range ( len ( nodes ) ) :
for i in range ( len ( nodes ) ) :
# Create a p2p connection to each node
# Create a p2p connection to each node
test_node = TestNode ( self . block_store , self . tx_store )
node = TestNode ( self . block_store , self . tx_store )
self . test_nodes . append ( test_node )
node . peer_connect ( ' 127.0.0.1 ' , p2p_port ( i ) )
self . connections . append ( NodeConn ( ' 127.0.0.1 ' , p2p_port ( i ) , test_node ) )
self . p2p_connections . append ( node )
# Make sure the TestNode (callback class) has a reference to its
# associated NodeConn
test_node . add_connection ( self . connections [ - 1 ] )
def clear_all_connections ( self ) :
def clear_all_connections ( self ) :
self . connections = [ ]
self . p2p_connections = [ ]
self . test_nodes = [ ]
def wait_for_disconnections ( self ) :
def wait_for_disconnections ( self ) :
def disconnected ( ) :
def disconnected ( ) :
return all ( node . closed for node in self . test_node s)
return all ( node . closed for node in self . p2p_connection s)
wait_until ( disconnected , timeout = 10 , lock = mininode_lock )
wait_until ( disconnected , timeout = 10 , lock = mininode_lock )
def wait_for_verack ( self ) :
def wait_for_verack ( self ) :
return all ( node . wait_for_verack ( ) for node in self . test_node s)
return all ( node . wait_for_verack ( ) for node in self . p2p_connection s)
def wait_for_pings ( self , counter ) :
def wait_for_pings ( self , counter ) :
def received_pongs ( ) :
def received_pongs ( ) :
return all ( node . received_ping_response ( counter ) for node in self . test_node s)
return all ( node . received_ping_response ( counter ) for node in self . p2p_connection s)
wait_until ( received_pongs , lock = mininode_lock )
wait_until ( received_pongs , lock = mininode_lock )
# sync_blocks: Wait for all connections to request the blockhash given
# sync_blocks: Wait for all connections to request the blockhash given
@ -206,17 +197,17 @@ class TestManager():
def blocks_requested ( ) :
def blocks_requested ( ) :
return all (
return all (
blockhash in node . block_request_map and node . block_request_map [ blockhash ]
blockhash in node . block_request_map and node . block_request_map [ blockhash ]
for node in self . test_node s
for node in self . p2p_connection s
)
)
# --> error if not requested
# --> error if not requested
wait_until ( blocks_requested , attempts = 20 * num_blocks , lock = mininode_lock )
wait_until ( blocks_requested , attempts = 20 * num_blocks , lock = mininode_lock )
# Send getheaders message
# Send getheaders message
[ c . cb . send_getheaders ( ) for c in self . connections ]
[ c . send_getheaders ( ) for c in self . p2p_ connections ]
# Send ping and wait for response -- synchronization hack
# Send ping and wait for response -- synchronization hack
[ c . cb . send_ping ( self . ping_counter ) for c in self . connections ]
[ c . send_ping ( self . ping_counter ) for c in self . p2p_ connections ]
self . wait_for_pings ( self . ping_counter )
self . wait_for_pings ( self . ping_counter )
self . ping_counter + = 1
self . ping_counter + = 1
@ -226,42 +217,42 @@ class TestManager():
def transaction_requested ( ) :
def transaction_requested ( ) :
return all (
return all (
txhash in node . tx_request_map and node . tx_request_map [ txhash ]
txhash in node . tx_request_map and node . tx_request_map [ txhash ]
for node in self . test_node s
for node in self . p2p_connection s
)
)
# --> error if not requested
# --> error if not requested
wait_until ( transaction_requested , attempts = 20 * num_events , lock = mininode_lock )
wait_until ( transaction_requested , attempts = 20 * num_events , lock = mininode_lock )
# Get the mempool
# Get the mempool
[ c . cb . send_mempool ( ) for c in self . connections ]
[ c . send_mempool ( ) for c in self . p2p_ connections ]
# Send ping and wait for response -- synchronization hack
# Send ping and wait for response -- synchronization hack
[ c . cb . send_ping ( self . ping_counter ) for c in self . connections ]
[ c . send_ping ( self . ping_counter ) for c in self . p2p_ connections ]
self . wait_for_pings ( self . ping_counter )
self . wait_for_pings ( self . ping_counter )
self . ping_counter + = 1
self . ping_counter + = 1
# Sort inv responses from each node
# Sort inv responses from each node
with mininode_lock :
with mininode_lock :
[ c . cb . lastInv . sort ( ) for c in self . connections ]
[ c . lastInv . sort ( ) for c in self . p2p_ connections ]
# Verify that the tip of each connection all agree with each other, and
# Verify that the tip of each connection all agree with each other, and
# with the expected outcome (if given)
# with the expected outcome (if given)
def check_results ( self , blockhash , outcome ) :
def check_results ( self , blockhash , outcome ) :
with mininode_lock :
with mininode_lock :
for c in self . connections :
for c in self . p2p_ connections:
if outcome is None :
if outcome is None :
if c . cb . bestblockhash != self . connections [ 0 ] . cb . bestblockhash :
if c . bestblockhash != self . p2p_ connections[ 0 ] . bestblockhash :
return False
return False
elif isinstance ( outcome , RejectResult ) : # Check that block was rejected w/ code
elif isinstance ( outcome , RejectResult ) : # Check that block was rejected w/ code
if c . cb . bestblockhash == blockhash :
if c . bestblockhash == blockhash :
return False
return False
if blockhash not in c . cb . block_reject_map :
if blockhash not in c . block_reject_map :
logger . error ( ' Block not in reject map: %064x ' % ( blockhash ) )
logger . error ( ' Block not in reject map: %064x ' % ( blockhash ) )
return False
return False
if not outcome . match ( c . cb . block_reject_map [ blockhash ] ) :
if not outcome . match ( c . block_reject_map [ blockhash ] ) :
logger . error ( ' Block rejected with %s instead of expected %s : %064x ' % ( c . cb . block_reject_map [ blockhash ] , outcome , blockhash ) )
logger . error ( ' Block rejected with %s instead of expected %s : %064x ' % ( c . block_reject_map [ blockhash ] , outcome , blockhash ) )
return False
return False
elif ( ( c . cb . bestblockhash == blockhash ) != outcome ) :
elif ( ( c . bestblockhash == blockhash ) != outcome ) :
return False
return False
return True
return True
@ -273,21 +264,21 @@ class TestManager():
# a particular tx's existence in the mempool is the same across all nodes.
# a particular tx's existence in the mempool is the same across all nodes.
def check_mempool ( self , txhash , outcome ) :
def check_mempool ( self , txhash , outcome ) :
with mininode_lock :
with mininode_lock :
for c in self . connections :
for c in self . p2p_ connections:
if outcome is None :
if outcome is None :
# Make sure the mempools agree with each other
# Make sure the mempools agree with each other
if c . cb . lastInv != self . connections [ 0 ] . cb . lastInv :
if c . lastInv != self . p2p_ connections[ 0 ] . lastInv :
return False
return False
elif isinstance ( outcome , RejectResult ) : # Check that tx was rejected w/ code
elif isinstance ( outcome , RejectResult ) : # Check that tx was rejected w/ code
if txhash in c . cb . lastInv :
if txhash in c . lastInv :
return False
return False
if txhash not in c . cb . tx_reject_map :
if txhash not in c . tx_reject_map :
logger . error ( ' Tx not in reject map: %064x ' % ( txhash ) )
logger . error ( ' Tx not in reject map: %064x ' % ( txhash ) )
return False
return False
if not outcome . match ( c . cb . tx_reject_map [ txhash ] ) :
if not outcome . match ( c . tx_reject_map [ txhash ] ) :
logger . error ( ' Tx rejected with %s instead of expected %s : %064x ' % ( c . cb . tx_reject_map [ txhash ] , outcome , txhash ) )
logger . error ( ' Tx rejected with %s instead of expected %s : %064x ' % ( c . tx_reject_map [ txhash ] , outcome , txhash ) )
return False
return False
elif ( ( txhash in c . cb . lastInv ) != outcome ) :
elif ( ( txhash in c . lastInv ) != outcome ) :
return False
return False
return True
return True
@ -332,25 +323,25 @@ class TestManager():
first_block_with_hash = False
first_block_with_hash = False
with mininode_lock :
with mininode_lock :
self . block_store . add_block ( block )
self . block_store . add_block ( block )
for c in self . connections :
for c in self . p2p_ connections:
if first_block_with_hash and block . sha256 in c . cb . block_request_map and c . cb . block_request_map [ block . sha256 ] == True :
if first_block_with_hash and block . sha256 in c . block_request_map and c . block_request_map [ block . sha256 ] == True :
# There was a previous request for this block hash
# There was a previous request for this block hash
# Most likely, we delivered a header for this block
# Most likely, we delivered a header for this block
# but never had the block to respond to the getdata
# but never had the block to respond to the getdata
c . send_message ( msg_block ( block ) )
c . send_message ( msg_block ( block ) )
else :
else :
c . cb . block_request_map [ block . sha256 ] = False
c . block_request_map [ block . sha256 ] = False
# Either send inv's to each node and sync, or add
# Either send inv's to each node and sync, or add
# to invqueue for later inv'ing.
# to invqueue for later inv'ing.
if ( test_instance . sync_every_block ) :
if ( test_instance . sync_every_block ) :
# if we expect success, send inv and sync every block
# if we expect success, send inv and sync every block
# if we expect failure, just push the block and see what happens.
# if we expect failure, just push the block and see what happens.
if outcome == True :
if outcome == True :
[ c . cb . send_inv ( block ) for c in self . connections ]
[ c . send_inv ( block ) for c in self . p2p_ connections ]
self . sync_blocks ( block . sha256 , 1 )
self . sync_blocks ( block . sha256 , 1 )
else :
else :
[ c . send_message ( msg_block ( block ) ) for c in self . connections ]
[ c . send_message ( msg_block ( block ) ) for c in self . p2p_ connections ]
[ c . cb . send_ping ( self . ping_counter ) for c in self . connections ]
[ c . send_ping ( self . ping_counter ) for c in self . p2p_ connections ]
self . wait_for_pings ( self . ping_counter )
self . wait_for_pings ( self . ping_counter )
self . ping_counter + = 1
self . ping_counter + = 1
if ( not self . check_results ( tip , outcome ) ) :
if ( not self . check_results ( tip , outcome ) ) :
@ -360,7 +351,7 @@ class TestManager():
elif isinstance ( b_or_t , CBlockHeader ) :
elif isinstance ( b_or_t , CBlockHeader ) :
block_header = b_or_t
block_header = b_or_t
self . block_store . add_header ( block_header )
self . block_store . add_header ( block_header )
[ c . cb . send_header ( block_header ) for c in self . connections ]
[ c . send_header ( block_header ) for c in self . p2p_ connections ]
else : # Tx test runner
else : # Tx test runner
assert ( isinstance ( b_or_t , CTransaction ) )
assert ( isinstance ( b_or_t , CTransaction ) )
@ -369,11 +360,11 @@ class TestManager():
# Add to shared tx store and clear map entry
# Add to shared tx store and clear map entry
with mininode_lock :
with mininode_lock :
self . tx_store . add_transaction ( tx )
self . tx_store . add_transaction ( tx )
for c in self . connections :
for c in self . p2p_ connections:
c . cb . tx_request_map [ tx . sha256 ] = False
c . tx_request_map [ tx . sha256 ] = False
# Again, either inv to all nodes or save for later
# Again, either inv to all nodes or save for later
if ( test_instance . sync_every_tx ) :
if ( test_instance . sync_every_tx ) :
[ c . cb . send_inv ( tx ) for c in self . connections ]
[ c . send_inv ( tx ) for c in self . p2p_ connections ]
self . sync_transaction ( tx . sha256 , 1 )
self . sync_transaction ( tx . sha256 , 1 )
if ( not self . check_mempool ( tx . sha256 , outcome ) ) :
if ( not self . check_mempool ( tx . sha256 , outcome ) ) :
raise AssertionError ( " Test failed at test %d " % test_number )
raise AssertionError ( " Test failed at test %d " % test_number )
@ -381,26 +372,26 @@ class TestManager():
invqueue . append ( CInv ( 1 , tx . sha256 ) )
invqueue . append ( CInv ( 1 , tx . sha256 ) )
# Ensure we're not overflowing the inv queue
# Ensure we're not overflowing the inv queue
if len ( invqueue ) == MAX_INV_SZ :
if len ( invqueue ) == MAX_INV_SZ :
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . connections ]
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . p2p_ connections ]
invqueue = [ ]
invqueue = [ ]
# Do final sync if we weren't syncing on every block or every tx.
# Do final sync if we weren't syncing on every block or every tx.
if ( not test_instance . sync_every_block and block is not None ) :
if ( not test_instance . sync_every_block and block is not None ) :
if len ( invqueue ) > 0 :
if len ( invqueue ) > 0 :
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . connections ]
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . p2p_ connections ]
invqueue = [ ]
invqueue = [ ]
self . sync_blocks ( block . sha256 , len ( test_instance . blocks_and_transactions ) )
self . sync_blocks ( block . sha256 , len ( test_instance . blocks_and_transactions ) )
if ( not self . check_results ( tip , block_outcome ) ) :
if ( not self . check_results ( tip , block_outcome ) ) :
raise AssertionError ( " Block test failed at test %d " % test_number )
raise AssertionError ( " Block test failed at test %d " % test_number )
if ( not test_instance . sync_every_tx and tx is not None ) :
if ( not test_instance . sync_every_tx and tx is not None ) :
if len ( invqueue ) > 0 :
if len ( invqueue ) > 0 :
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . connections ]
[ c . send_message ( msg_inv ( invqueue ) ) for c in self . p2p_ connections ]
invqueue = [ ]
invqueue = [ ]
self . sync_transaction ( tx . sha256 , len ( test_instance . blocks_and_transactions ) )
self . sync_transaction ( tx . sha256 , len ( test_instance . blocks_and_transactions ) )
if ( not self . check_mempool ( tx . sha256 , tx_outcome ) ) :
if ( not self . check_mempool ( tx . sha256 , tx_outcome ) ) :
raise AssertionError ( " Mempool test failed at test %d " % test_number )
raise AssertionError ( " Mempool test failed at test %d " % test_number )
[ c . disconnect_node ( ) for c in self . connections ]
[ c . disconnect_node ( ) for c in self . p2p_ connections ]
self . wait_for_disconnections ( )
self . wait_for_disconnections ( )
self . block_store . close ( )
self . block_store . close ( )
self . tx_store . close ( )
self . tx_store . close ( )