def send_blocks_until_disconnected(self, node): """Keep sending blocks to the node until we're disconnected.""" for i in range(len(self.blocks)): try: node.send_message(msg_block(self.blocks[i])) except IOError as e: assert str(e) == 'Not connected, no pushbuf' break
def send_blocks_until_disconnected(self, p2p_conn): """Keep sending blocks to the node until we're disconnected.""" for i in range(len(self.blocks)): if not p2p_conn.is_connected: break try: p2p_conn.send_message(msg_block(self.blocks[i])) except IOError as e: assert not p2p_conn.is_connected break
def test_null_locators(self, test_node, inv_node): tip = self.nodes[0].getblockheader(self.nodes[0].generate(1)[0]) tip_hash = int(tip["hash"], 16) inv_node.check_last_announcement(inv=[tip_hash], headers=[]) test_node.check_last_announcement(inv=[tip_hash], headers=[]) self.log.info("Verify getheaders with null locator and valid hashstop returns headers.") test_node.clear_last_announcement() test_node.send_get_headers(locator=[], hashstop=tip_hash) test_node.check_last_announcement(headers=[tip_hash]) self.log.info("Verify getheaders with null locator and invalid hashstop does not return headers.") block = create_block(int(tip["hash"], 16), create_coinbase(tip["height"] + 1), tip["mediantime"] + 1) block.solve() test_node.send_header_for_blocks([block]) test_node.clear_last_announcement() test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16)) test_node.sync_with_ping() assert_equal(test_node.block_announced, False) inv_node.clear_last_announcement() test_node.send_message(msg_block(block)) inv_node.check_last_announcement(inv=[int(block.hash, 16)], headers=[])
def run_scenario6(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, timeout=30): # Create and send tx chains. txchains, bad, orphan = self.get_txchains_n(num_of_chains, chain_length, spend, num_of_bad_chains=0) to_mine = [] # Prepare inputs for sendrawtransactions rpc_txs_bulk_input = [] for tx in range(len(txchains)): # Collect txn input data for bulk submit through rpc interface. rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) if tx < len(txchains) // 2: # First half of txns will be mined in a block submitted through p2p interface. to_mine.append(txchains[tx]) root_block_info = conn.rpc.getblock(conn.rpc.getbestblockhash()) root_hash = root_block_info["hash"] root_height = root_block_info["height"] root_time = root_block_info["time"] # create the block block = self.make_block(to_mine, root_hash, root_height, root_time) conn.send_message(msg_block(block)) wait_until(lambda: conn.rpc.getbestblockhash() == block.hash, check_interval=0.3) # Check if there is an expected number of transactions in the mempool assert_equal(conn.rpc.getmempoolinfo()['size'], 0) # Submit a batch of txns through rpc interface. rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) # There should be to_mine rejected transactions. assert_equal(len(rejected_txns['invalid']), len(to_mine)) # bitcoind knows about the outputs of the last already mined transaction assert_equal( len([ tx for tx in rejected_txns['invalid'] if tx['reject_reason'] == 'txn-already-known' ]), 1) # bitcoind knows nothing about the previous already mined transactions so it considers them orphans assert_equal( len([ tx for tx in rejected_txns['invalid'] if tx['reject_reason'] == 'missing-inputs' ]), len(to_mine) - 1) # No transactions that were already mined should be in the mempool. The rest should be assert_equal(conn.rpc.getmempoolinfo()['size'], len(txchains) - len(to_mine))
def run_test_case(self, description, order=1, wait=False, numberOfSafeModeLevelChanges=1): self.log.info("Running test case: %s", description) # Remove test folder to start building chain from the beginning for each case if os.path.exists(os.path.join(self.nodes[0].datadir, "regtest")): shutil.rmtree(os.path.join(self.nodes[0].datadir, "regtest")) with self.run_node_with_connections(description, 0, None, 3) as (conn1, conn2, conn3): last_block_time = 0 conn1.rpc.generate(1) branch_1_root, last_block_time = make_block( conn1, last_block_time=last_block_time) branch_1_blocks = [branch_1_root] for _ in range(10): new_block, last_block_time = make_block( conn1, branch_1_blocks[-1], last_block_time=last_block_time) branch_1_blocks.append(new_block) branch_2_root, last_block_time = make_block( conn2, last_block_time=last_block_time) branch_2_blocks = [branch_2_root] for _ in range(20): new_block, last_block_time = make_block( conn2, branch_2_blocks[-1], last_block_time=last_block_time) branch_2_blocks.append(new_block) branch_3_root, last_block_time = make_block( conn3, last_block_time=last_block_time) if order == 1: self.send_branches( { 'conn': conn1, 'blocks': branch_1_blocks, 'do_send_blocks': True }, { 'conn': conn2, 'blocks': branch_2_blocks, 'do_send_blocks': False }, wait) else: self.send_branches( { 'conn': conn2, 'blocks': branch_2_blocks, 'do_send_blocks': False }, { 'conn': conn1, 'blocks': branch_1_blocks, 'do_send_blocks': True }, wait) # active tip is last block from branch 1 and branch 2 has status headers-only wait_for_tip(conn1, branch_1_blocks[-1].hash) wait_for_tip_status(conn2, branch_2_blocks[-1].hash, "headers-only") # we should have entered the safe mode try: conn1.rpc.getbalance() assert False, "Should not come to here, should raise exception in line above." except JSONRPCException as e: assert e.error[ "message"] == "Safe mode: Warning: The network does not appear to fully agree! We received headers of a large fork. Still waiting for block data for more details." def wait_for_log(): safeModeChanges = 0 line_text = "NotifySafeModeLevelChange: Warning: Found chain at least ~6 blocks longer than our best chain." for line in open( glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if line_text in line: self.log.info("Found line: %s", line) safeModeChanges += 1 if safeModeChanges == numberOfSafeModeLevelChanges: return True return False wait_until(wait_for_log) conn2.send_message(msg_block(branch_2_blocks[0])) conn2.cb.sync_with_ping() # send block from the third branch conn3.send_message(msg_block(branch_3_root)) conn3.cb.sync_with_ping() # we should still be in safe mode try: conn1.rpc.getbalance() assert False, "Should not come to here, should raise exception in line above." except JSONRPCException as e: assert e.error[ "message"] == "Safe mode: Warning: The network does not appear to fully agree! We received headers of a large fork. Still waiting for block data for more details."
def run_test(self): block_count = 0 # Create a P2P connection node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) node2 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2) node2.add_connection(connection) node3 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node3) node3.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() node2.wait_for_verack() node3.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) out = [] for i in range(100): out.append(self.chain.get_spendable_output()) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3 = self.chain.next_block(block_count, spend=out[0], extra_txns=10) block_count += 1 self.chain.set_tip(tip_block_num) block4 = self.chain.next_block(block_count, spend=out[0], extra_txns=12) block_count += 1 self.chain.set_tip(tip_block_num) block5 = self.chain.next_block(block_count, spend=out[0], extra_txns=14) block5_num = block_count block_count += 1 block6 = self.chain.next_block(block_count, spend=out[1], extra_txns=8) block_count += 1 self.chain.set_tip(block5_num) block7 = self.chain.next_block(block_count, spend=out[1], extra_txns=10) self.log.info(f"block2 hash: {block2.hash}") self.nodes[0].waitaftervalidatingblock(block2.hash, "add") self.log.info(f"block3 hash: {block3.hash}") self.nodes[0].waitaftervalidatingblock(block3.hash, "add") self.log.info(f"block4 hash: {block4.hash}") self.nodes[0].waitaftervalidatingblock(block4.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2.hash, block3.hash, block4.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block2)) # make sure we started validating block2 first as we expect this one to # be terminated later on in the test before its validation is complete # (algorithm for premature termination selects based on block height and # and validation duration - those that are in validation with smaller # height and longer are terminated first) wait_for_validating_blocks({block2.hash}, self.nodes[0], self.log) node1.send_message(msg_block(block3)) node2.send_message(msg_block(block4)) # make sure we started validating blocks wait_for_validating_blocks({block2.hash, block3.hash, block4.hash}, self.nodes[0], self.log) node3.send_message(msg_block(block5)) self.log.info(f"block5 hash: {block5.hash}") # check log file for logging about which block validation was terminated termination_log_found = False for line in open( glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if f"Block {block2.hash} will not be considered by the current tip activation as the maximum parallel block" in line: termination_log_found = True self.log.info("Found line: %s", line.strip()) break self.log.info(f"block6 hash: {block6.hash}") self.nodes[0].waitaftervalidatingblock(block6.hash, "add") self.log.info(f"block7 hash: {block7.hash}") self.nodes[0].waitaftervalidatingblock(block7.hash, "add") wait_for_waiting_blocks({block6.hash, block7.hash}, self.nodes[0], self.log) node3.send_message(msg_block(block6)) wait_for_validating_blocks({block6.hash}, self.nodes[0], self.log) node3.send_message(msg_block(block7)) wait_for_validating_blocks({block7.hash}, self.nodes[0], self.log) self.nodes[0].waitaftervalidatingblock(block2.hash, "remove") # block2 should be canceled. wait_for_not_validating_blocks({block2.hash}, self.nodes[0], self.log) self.log.info("removing wait status from block7") self.nodes[0].waitaftervalidatingblock(block7.hash, "remove") # finish block7 validation wait_for_not_validating_blocks({block7.hash}, self.nodes[0], self.log) # remove wait status from block to finish its validations so the test exits properly self.nodes[0].waitaftervalidatingblock(block3.hash, "remove") self.nodes[0].waitaftervalidatingblock(block4.hash, "remove") self.nodes[0].waitaftervalidatingblock(block6.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # block7 should be active in the end assert_equal(block7.hash, self.nodes[0].getbestblockhash()) # check log file for logging about which block validation was terminated termination_log_found = False for line in open( glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if f"Block {block2.hash} validation was terminated before completion." in line: termination_log_found = True self.log.info("Found line: %s", line.strip()) break assert_equal(termination_log_found, True)
def run_test(self): """Main test logic""" # Create P2P connections to two of the nodes self.nodes[0].add_p2p_connection(BaseNode()) # Start up network handling in another thread. This needs to be called # after the P2P connections have been created. network_thread_start() # wait_for_verack ensures that the P2P connection is fully up. self.nodes[0].p2p.wait_for_verack() # Generating a block on one of the nodes will get us out of IBD blocks = [int(self.nodes[0].generate(1)[0], 16)] self.sync_all([self.nodes[0:1]]) # Notice above how we called an RPC by calling a method with the same # name on the node object. Notice also how we used a keyword argument # to specify a named RPC argument. Neither of those are defined on the # node object. Instead there's some __getattr__() magic going on under # the covers to dispatch unrecognised attribute calls to the RPC # interface. # Logs are nice. Do plenty of them. They can be used in place of comments for # breaking the test into sub-sections. self.log.info("Starting test!") self.log.info("Calling a custom function") custom_function() self.log.info("Calling a custom method") self.custom_method() self.log.info("Create some blocks") self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock( self.nodes[0].getbestblockhash())['time'] + 1 height = 1 for i in range(10): # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface self.nodes[0].p2p.send_message(block_message) self.tip = block.sha256 blocks.append(self.tip) self.block_time += 1 height += 1 self.log.info( "Wait for node1 to reach current tip (height 11) using RPC") self.nodes[1].waitforblockheight(11) self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) self.log.info("Add P2P connection to node2") # We can't add additional P2P connections once the network thread has started. Disconnect the connection # to node0, wait for the network thread to terminate, then connect to node2. This is specific to # the current implementation of the network thread and may be improved in future. self.nodes[0].disconnect_p2ps() network_thread_join() self.nodes[2].add_p2p_connection(BaseNode()) network_thread_start() self.nodes[2].p2p.wait_for_verack() self.log.info( "Wait for node2 reach current tip. Test that it has propagated all the blocks to us" ) getdata_request = msg_getdata() for block in blocks: getdata_request.inv.append(CInv(2, block)) self.nodes[2].p2p.send_message(getdata_request) # wait_until() will loop until a predicate condition is met. Use it to test properties of the # P2PInterface objects. wait_until(lambda: sorted(blocks) == sorted( list(self.nodes[2].p2p.block_receive_map.keys())), timeout=5, lock=mininode_lock) self.log.info("Check that each block was received only once") # The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving # messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate. with mininode_lock: for block in self.nodes[2].p2p.block_receive_map.values(): assert_equal(block, 1)
def run_test(self): self.stop_node(0) with self.run_node_with_connections( "reject headers if previous block is missing", 0, [], self.num_peers) as p2p_connections: connection = p2p_connections[0] coinbase_height = 1 # 1. Create first block. block_0 = prepareBlock(coinbase_height, self.nodes[0].getbestblockhash()) # 2. Connection sends HEADERS msg to bitcoind and waits for GETDATA. headers_message = msg_headers() headers_message.headers = [CBlockHeader(block_0)] connection.cb.send_message(headers_message) connection.cb.wait_for_getdata() wait_until(lambda: connection.cb.last_message["getdata"].inv[0]. hash == block_0.sha256) # 3. Connection sends BLOCK to bitcoind. connection.cb.send_message(msg_block(block_0)) # 4. Bitcoind adds block to active chain. wait_for_tip(self.nodes[0], block_0.hash) # 5. Create two chained blocks. block_1 = prepareBlock(coinbase_height + 1, block_0.hash) block_2 = prepareBlock(coinbase_height + 2, block_1.hash) # 6. Connection sends HEADERS of the second block to bitcoind. It should be rejected. headers_message = msg_headers() headers_message.headers = [CBlockHeader(block_2)] connection.cb.send_message(headers_message) wait_until(lambda: check_for_log_msg( self, "received header " + block_2.hash + ": missing prev block", "/node0")) # 7. Connection sends HEADERS of the first block to bitcoind. It should be accepted. headers_message = msg_headers() headers_message.headers = [CBlockHeader(block_1)] connection.cb.send_message(headers_message) wait_until(lambda: connection.cb.last_message["getdata"].inv[0]. hash == block_1.sha256) # 8. Connection sends HEADERS of the second block to bitcoind. It should be accepted now that previous block is known. headers_message = msg_headers() headers_message.headers = [CBlockHeader(block_2)] connection.cb.send_message(headers_message) wait_until(lambda: connection.cb.last_message["getdata"].inv[0]. hash == block_2.sha256) # 9. Try to send alternative Genesis block (no previous block). It should be rejected. genesis_block = create_block(hashprev=0, coinbase=create_coinbase( height=0, outputValue=25)) genesis_block.solve() connection.cb.send_message(msg_block(genesis_block)) wait_until(lambda: check_for_log_msg( self, "ERROR: FindPreviousBlockIndex: prev block not found", "/node0"))
def run_test(self): def build_block_with_immature_stake(node): height = node.getblockcount() stakes = node.listunspent() # Take the latest, immature stake stake = min(stakes, key=lambda x: x['confirmations']) snapshot_meta = get_tip_snapshot_meta(node) coinbase = sign_coinbase( node, create_coinbase( height, stake, snapshot_meta.hash)) tip = int(node.getbestblockhash(), 16) block_time = node.getblock( self.nodes[0].getbestblockhash())['time'] + 1 block = create_block(tip, coinbase, block_time) return block def has_synced_blockchain(node): status = node.proposerstatus() return status['wallets'][0]['status'] != 'NOT_PROPOSING_SYNCING_BLOCKCHAIN' def wait_until_all_have_reached_state(expected, which_nodes): def predicate(i): status = nodes[i].proposerstatus() return status['wallets'][0]['status'] == expected wait_until(lambda: all(predicate(i) for i in which_nodes), timeout=5) return predicate def assert_number_of_connections(node, incoming, outgoing): status = node.proposerstatus() assert_equal(status['incoming_connections'], incoming) assert_equal(status['outgoing_connections'], outgoing) def check_reject(node, err, block): wait_until(lambda: node.p2p.has_reject(err, block), timeout=5) nodes = self.nodes # Create P2P connections to the second node self.nodes[1].add_p2p_connection(P2P()) self.log.info("Waiting untill the P2P connection is fully up...") self.nodes[1].p2p.wait_for_verack() self.log.info("Waiting for nodes to have started up...") wait_until(lambda: all(has_synced_blockchain(node) for node in self.nodes), timeout=5) self.log.info("Connecting nodes") connect_nodes(nodes[0], nodes[1].index) assert_number_of_connections( self.nodes[0], self.num_nodes - 1, self.num_nodes - 1) assert_number_of_connections( self.nodes[1], self.num_nodes, self.num_nodes - 1) self.setup_stake_coins(*self.nodes) # Generate stakeable outputs on both nodes nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32')) sync_blocks(nodes) nodes[1].proposetoaddress(1, nodes[1].getnewaddress('', 'bech32')) sync_blocks(nodes) # Current chain length doesn't overcome stake threshold, so # stakeable_balance == balance for i in range(self.num_nodes): self.check_node_balance(nodes[i], 10000, 10000) # Generate another block to overcome the stake threshold nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32')) sync_blocks(nodes) # Maturity check in action and we have one immature stake output self.check_node_balance(nodes[0], 10000, 9000) # Let's go further and propose yet another block nodes[0].proposetoaddress(1, nodes[0].getnewaddress('', 'bech32')) sync_blocks(nodes) # Now we have two immature stake outputs self.check_node_balance(nodes[0], 10000, 8000) # Generate two more blocks at another node nodes[1].proposetoaddress(2, nodes[1].getnewaddress('', 'bech32')) sync_blocks(nodes) # Thus all stake ouputs of the first node are mature self.check_node_balance(nodes[0], 10000, 10000) # Second node still have two immature stake outputs self.check_node_balance(nodes[1], 10000, 8000) # Try to send the block with immature stake block = build_block_with_immature_stake(self.nodes[1]) self.nodes[1].p2p.send_message(msg_block(block)) check_reject(self.nodes[1], b'bad-stake-immature', block.sha256)
def run_test(self): # Connect to node0 node0 = BaseNode() connections = [] connections.append( NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) NetworkThread().start() # Start up network handling in another thread node0.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock( self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append( CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 2100 deep for i in range(2100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) node1 = BaseNode() # connects to node1 connections.append( NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1)) node1.add_connection(connections[1]) node1.wait_for_verack() self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)]) node2 = BaseNode() # connects to node2 connections.append( NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2)) node2.add_connection(connections[2]) node2.wait_for_verack() # send header lists to all three nodes node0.send_header_for_blocks(self.blocks[0:2000]) node0.send_header_for_blocks(self.blocks[2000:]) node1.send_header_for_blocks(self.blocks[0:2000]) node1.send_header_for_blocks(self.blocks[2000:]) node2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(node0) self.assert_blockchain_height(self.nodes[0], 101) # Send all blocks to node1. All blocks will be accepted. for i in range(2202): node1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. node1.sync_with_ping(120) assert_equal( self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(node2) self.assert_blockchain_height(self.nodes[2], 101)
def test_nonnull_locators(self, test_node, inv_node): tip = int(self.nodes[0].getbestblockhash(), 16) # PART 1 # 1. Mine a block; expect inv announcements each time self.log.info("Part 1: headers don't start before sendheaders message...") for i in range(4): self.log.debug("Part 1.{}: starting...".format(i)) old_tip = tip tip = self.mine_blocks(1) inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_inv_announcement(inv=[tip]) # Try a few different responses; none should affect next announcement if i == 0: # first request the block test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # next try requesting header and block test_node.send_get_headers(locator=[old_tip], hashstop=tip) test_node.send_get_data([tip]) test_node.wait_for_block(tip) test_node.clear_block_announcements() # since we requested headers... elif i == 2: # this time announce own block via headers inv_node.clear_block_announcements() height = self.nodes[0].getblockcount() last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 new_block = create_block(tip, create_coinbase(height + 1), block_time) new_block.solve() test_node.send_header_for_blocks([new_block]) test_node.wait_for_getdata([new_block.sha256]) test_node.send_message(msg_block(new_block)) test_node.sync_with_ping() # make sure this block is processed wait_until(lambda: inv_node.block_announced, timeout=60, lock=mininode_lock) inv_node.clear_block_announcements() test_node.clear_block_announcements() self.log.info("Part 1: success!") self.log.info("Part 2: announce blocks with headers after sendheaders message...") # PART 2 # 2. Send a sendheaders message and test that headers announcements # commence and keep working. test_node.send_message(msg_sendheaders()) prev_tip = int(self.nodes[0].getbestblockhash(), 16) test_node.send_get_headers(locator=[prev_tip], hashstop=0) test_node.sync_with_ping() # Now that we've synced headers, headers announcements should work tip = self.mine_blocks(1) inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_headers_announcement(headers=[tip]) height = self.nodes[0].getblockcount() + 1 block_time += 10 # Advance far enough ahead for i in range(10): self.log.debug("Part 2.{}: starting...".format(i)) # Mine i blocks, and alternate announcing either via # inv (of tip) or via headers. After each, new blocks # mined by the node should successfully be announced # with block header, even though the blocks are never requested for j in range(2): self.log.debug("Part 2.{}.{}: starting...".format(i, j)) blocks = [] for b in range(i + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 if j == 0: # Announce via inv test_node.send_block_inv(tip) test_node.wait_for_getheaders() # Should have received a getheaders now test_node.send_header_for_blocks(blocks) # Test that duplicate inv's won't result in duplicate # getdata requests, or duplicate headers announcements [inv_node.send_block_inv(x.sha256) for x in blocks] test_node.wait_for_getdata([x.sha256 for x in blocks]) inv_node.sync_with_ping() else: # Announce via headers test_node.send_header_for_blocks(blocks) test_node.wait_for_getdata([x.sha256 for x in blocks]) # Test that duplicate headers won't result in duplicate # getdata requests (the check is further down) inv_node.send_header_for_blocks(blocks) inv_node.sync_with_ping() [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() inv_node.sync_with_ping() # This block should not be announced to the inv node (since it also # broadcast it) assert "inv" not in inv_node.last_message assert "headers" not in inv_node.last_message tip = self.mine_blocks(1) inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_headers_announcement(headers=[tip]) height += 1 block_time += 1 self.log.info("Part 2: success!") self.log.info("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...") # PART 3. Headers announcements can stop after large reorg, and resume after # getheaders or inv from peer. for j in range(2): self.log.debug("Part 3.{}: starting...".format(j)) # First try mining a reorg that can propagate with header announcement new_block_hashes = self.mine_reorg(length=7) tip = new_block_hashes[-1] inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_headers_announcement(headers=new_block_hashes) block_time += 8 # Mine a too-large reorg, which should be announced with a single inv new_block_hashes = self.mine_reorg(length=8) tip = new_block_hashes[-1] inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_inv_announcement(inv=[tip]) block_time += 9 fork_point = self.nodes[0].getblock("%064x" % new_block_hashes[0])["previousblockhash"] fork_point = int(fork_point, 16) # Use getblocks/getdata test_node.send_getblocks(locator=[fork_point]) test_node.check_last_inv_announcement(inv=new_block_hashes) test_node.send_get_data(new_block_hashes) test_node.wait_for_block(new_block_hashes[-1]) for i in range(3): self.log.debug("Part 3.{}.{}: starting...".format(j, i)) # Mine another block, still should get only an inv tip = self.mine_blocks(1) inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_inv_announcement(inv=[tip]) if i == 0: # Just get the data -- shouldn't cause headers announcements to resume test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # Send a getheaders message that shouldn't trigger headers announcements # to resume (best header sent will be too old) test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 2: # This time, try sending either a getheaders to trigger resumption # of headers announcements, or mine a new block and inv it, also # triggering resumption of headers announcements. test_node.send_get_data([tip]) test_node.wait_for_block(tip) if j == 0: test_node.send_get_headers(locator=[tip], hashstop=0) test_node.sync_with_ping() else: test_node.send_block_inv(tip) test_node.sync_with_ping() # New blocks should now be announced with header tip = self.mine_blocks(1) inv_node.check_last_inv_announcement(inv=[tip]) test_node.check_last_headers_announcement(headers=[tip]) self.log.info("Part 3: success!") self.log.info("Part 4: Testing direct fetch behavior...") tip = self.mine_blocks(1) height = self.nodes[0].getblockcount() + 1 last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 # Create 2 blocks. Send the blocks, then send the headers. blocks = [] for b in range(2): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 inv_node.send_message(msg_block(blocks[-1])) inv_node.sync_with_ping() # Make sure blocks are processed test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() # should not have received any getdata messages with mininode_lock: assert "getdata" not in test_node.last_message # This time, direct fetch should work blocks = [] for b in range(3): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME) [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() # Now announce a header that forks the last two blocks tip = blocks[0].sha256 height -= 2 blocks = [] # Create extra blocks for later for b in range(20): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 # Announcing one block on fork should not trigger direct fetch # (less work than tip) test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks[0:1]) test_node.sync_with_ping() with mininode_lock: assert "getdata" not in test_node.last_message # Announcing one more block on fork should trigger direct fetch for # both blocks (same work as tip) test_node.send_header_for_blocks(blocks[1:2]) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 16 more headers should trigger direct fetch for 14 more # blocks test_node.send_header_for_blocks(blocks[2:18]) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 1 more header should not trigger any response test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks[18:19]) test_node.sync_with_ping() with mininode_lock: assert "getdata" not in test_node.last_message self.log.info("Part 4: success!") # Now deliver all those blocks we announced. [test_node.send_message(msg_block(x)) for x in blocks] self.log.info("Part 5: Testing handling of unconnecting headers") # First we test that receipt of an unconnecting header doesn't prevent # chain sync. for i in range(10): self.log.debug("Part 5.{}: starting...".format(i)) test_node.last_message.pop("getdata", None) blocks = [] # Create two more blocks. for j in range(2): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 # Send the header of the second block -> this won't connect. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[1]]) test_node.wait_for_getheaders() test_node.send_header_for_blocks(blocks) test_node.wait_for_getdata([x.sha256 for x in blocks]) [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256) blocks = [] # Now we test that if we repeatedly don't send connecting headers, we # don't go into an infinite loop trying to get them to connect. MAX_UNCONNECTING_HEADERS = 10 for j in range(MAX_UNCONNECTING_HEADERS + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 for i in range(1, MAX_UNCONNECTING_HEADERS): # Send a header that doesn't connect, check that we get a getheaders. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[i]]) test_node.wait_for_getheaders() # Next header will connect, should re-set our count: test_node.send_header_for_blocks([blocks[0]]) # Remove the first two entries (blocks[1] would connect): blocks = blocks[2:] # Now try to see how many unconnecting headers we can send # before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS for i in range(5 * MAX_UNCONNECTING_HEADERS - 1): # Send a header that doesn't connect, check that we get a getheaders. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[i % len(blocks)]]) test_node.wait_for_getheaders() # Eventually this stops working. test_node.send_header_for_blocks([blocks[-1]]) # Should get disconnected test_node.wait_for_disconnect() self.log.info("Part 5: success!") # Finally, check that the inv node never received a getdata request, # throughout the test assert "getdata" not in inv_node.last_message
def run_test(self): block_count = 0 # Create a P2P connections node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) node2 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2) node2.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() node2.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) _, out, block_count = prepare_init_chain(self.chain, 101, 100, block_0=False, start_block=0, node=node0) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 # adding extra transactions to get different block hashes block2_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3_easier = self.chain.next_block(block_count, spend=out[0], extra_txns=2) block_count += 1 self.chain.set_tip(tip_block_num) block4_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=10) block_count += 1 # send two "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block2 hash: {block2_hard.hash}") self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add") self.log.info(f"hard block4 hash: {block4_hard.hash}") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block2_hard)) node1.send_message(msg_block(block4_hard)) # make sure we started validating blocks wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) self.log.info(f"easier block3 hash: {block3_easier.hash}") node2.send_message(msg_block(block3_easier)) self.nodes[0].waitforblockheight(102) assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash()) # now we can remove waiting status from blocks and finish their validation self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
def run_test(self): """Main test logic""" # Create a P2P connection to one of the nodes node0 = BaseNode() connections = [] connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) # Start up network handling in another thread. This needs to be called # after the P2P connections have been created. NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() # Generating a block on one of the nodes will get us out of IBD blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)] self.sync_all([self.nodes[0:1]]) # Notice above how we called an RPC by calling a method with the same # name on the node object. Notice also how we used a keyword argument # to specify a named RPC argument. Neither of those are defined on the # node object. Instead there's some __getattr__() magic going on under # the covers to dispatch unrecognised attribute calls to the RPC # interface. # Logs are nice. Do plenty of them. They can be used in place of comments for # breaking the test into sub-sections. self.log.info("Starting test!") self.log.info("Calling a custom function") custom_function() self.log.info("Calling a custom method") self.custom_method() self.log.info("Create some blocks") self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 height = 1 for i in range(10): # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our NodeConn connection node0.send_message(block_message) self.tip = block.sha256 blocks.append(self.tip) self.block_time += 1 height += 1 self.log.info("Wait for node1 to reach current tip (height 11) using RPC") self.nodes[1].waitforblockheight(11) self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) self.log.info("Add P2P connection to node2") node2 = BaseNode() connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2)) node2.add_connection(connections[1]) node2.wait_for_verack() self.log.info("Wait for node2 reach current tip. Test that it has propogated all the blocks to us") for block in blocks: getdata_request = msg_getdata() getdata_request.inv.append(CInv(2, block)) node2.send_message(getdata_request) # wait_until() will loop until a predicate condition is met. Use it to test properties of the # NodeConnCB objects. assert wait_until(lambda: sorted(blocks) == sorted(list(node2.block_receive_map.keys())), timeout=5) self.log.info("Check that each block was received only once") # The network thread uses a global lock on data access to the NodeConn objects when sending and receiving # messages. The test thread should acquire the global lock before accessing any NodeConn data to avoid locking # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate. with mininode_lock: for block in node2.block_receive_map.values(): assert_equal(block, 1)
def run_test(self): block_count = 0 # Create a P2P connection node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) node2 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2) node2.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() node2.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) out = [] for i in range(100): out.append(self.chain.get_spendable_output()) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 block2_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3_easier = self.chain.next_block(block_count, spend=out[0], extra_txns=2) block_count += 1 self.chain.set_tip(tip_block_num) block4_hard = self.chain.next_block(block_count, spend=out[0], extra_txns=10) block_count += 1 # send two "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block2 hash: {block2_hard.hash}") self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add") self.log.info(f"hard block4 hash: {block4_hard.hash}") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block2_hard)) node1.send_message(msg_block(block4_hard)) # make sure we started validating blocks wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) self.log.info(f"easier hash: {block3_easier.hash}") node2.send_message(msg_block(block3_easier)) self.nodes[0].waitforblockheight(102) assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash()) # now we can remove waiting status from blocks and finish their validation self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # now we want our precious block to be one of the harder blocks (block4_hard) self.nodes[0].preciousblock(block4_hard.hash) assert_equal(block4_hard.hash, self.nodes[0].getbestblockhash())
def run_test(self): block_count = 0 # Create a P2P connections node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) num_blocks = 150 for i in range(num_blocks): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) out = [] for i in range(num_blocks): out.append(self.chain.get_spendable_output()) self.log.info("waiting for block height 151 via rpc") self.nodes[0].waitforblockheight(num_blocks + 1) tip_block_num = block_count - 1 # left branch block2 = self.chain.next_block(block_count, spend=out[0:9], extra_txns=8) block_count += 1 node0.send_message(msg_block(block2)) self.log.info(f"block2 hash: {block2.hash}") self.nodes[0].waitforblockheight(num_blocks + 2) # send blocks 3,4 for parallel validation on left branch self.chain.set_tip(tip_block_num) block3 = self.chain.next_block(block_count, spend=out[10:19], extra_txns=10) block_count += 1 block4 = self.chain.next_block(block_count, spend=out[20:29], extra_txns=8) block_count += 1 # send two "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"block3 hash: {block3.hash}") self.log.info(f"block4 hash: {block4.hash}") self.nodes[0].waitaftervalidatingblock(block4.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block4.hash}, self.nodes[0], self.log) node1.send_message(msg_block(block3)) node1.send_message(msg_block(block4)) # make sure we started validating blocks wait_for_validating_blocks({block4.hash}, self.nodes[0], self.log) # right branch self.chain.set_tip(tip_block_num) block5 = self.chain.next_block(block_count) # Add some txns from block2 & block3 to block5, just to check that they get # filtered from the mempool and not re-added block5_duplicated_txns = block3.vtx[1:3] + block2.vtx[1:3] self.chain.update_block(block_count, block5_duplicated_txns) block_count += 1 node0.send_message(msg_block(block5)) self.log.info(f"block5 hash: {block5.hash}") # and two blocks to extend second branch to cause reorg # - they must be sent from the same node as otherwise they will be # rejected with "prev block not found" as we don't wait for the first # block to arrive so there is a race condition which block is seen # first when using multiple connections block6 = self.chain.next_block(block_count) node0.send_message(msg_block(block6)) self.log.info(f"block6 hash: {block6.hash}") block_count += 1 block7 = self.chain.next_block(block_count) node0.send_message(msg_block(block7)) self.log.info(f"block7 hash: {block7.hash}") block_count += 1 self.nodes[0].waitforblockheight(num_blocks + 4) assert_equal(block7.hash, self.nodes[0].getbestblockhash()) self.log.info( "releasing wait status on parallel blocks to finish their validation" ) self.nodes[0].waitaftervalidatingblock(block4.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # block that arrived last on competing chain should be active assert_equal(block7.hash, self.nodes[0].getbestblockhash()) # make sure that transactions from block2 and 3 (except coinbase, and those also # in block 5) are in mempool not_expected_in_mempool = set() for txn in block5_duplicated_txns: not_expected_in_mempool.add(txn.hash) expected_in_mempool = set() for txn in block2.vtx[1:] + block3.vtx[1:]: expected_in_mempool.add(txn.hash) expected_in_mempool = expected_in_mempool.difference( not_expected_in_mempool) mempool = self.nodes[0].getrawmempool() assert_equal(expected_in_mempool, set(mempool))
def run_test(self): """Main test logic""" self.setup_stake_coins(*self.nodes) # Create P2P connections will wait for a verack to make sure the connection is fully up self.nodes[0].add_p2p_connection(BaseNode()) # Generating a block on one of the nodes will get us out of IBD blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)] self.sync_all([self.nodes[0:2]]) # Notice above how we called an RPC by calling a method with the same # name on the node object. Notice also how we used a keyword argument # to specify a named RPC argument. Neither of those are defined on the # node object. Instead there's some __getattr__() magic going on under # the covers to dispatch unrecognised attribute calls to the RPC # interface. # Logs are nice. Do plenty of them. They can be used in place of comments for # breaking the test into sub-sections. self.log.info("Starting test!") self.log.info("Calling a custom function") custom_function() self.log.info("Calling a custom method") self.custom_method() self.log.info("Create some blocks") self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 height = self.nodes[0].getblockcount() snapshot_meta = get_tip_snapshot_meta(self.nodes[0]) stakes = self.nodes[0].listunspent() for stake in stakes: # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. coinbase = sign_coinbase(self.nodes[0], create_coinbase(height, stake, snapshot_meta.hash)) block = create_block(self.tip, coinbase, self.block_time) # Wait until the active chain picks up the previous block wait_until(lambda: self.nodes[0].getblockcount() == height, timeout=5) snapshot_meta = update_snapshot_with_tx(self.nodes[0], snapshot_meta, height + 1, coinbase) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface self.nodes[0].p2p.send_message(block_message) self.tip = block.sha256 blocks.append(self.tip) self.block_time += 1 height += 1 self.log.info("Wait for node1 to reach current tip (height %d) using RPC" % height) self.nodes[1].waitforblockheight(height) self.log.info("Connect node2 and node1") connect_nodes(self.nodes[1], 2) self.log.info("Add P2P connection to node2") self.nodes[0].disconnect_p2ps() self.nodes[2].add_p2p_connection(BaseNode()) self.log.info("Wait for node2 reach current tip. Test that it has propagated all the blocks to us") self.nodes[2].waitforblockheight(height) getdata_request = msg_getdata() for block in blocks: getdata_request.inv.append(CInv(2, block)) self.nodes[2].p2p.send_message(getdata_request) # wait_until() will loop until a predicate condition is met. Use it to test properties of the # P2PInterface objects. wait_until(lambda: sorted(blocks) == sorted(list(self.nodes[2].p2p.block_receive_map.keys())), timeout=5, lock=mininode_lock) self.log.info("Check that each block was received only once") # The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving # messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking # and synchronization issues. Note wait_until() acquires this global lock when testing the predicate. with mininode_lock: for block in self.nodes[2].p2p.block_receive_map.values(): assert_equal(block, 1)
def run_test(self): node0 = NodeConnCB() connections = [] connections.append( NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) NetworkThread().start() # Start up network handling in another thread # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() self.log.info("Mining %d blocks", CLTV_HEIGHT - 2) self.coinbase_blocks = self.nodes[0].generate(CLTV_HEIGHT - 2) self.nodeaddress = self.nodes[0].getnewaddress() self.log.info( "Test that an invalid-according-to-CLTV transaction can still appear in a block" ) spendtx = create_transaction(self.nodes[0], self.coinbase_blocks[0], self.nodeaddress, 1.0) cltv_invalidate(spendtx) spendtx.rehash() tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1), block_time) block.nVersion = 3 block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() node0.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 4") tip = block.sha256 block_time += 1 block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time) block.nVersion = 3 block.solve() node0.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock) with mininode_lock: assert_equal(node0.last_message["reject"].code, REJECT_OBSOLETE) assert_equal(node0.last_message["reject"].reason, b'bad-version(0x00000003)') assert_equal(node0.last_message["reject"].data, block.sha256) del node0.last_message["reject"] self.log.info( "Test that invalid-according-to-cltv transactions cannot appear in a block" ) block.nVersion = 4 spendtx = create_transaction(self.nodes[0], self.coinbase_blocks[1], self.nodeaddress, 1.0) cltv_invalidate(spendtx) spendtx.rehash() # First we show that this tx is valid except for CLTV by getting it # accepted to the mempool (which we can achieve with # -promiscuousmempoolflags). node0.send_and_ping(msg_tx(spendtx)) assert spendtx.hash in self.nodes[0].getrawmempool() # Now we verify that a block with this transaction is invalid. block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() node0.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock) with mininode_lock: assert node0.last_message["reject"].code in [ REJECT_INVALID, REJECT_NONSTANDARD ] assert_equal(node0.last_message["reject"].data, block.sha256) if node0.last_message["reject"].code == REJECT_INVALID: # Generic rejection when a block is invalid assert_equal(node0.last_message["reject"].reason, b'block-validation-failed') else: assert b'Negative locktime' in node0.last_message[ "reject"].reason self.log.info( "Test that a version 4 block with a valid-according-to-CLTV transaction is accepted" ) spendtx = cltv_validate(self.nodes[0], spendtx, CLTV_HEIGHT - 1) spendtx.rehash() block.vtx.pop(1) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() node0.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
def run_test(self): MAX_FORK_DISTANCE = 10 MIN_FORK_LENGTH = 3 MIN_FORK_DIFFERENCE = 1 args= [f"-safemodemaxforkdistance={MAX_FORK_DISTANCE}", f"-safemodeminforklength={MIN_FORK_LENGTH}", f"-safemodeminblockdifference={MIN_FORK_DIFFERENCE}",] with self.run_node_with_connections("Preparation", 0, args, 2) as (conn1, conn2): last_block_time = 0 conn1.rpc.generate(1) branch_1_root, last_block_time = make_block(conn1, last_block_time = last_block_time) branch_1_blocks = [branch_1_root] for _ in range(MAX_FORK_DISTANCE): new_block, last_block_time = make_block(conn1, branch_1_blocks[-1], last_block_time = last_block_time) branch_1_blocks.append(new_block) branch_2_root, last_block_time = make_block(conn2, makeValid=False, last_block_time = last_block_time) branch_2_blocks = [branch_2_root] for _ in range(MAX_FORK_DISTANCE + MIN_FORK_DIFFERENCE + 1): new_block, last_block_time = make_block(conn2, branch_2_blocks[-1], last_block_time = last_block_time) branch_2_blocks.append(new_block) # send first branch that should be active tip send_by_headers(conn1, branch_1_blocks, do_send_blocks=True) wait_for_tip(conn1, branch_1_blocks[-1].hash) # send second branch with more POW send_by_headers(conn2, branch_2_blocks, do_send_blocks=False) wait_for_tip(conn1, branch_1_blocks[-1].hash) wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "headers-only") # we should not be in safe mode (distance to the fork is too large) assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"] conn1.rpc.invalidateblock(branch_1_blocks[-1].hash) wait_for_tip(conn1, branch_1_blocks[-2].hash) # here we have shortened distance from the active tip to the fork root so the safe mode should be activated assert conn1.rpc.getsafemodeinfo()["safemodeenabled"] conn1.rpc.reconsiderblock(branch_1_blocks[-1].hash) wait_for_tip(conn1, branch_1_blocks[-1].hash) # returning to the old state (distance to the fork is too large) assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"] # From time to time this test can run faster than expected and # the older blocks for batch 2 headers are not yet requested. # In that case they will be rejected due to being too far away # form the tip. In that case we need to send them again once they # are requested. def on_getdata(conn, msg): for i in msg.inv: if i.type != 2: # MSG_BLOCK error_msg = f"Unexpected data requested {i}" self.log.error(error_msg) raise NotImplementedError(error_msg) for block in branch_2_blocks: if int(block.hash, 16) == i.hash: conn.send_message(msg_block(block)) break conn2.cb.on_getdata = on_getdata # send sencond branch full blocks for block in branch_2_blocks: conn2.send_message(msg_block(block)) tips = conn2.rpc.getchaintips() # second branch should now be invalid wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "invalid") wait_for_tip(conn1, branch_1_blocks[-1].hash) # we should not be in safe mode assert not conn1.rpc.getsafemodeinfo()["safemodeenabled"]
def run_test(self): with self.run_node_with_connections( "Eviction order test; fill the memppol over its size and see what txs will be evicted.", 0, [ "-blockmintxfee=0.00001", # 1 satoshi/byte "-minrelaytxfee=0", "-maxmempool=300MB", "-maxmempoolsizedisk=0", "-genesisactivationheight=1", '-maxstdtxvalidationduration=5000', '-maxnonstdtxvalidationduration=5001', '-maxstackmemoryusageconsensus=5MB', '-maxstackmemoryusagepolicy=5MB', '-maxscriptsizepolicy=5MB', '-checkmempool=0', ], number_of_connections=1) as (conn, ): mining_fee = 1.01 # in satoshi per byte # create block with coinbase coinbase1 = create_coinbase(height=1) first_block = create_block(int(conn.rpc.getbestblockhash(), 16), coinbase=coinbase1) first_block.solve() conn.send_message(msg_block(first_block)) wait_until(lambda: conn.rpc.getbestblockhash() == first_block.hash, check_interval=1) coinbase2 = create_coinbase(height=2) second_block = create_block(int(conn.rpc.getbestblockhash(), 16), coinbase=coinbase2) second_block.solve() conn.send_message(msg_block(second_block)) wait_until( lambda: conn.rpc.getbestblockhash() == second_block.hash, check_interval=1) #mature the coinbase conn.rpc.generate(100) funding_tx = self.create_tx([(coinbase1, 0), (coinbase2, 0)], 16, mining_fee, 0) conn.send_message(msg_tx(funding_tx)) check_mempool_equals(conn.rpc, [funding_tx]) conn.rpc.generate(1) # (funding_tx, 0) (funding_tx, 1) (funding_tx, 2) (funding_tx, 3) (funding_tx, 4-14) (funding_tx, 15) # --------------------------------------------------------------------------------------------------------------------- # group1tx1 lowPaying3 tx1 tx3 (long chain of (chain of high # | | | | high paying txs) paying txs used to # group1tx2 lowPaying4 tx2 lowPaying5 to fill the mempool) push low paying txs # / \ out of mempools) # group1paying group2tx1 # | | # lowPaying1 group2tx2 # | # group2paying # | # lowPaying2 group1tx1 = self.create_tx([(funding_tx, 0)], noutput=1, feerate=0, totalSize=ONE_MEGABYTE) group1tx2 = self.create_tx([(group1tx1, 0)], noutput=2, feerate=0, totalSize=ONE_MEGABYTE) group1paying = self.create_tx( [(group1tx2, 0)], noutput=1, feerate=1.4, totalSize=ONE_MEGABYTE, size_of_nonpayin_txs=self.tx_size(group1tx1) + self.tx_size(group1tx2)) group2tx1 = self.create_tx([(group1tx2, 1)], noutput=1, feerate=0, totalSize=ONE_MEGABYTE) group2tx2 = self.create_tx([(group2tx1, 0)], noutput=1, feerate=0, totalSize=ONE_MEGABYTE) group2paying = self.create_tx( [(group2tx2, 0)], noutput=1, feerate=1.6, totalSize=ONE_MEGABYTE, size_of_nonpayin_txs=self.tx_size(group2tx1) + self.tx_size(group2tx2)) tx1 = self.create_tx([(funding_tx, 2)], noutput=1, feerate=1.1, totalSize=ONE_MEGABYTE) tx2 = self.create_tx([(tx1, 0)], noutput=1, feerate=1.8, totalSize=ONE_MEGABYTE) tx3 = self.create_tx([(funding_tx, 3)], noutput=1, feerate=1.1, totalSize=ONE_MEGABYTE) lowPaying1 = self.create_tx([(group1paying, 0)], noutput=1, feerate=0.1, totalSize=ONE_MEGABYTE) lowPaying2 = self.create_tx([(group2paying, 0)], noutput=1, feerate=0.2, totalSize=ONE_MEGABYTE) lowPaying3 = self.create_tx([(funding_tx, 1)], noutput=1, feerate=0.3, totalSize=ONE_MEGABYTE) lowPaying4 = self.create_tx([(lowPaying3, 0)], noutput=1, feerate=0.4, totalSize=ONE_MEGABYTE) lowPaying5 = self.create_tx([(tx3, 0)], noutput=1, feerate=0.5, totalSize=ONE_MEGABYTE) primaryMempoolTxs = [ group1tx1, group1tx2, group1paying, group2tx1, group2tx2, group2paying, tx1, tx2, tx3 ] secondaryMempoolTxs = [ lowPaying1, lowPaying2, lowPaying3, lowPaying4, lowPaying5 ] for tx in primaryMempoolTxs + secondaryMempoolTxs: conn.send_message(msg_tx(tx)) check_mempool_equals(conn.rpc, primaryMempoolTxs + secondaryMempoolTxs) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == len( primaryMempoolTxs) + 1) txs_in_mempool = set(primaryMempoolTxs + secondaryMempoolTxs) outpoints_to_spend = [(funding_tx, n) for n in range(4, 15)] while len(txs_in_mempool) < 299: tx = self.create_tx([ outpoints_to_spend.pop(0), ], noutput=2, feerate=5, totalSize=ONE_MEGABYTE) outpoints_to_spend.append((tx, 0)) outpoints_to_spend.append((tx, 1)) conn.send_message(msg_tx(tx)) txs_in_mempool.add(tx) check_mempool_equals(conn.rpc, txs_in_mempool, timeout=600, check_interval=2) eviction_order = [ lowPaying1, lowPaying2, lowPaying4, lowPaying3, lowPaying5, tx3, group2paying, group2tx2, group2tx1, group1paying, group1tx2, group1tx1, tx2, tx1 ] conn.rpc.log.info(f"lowPaying1 = {lowPaying1.hash}") conn.rpc.log.info(f"lowPaying2 = {lowPaying2.hash}") conn.rpc.log.info(f"lowPaying3 = {lowPaying3.hash}") conn.rpc.log.info(f"lowPaying4 = {lowPaying4.hash}") conn.rpc.log.info(f"lowPaying5 = {lowPaying5.hash}") conn.rpc.log.info(f"tx1 = {tx1.hash}") conn.rpc.log.info(f"tx2 = {tx2.hash}") conn.rpc.log.info(f"tx3 = {tx3.hash}") conn.rpc.log.info(f"group2paying = {group2paying.hash}") conn.rpc.log.info(f"group2tx2 = {group2tx2.hash}") conn.rpc.log.info(f"group2tx1 = {group2tx1.hash}") conn.rpc.log.info(f"group1paying = {group1paying.hash}") conn.rpc.log.info(f"group1tx2 = {group1tx2.hash}") conn.rpc.log.info(f"group1tx1 = {group1tx1.hash}") outpoint_to_spend = (funding_tx, 15) for evicting in eviction_order: tx = self.create_tx([ outpoint_to_spend, ], noutput=1, feerate=30, totalSize=ONE_MEGABYTE) outpoint_to_spend = (tx, 0) conn.send_message(msg_tx(tx)) txs_in_mempool.add(tx) txs_in_mempool.remove(evicting) check_mempool_equals(conn.rpc, txs_in_mempool, check_interval=0.5, timeout=60) # when there are still some secondary mempool transaction in the mempool if len(txs_in_mempool & set(secondaryMempoolTxs)) != 0: # the mempoolminfee should not exceed blockmintxfee assert conn.rpc.getmempoolinfo( )['mempoolminfee'] <= conn.rpc.getsettings( )['blockmintxfee'] with self.run_node_with_connections( "Restart the node with using the disk for storing transactions.", 0, [ "-blockmintxfee=0.00001", # 1 satoshi/byte "-minrelaytxfee=0", "-maxmempool=300MB", "-maxmempoolsizedisk=10MB", "-genesisactivationheight=1", '-maxstdtxvalidationduration=5000', '-maxnonstdtxvalidationduration=5001', '-maxstackmemoryusageconsensus=5MB', '-maxstackmemoryusagepolicy=5MB', '-maxscriptsizepolicy=5MB', '-checkmempool=0', ], number_of_connections=1) as (conn, ): # check that we have all txs in the mempool check_mempool_equals(conn.rpc, txs_in_mempool, check_interval=1, timeout=60) # check that we are not using the tx database assert conn.rpc.getmempoolinfo()['usagedisk'] == 0 #now we have room for some more txs for _ in range(3): tx = self.create_tx([ outpoint_to_spend, ], noutput=1, feerate=1, totalSize=ONE_MEGABYTE) outpoint_to_spend = (tx, 0) conn.send_message(msg_tx(tx)) txs_in_mempool.add(tx) check_mempool_equals(conn.rpc, txs_in_mempool, check_interval=0.5, timeout=60) # make sure that we are using the tx database now assert conn.rpc.getmempoolinfo()['usagedisk'] != 0 with self.run_node_with_connections( "Restart the node once again to see if transaction were stored in the db.", 0, [ "-blockmintxfee=0.00001", # 1 satoshi/byte "-minrelaytxfee=0", "-maxmempool=300MB", "-maxmempoolsizedisk=10MB", "-genesisactivationheight=1", '-maxstdtxvalidationduration=5000', '-maxnonstdtxvalidationduration=5001', '-maxstackmemoryusageconsensus=5MB', '-maxstackmemoryusagepolicy=5MB', '-maxscriptsizepolicy=5MB', '-checkmempool=0', ], number_of_connections=1) as (conn, ): # check that we have all txs in the mempool check_mempool_equals(conn.rpc, txs_in_mempool, check_interval=1, timeout=60) # make sure that we are using the tx database assert conn.rpc.getmempoolinfo()['usagedisk'] != 0
def send_block(self, block): self.send_message(msg_block(block))
def run_test(self): block_count = 0 # Create a P2P connections node0 = NodeConnCB() connection0 = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection0) node1 = NodeConnCB() connection1 = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection1) # *** Prepare node connection for early announcements testing node2 = NodeConnCB() node2.add_connection( NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2)) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() # *** Activate early announcement functionality for this connection # After this point the early announcements are not received yet - # we still need to set latest announced block (CNode::pindexBestKnownBlock) # which is set for e.g. by calling best headers message with locator # set to non-null node2.wait_for_verack() node2.send_message(msg_sendcmpct(announce=True)) self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) _, outs, block_count = prepare_init_chain(self.chain, 101, 1, block_0=False, start_block=0, node=node0) out = outs[0] self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 # adding extra transactions to get different block hashes block2_hard = self.chain.next_block(block_count, spend=out, extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3_easier = self.chain.next_block(block_count, spend=out, extra_txns=2) block_count += 1 self.chain.set_tip(tip_block_num) block4_hard = self.chain.next_block(block_count, spend=out, extra_txns=10) block_count += 1 # send three "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block2 hash: {block2_hard.hash}") self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add") self.log.info(f"hard block4 hash: {block4_hard.hash}") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) # *** Complete early announcement setup by sending getheaders message # with a non-null locator (pointing to the last block that we know # of on python side - we claim that we know of all the blocks that # bitcoind node knows of) # # We also set on_cmpctblock handler as early announced blocks are # announced via compact block messages instead of inv messages node2.send_and_ping( msg_getheaders( locator_have=[int(self.nodes[0].getbestblockhash(), 16)])) receivedAnnouncement = False waiting_for_announcement_block_hash = block2_hard.sha256 def on_cmpctblock(conn, message): nonlocal receivedAnnouncement message.header_and_shortids.header.calc_sha256() if message.header_and_shortids.header.sha256 == waiting_for_announcement_block_hash: receivedAnnouncement = True node2.on_cmpctblock = on_cmpctblock # send one block via p2p and one via rpc node0.send_message(msg_block(block2_hard)) # *** make sure that we receive announcement of the block before it has # been validated wait_until(lambda: receivedAnnouncement) # making rpc call submitblock in a separate thread because waitaftervalidation is blocking # the return of submitblock submitblock_thread = threading.Thread(target=self.nodes[0].submitblock, args=(ToHex(block4_hard), )) submitblock_thread.start() # because self.nodes[0] rpc is blocked we use another rpc client rpc_client = get_rpc_proxy(rpc_url( get_datadir_path(self.options.tmpdir, 0), 0), 0, coveragedir=self.options.coveragedir) wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, rpc_client, self.log) # *** prepare to intercept block3_easier announcement - it will not be # announced before validation is complete as early announcement is # limited to announcing one block per height (siblings are ignored) # but after validation is complete we should still get the announcing # compact block message receivedAnnouncement = False waiting_for_announcement_block_hash = block3_easier.sha256 self.log.info(f"easy block3 hash: {block3_easier.hash}") node1.send_message(msg_block(block3_easier)) # *** Make sure that we receive compact block announcement of the block # after the validation is complete even though it was not the first # block that was received by bitcoind node. # # Also make sure that we receive inv announcement of the block after # the validation is complete by the nodes that are not using early # announcement functionality. wait_until(lambda: receivedAnnouncement) node0.wait_for_inv([CInv(2, block3_easier.sha256) ]) # 2 == GetDataMsg::MSG_BLOCK # node 1 was the sender but receives inv for block non the less # (with early announcement that's not the case - sender does not receive the announcement) node1.wait_for_inv([CInv(2, block3_easier.sha256) ]) # 2 == GetDataMsg::MSG_BLOCK rpc_client.waitforblockheight(102) assert_equal(block3_easier.hash, rpc_client.getbestblockhash()) # now we can remove waiting status from blocks and finish their validation rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove") rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove") submitblock_thread.join() # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
def run_test(self): block_count = 0 # Create a P2P connections node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) out = [] for i in range(100): out.append(self.chain.get_spendable_output()) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) # wait till validation of block or blocks finishes node0.sync_with_ping() block1 = self.chain.next_block(block_count, spend=out[0], extra_txns=8) block_count += 1 # send block but block him at validation point self.nodes[0].waitaftervalidatingblock(block1.hash, "add") node0.send_message(msg_block(block1)) self.log.info(f"block1 hash: {block1.hash}") # make sure block hash is in waiting list wait_for_waiting_blocks({block1.hash}, self.nodes[0], self.log) # send child block block2 = self.chain.next_block(block_count, spend=out[1], extra_txns=10) block_count += 1 node0.send_message(msg_block(block2)) self.log.info(f"block2 hash: {block2.hash}") def wait_for_log(): line_text = block2.hash + " will not be considered by the current" for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if line_text in line: self.log.info("Found line: %s", line) return True return False wait_until(wait_for_log) self.nodes[0].waitaftervalidatingblock(block1.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # block that arrived last on competing chain should be active assert_equal(block2.hash, self.nodes[0].getbestblockhash())
def run_test(self): block_count = 0 node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) node2 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node2) node2.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() node2.wait_for_verack() self.log.info("Sending blocks to get spendable output") self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() node0.send_message(msg_block(block)) out = [] for i in range(100): out.append(self.chain.get_spendable_output()) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=1) block2_count = block_count block_count += 1 self.log.info(f"blockA hash: {block2.hash}") node0.send_message(msg_block(block2)) self.nodes[0].waitforblockheight(102) block3_hard = self.chain.next_block(block_count, spend=out[1], extra_txns=8) block_count += 1 self.chain.set_tip(block2_count) block4_easier = self.chain.next_block(block_count, spend=out[1], extra_txns=2) block_count += 1 self.chain.set_tip(block2_count) block5_hard = self.chain.next_block(block_count, spend=out[1], extra_txns=10) block_count += 1 # send two "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block3 hash: {block3_hard.hash}") self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "add") self.log.info(f"hard block5 hash: {block5_hard.hash}") self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block3_hard.hash, block5_hard.hash}, self.nodes[0], self.log) self.log.info( "Sending blocks 3,4,5 on branch 2 for parallel validation") node0.send_message(msg_block(block3_hard)) node2.send_message(msg_block(block5_hard)) # make sure we started validating blocks wait_for_validating_blocks({block3_hard.hash, block5_hard.hash}, self.nodes[0], self.log) self.log.info(f"easier hash: {block4_easier.hash}") node1.send_message(msg_block(block4_easier)) self.nodes[0].waitforblockheight(103) # Because 4 is easy to validate it will be validated first and set as active tip assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash()) # now we can remove waiting status from blocks and finish their validation self.nodes[0].waitaftervalidatingblock(block3_hard.hash, "remove") self.nodes[0].waitaftervalidatingblock(block5_hard.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash()) self.log.info("Sending blocks 6,7,8 on competing chain to cause reorg") self.chain.set_tip(tip_block_num) block6 = self.chain.next_block(block_count, spend=out[0], extra_txns=2) block_count += 1 self.log.info(f"block6 hash: {block6.hash}") node0.send_message(msg_block(block6)) block7 = self.chain.next_block(block_count) block_count += 1 self.log.info(f"block7: {block7.hash}") node0.send_message(msg_block(block7)) # send one to cause reorg this should be active block8 = self.chain.next_block(block_count) block_count += 1 self.log.info(f"block8: {block8.hash}") node0.send_message(msg_block(block8)) self.nodes[0].waitforblockheight(104) assert_equal(block8.hash, self.nodes[0].getbestblockhash()) self.log.info( "Invalidating block7 on competing chain to reorg to first branch again" ) self.log.info(f"invalidating hash {block7.hash}") self.nodes[0].invalidateblock(block7.hash) #after invalidating, active block should be the one first validated on first branch assert_equal(block4_easier.hash, self.nodes[0].getbestblockhash())
def run_test(self): test_node = NodeConnCB() connections = [] connections.append( NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) test_node.add_connection(connections[0]) NetworkThread().start() test_node.wait_for_verack() starting_height = 3 self.nodes[0].generate(starting_height) # Create block with P2SH output and send it to node. # It should be validated and accepted. block = self.make_block_withP2SH_coinbase() test_node.send_message(msg_block(block)) test_node.sync_with_ping() # check if block was accepted assert_equal(self.nodes[0].getbestblockhash(), block.hash) # submitblock with P2SH in coinbase tx (not included in blockchain) block = self.make_block_withP2SH_coinbase() block.solve() assert_raises_rpc_error(-26, "bad-txns-vout-p2sh", self.nodes[0].submitblock, ToHex(block)) # verifyblockcandidate with P2SH in coinbase tx (not included in blockchain) assert_raises_rpc_error(-26, "bad-txns-vout-p2sh", self.nodes[0].verifyblockcandidate, ToHex(block)) # submitblock without P2SH in coinbase tx (included in blockchain) hashPrev = int(self.nodes[0].getbestblockhash(), 16) ctx = create_coinbase(self.nodes[0].getblockcount() + 1) block2 = create_block(hashPrev, ctx) block2.solve() self.nodes[0].submitblock(ToHex(block2)) assert_equal(block2.hash, self.nodes[0].getbestblockhash()) # submit block with: submitminingsolution # Add P2SH to coinbase output - should be rejected candidate = self.nodes[0].getminingcandidate(False) block, ctx = create_block_from_candidate(candidate, False) coinbase_tx = create_coinbase_P2SH(self.nodes[0].getblockcount() + 1, example_script_hash) # submitminingsolution with P2SH in coinbase tx - should be denied. assert_raises_rpc_error( -26, "bad-txns-vout-p2sh", self.nodes[0].submitminingsolution, { 'id': candidate['id'], 'nonce': block.nNonce, 'coinbase': '{}'.format(ToHex(coinbase_tx)) }) # submitminingsolution without P2SH in coinbase - should be accepted candidate = self.nodes[0].getminingcandidate(False) block, ctx = create_block_from_candidate(candidate, False) result = self.nodes[0].submitminingsolution({ 'id': candidate['id'], 'nonce': block.nNonce, 'coinbase': '{}'.format(ToHex(ctx)) }) assert_equal(result, True) assert_equal(block.hash, self.nodes[0].getbestblockhash()) # generatetoaddress with nonP2SH address height_before = self.nodes[0].getblockcount() address = self.nodes[0].getnewaddress() self.nodes[0].generatetoaddress(1, address) height_after = self.nodes[0].getblockcount() assert_equal(height_before + 1, height_after) # generatetoaddress with P2SH address (example for regtest: 2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc) assert_raises_rpc_error(-26, "bad-txns-vout-p2sh", self.nodes[0].generatetoaddress, 1, '2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc')
def run_test(self): # Setup the p2p connections and start up the network thread. test_node = TestNode() # connects to node0 (not whitelisted) white_node = TestNode() # connects to node1 (whitelisted) connections = [] connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node)) test_node.add_connection(connections[0]) white_node.add_connection(connections[1]) NetworkThread().start() # Start up network handling in another thread # Test logic begins here test_node.wait_for_verack() white_node.wait_for_verack() # 1. Have both nodes mine a block (leave IBD) [ n.generate(1) for n in self.nodes ] tips = [ int ("0x" + n.getbestblockhash() + "L", 0) for n in self.nodes ] # 2. Send one block that builds on each tip. # This should be accepted. blocks_h2 = [] # the height 2 blocks on each node's chain block_time = time.time() + 1 for i in xrange(2): blocks_h2.append(create_block(tips[i], create_coinbase(), block_time)) blocks_h2[i].solve() block_time += 1 test_node.send_message(msg_block(blocks_h2[0])) white_node.send_message(msg_block(blocks_h2[1])) [ x.sync_with_ping() for x in [test_node, white_node] ] assert_equal(self.nodes[0].getblockcount(), 2) assert_equal(self.nodes[1].getblockcount(), 2) print "First height 2 block accepted by both nodes" # 3. Send another block that builds on the original tip. blocks_h2f = [] # Blocks at height 2 that fork off the main chain for i in xrange(2): blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1)) blocks_h2f[i].solve() test_node.send_message(msg_block(blocks_h2f[0])) white_node.send_message(msg_block(blocks_h2f[1])) [ x.sync_with_ping() for x in [test_node, white_node] ] for x in self.nodes[0].getchaintips(): if x['hash'] == blocks_h2f[0].hash: assert_equal(x['status'], "headers-only") for x in self.nodes[1].getchaintips(): if x['hash'] == blocks_h2f[1].hash: assert_equal(x['status'], "valid-headers") print "Second height 2 block accepted only from whitelisted peer" # 4. Now send another block that builds on the forking chain. blocks_h3 = [] for i in xrange(2): blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1)) blocks_h3[i].solve() test_node.send_message(msg_block(blocks_h3[0])) white_node.send_message(msg_block(blocks_h3[1])) [ x.sync_with_ping() for x in [test_node, white_node] ] # Since the earlier block was not processed by node0, the new block # can't be fully validated. for x in self.nodes[0].getchaintips(): if x['hash'] == blocks_h3[0].hash: assert_equal(x['status'], "headers-only") # But this block should be accepted by node0 since it has more work. try: self.nodes[0].getblock(blocks_h3[0].hash) print "Unrequested more-work block accepted from non-whitelisted peer" except: raise AssertionError("Unrequested more work block was not processed") # Node1 should have accepted and reorged. assert_equal(self.nodes[1].getblockcount(), 3) print "Successfully reorged to length 3 chain from whitelisted peer" # 4b. Now mine 288 more blocks and deliver; all should be processed but # the last (height-too-high) on node0. Node1 should process the tip if # we give it the headers chain leading to the tip. tips = blocks_h3 headers_message = msg_headers() all_blocks = [] # node0's blocks for j in xrange(2): for i in xrange(288): next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1) next_block.solve() if j==0: test_node.send_message(msg_block(next_block)) all_blocks.append(next_block) else: headers_message.headers.append(CBlockHeader(next_block)) tips[j] = next_block time.sleep(2) for x in all_blocks: try: self.nodes[0].getblock(x.hash) if x == all_blocks[287]: raise AssertionError("Unrequested block too far-ahead should have been ignored") except: if x == all_blocks[287]: print "Unrequested block too far-ahead not processed" else: raise AssertionError("Unrequested block with more work should have been accepted") headers_message.headers.pop() # Ensure the last block is unrequested white_node.send_message(headers_message) # Send headers leading to tip white_node.send_message(msg_block(tips[1])) # Now deliver the tip try: white_node.sync_with_ping() self.nodes[1].getblock(tips[1].hash) print "Unrequested block far ahead of tip accepted from whitelisted peer" except: raise AssertionError("Unrequested block from whitelisted peer not accepted") # 5. Test handling of unrequested block on the node that didn't process # Should still not be processed (even though it has a child that has more # work). test_node.send_message(msg_block(blocks_h2f[0])) # Here, if the sleep is too short, the test could falsely succeed (if the # node hasn't processed the block by the time the sleep returns, and then # the node processes it and incorrectly advances the tip). # But this would be caught later on, when we verify that an inv triggers # a getdata request for this block. test_node.sync_with_ping() assert_equal(self.nodes[0].getblockcount(), 2) print "Unrequested block that would complete more-work chain was ignored" # 6. Try to get node to request the missing block. # Poke the node with an inv for block at height 3 and see if that # triggers a getdata on block 2 (it should if block 2 is missing). with mininode_lock: # Clear state so we can check the getdata request test_node.last_getdata = None test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)])) test_node.sync_with_ping() with mininode_lock: getdata = test_node.last_getdata # Check that the getdata includes the right block assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256) print "Inv at tip triggered getdata for unprocessed block" # 7. Send the missing block for the third time (now it is requested) test_node.send_message(msg_block(blocks_h2f[0])) test_node.sync_with_ping() assert_equal(self.nodes[0].getblockcount(), 290) print "Successfully reorged to longer chain from non-whitelisted peer" [ c.disconnect_node() for c in connections ]
def run_test(self): with self.run_node_with_connections("Preparation", 0, None, 2) as (conn1, conn2): last_block_time = 0 conn1.rpc.generate(1) branch_1_root, last_block_time = make_block( conn1, last_block_time=last_block_time) branch_1_blocks = [branch_1_root] for _ in range(SAFE_MODE_MAX_FORK_DISTANCE): new_block, last_block_time = make_block( conn1, branch_1_blocks[-1], last_block_time=last_block_time) branch_1_blocks.append(new_block) branch_2_root, last_block_time = make_block( conn2, makeValid=False, last_block_time=last_block_time) branch_2_blocks = [branch_2_root] for _ in range(SAFE_MODE_MAX_FORK_DISTANCE + SAFE_MODE_MIN_POW_DIFFERENCE + 1): new_block, last_block_time = make_block( conn2, branch_2_blocks[-1], last_block_time=last_block_time) branch_2_blocks.append(new_block) # send first branch that should be active tip send_by_headers(conn1, branch_1_blocks, do_send_blocks=True) # wait for active tip wait_for_tip(conn1, branch_1_blocks[-1].hash) # send second branch with more POW send_by_headers(conn2, branch_2_blocks, do_send_blocks=False) # active tip should be from first branch and second branch should have headers-only status wait_for_tip(conn1, branch_1_blocks[-1].hash) wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "headers-only") # we should not be in safe mode conn1.rpc.getbalance() # From time to time this test can run faster than expected and # the older blocks for batch 2 headers are not yet requested. # In that case they will be rejected due to being too far away # form the tip. In that case we need to send them again once they # are requested. def on_getdata(conn, msg): for i in msg.inv: if i.type != 2: # MSG_BLOCK error_msg = f"Unexpected data requested {i}" self.log.error(error_msg) raise NotImplementedError(error_msg) for block in branch_2_blocks: if int(block.hash, 16) == i.hash: conn.send_message(msg_block(block)) break conn2.cb.on_getdata = on_getdata # send sencond branch full blocks for block in branch_2_blocks: conn2.send_message(msg_block(block)) # second branch should now be invalid wait_for_tip_status(conn1, branch_2_blocks[-1].hash, "invalid") wait_for_tip(conn1, branch_1_blocks[-1].hash) # we should not be in safe mode conn1.rpc.getbalance()
def run_test(self): # Connect to node0 p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) network_thread_start() self.nodes[0].p2p.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock( self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append( CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 8400 deep (BiblePay needs 4x as much blocks to allow -assumevalid to work) for i in range(8400): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # We're adding new connections so terminate the network thread self.nodes[0].disconnect_p2ps() network_thread_join() # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)]) self.start_node(2, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)]) p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) p2p1 = self.nodes[1].add_p2p_connection(BaseNode()) p2p2 = self.nodes[2].add_p2p_connection(BaseNode()) network_thread_start() p2p0.wait_for_verack() p2p1.wait_for_verack() p2p2.wait_for_verack() # Make sure nodes actually accept the many headers self.mocktime = self.block_time set_node_times(self.nodes, self.mocktime) # send header lists to all three nodes. # node0 does not need to receive all headers # node1 must receive all headers as otherwise assumevalid is ignored in ConnectBlock # node2 should NOT receive all headers to force skipping of the assumevalid check in ConnectBlock p2p0.send_header_for_blocks(self.blocks[0:2000]) p2p1.send_header_for_blocks(self.blocks[0:2000]) p2p1.send_header_for_blocks(self.blocks[2000:4000]) p2p1.send_header_for_blocks(self.blocks[4000:6000]) p2p1.send_header_for_blocks(self.blocks[6000:8000]) p2p1.send_header_for_blocks(self.blocks[8000:]) p2p2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p0) self.assert_blockchain_height(self.nodes[0], 101) # Send 200 blocks to node1. All blocks, including block 102, will be accepted. for i in range(200): p2p1.send_message(msg_block(self.blocks[i])) # Syncing so many blocks can take a while on slow systems. Give it plenty of time to sync. p2p1.sync_with_ping(300) assert_equal( self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 200) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p2) self.assert_blockchain_height(self.nodes[2], 101)
def run_test(self): block_count = 0 # Create a P2P connection node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) network_thread = NetworkThread() network_thread.start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) _, out, block_count = prepare_init_chain(self.chain, 101, 100, block_0=False, start_block=0, node=node0) self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) block1_num = block_count - 1 # num of sig operations in one transaction num_of_sig_checks = 70 expensive_scriptPubKey = [OP_DUP, OP_HASH160, hash160(self.coinbase_pubkey), OP_EQUALVERIFY, OP_CHECKSIG, OP_DROP] * num_of_sig_checks + [OP_DUP, OP_HASH160, hash160( self.coinbase_pubkey), OP_EQUALVERIFY, OP_CHECKSIG] money_to_spend = 5000000000 spend = out[0] block2_hard = self.chain.next_block(block_count) # creates 4000 hard transaction and 4000 transaction to spend them. It will be 8k transactions in total add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=4000, num_of_sig_checks=num_of_sig_checks, expensive_script=expensive_scriptPubKey) self.chain.update_block(block_count, add_txns) block_count += 1 self.log.info(f"block2_hard hash: {block2_hard.hash}") self.chain.set_tip(block1_num) block3_easier = self.chain.next_block(block_count) add_txns = self.get_hard_transactions(spend, money_to_spend=money_to_spend, num_of_transactions=1000, num_of_sig_checks=num_of_sig_checks, expensive_script=expensive_scriptPubKey) self.chain.update_block(block_count, add_txns) self.log.info(f"block3_easier hash: {block3_easier.hash}") node0.send_message(msg_block(block2_hard)) node0.send_message(msg_block(block3_easier)) def wait_for_log(): text_activation = f"Block {block2_hard.hash} was not activated as best" text_block2 = "Verify 8000 txins" text_block3 = "Verify 2000 txins" results = 0 for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if text_activation in line: results += 1 elif text_block2 in line: results += 1 elif text_block3 in line: results += 1 return True if results == 3 else False # wait that everything is written to the log # try accounting for slower machines by having a large timeout wait_until(wait_for_log, timeout=120) text_activation = f"Block {block2_hard.hash} was not activated as best" text_block2 = "Verify 8000 txins" text_block3 = "Verify 2000 txins" for line in open(glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if text_activation in line: self.log.info(f"block2_hard was not activated as block3_easy won the validation race") elif text_block2 in line: line = line.split() self.log.info(f"block2_hard took {line[len(line) - 1]} to verify") elif text_block3 in line: line = line.split() self.log.info(f"block3_easy took {line[len(line)-1]} to verify") assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash()) node0.connection.close()
def run_test(self): node0 = self.nodes[0].add_p2p_connection(P2PInterface()) network_thread_start() node0.wait_for_verack() # Set node time to 60 days ago self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60) # Generating a chain of 10 blocks block_hashes = self.nodes[0].generate(10) # Create longer chain starting 2 blocks before current tip height = len(block_hashes) - 2 block_hash = block_hashes[height - 1] block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1 new_blocks = self.build_chain(5, block_hash, height, block_time) # Force reorg to a longer chain node0.send_message(msg_headers(new_blocks)) node0.wait_for_getdata() for block in new_blocks: node0.send_and_ping(msg_block(block)) # Check that reorg succeeded assert_equal(self.nodes[0].getblockcount(), 13) stale_hash = int(block_hashes[-1], 16) # Check that getdata request for stale block succeeds self.send_block_request(stale_hash, node0) test_function = lambda: self.last_block_equals(stale_hash, node0) wait_until(test_function, timeout=3) # Check that getheader request for stale block header succeeds self.send_header_request(stale_hash, node0) test_function = lambda: self.last_header_equals(stale_hash, node0) wait_until(test_function, timeout=3) # Longest chain is extended so stale is much older than chain tip self.nodes[0].setmocktime(0) tip = self.nodes[0].generate(nblocks=1)[0] assert_equal(self.nodes[0].getblockcount(), 14) # Send getdata & getheaders to refresh last received getheader message block_hash = int(tip, 16) self.send_block_request(block_hash, node0) self.send_header_request(block_hash, node0) node0.sync_with_ping() # Request for very old stale block should now fail self.send_block_request(stale_hash, node0) time.sleep(3) assert not self.last_block_equals(stale_hash, node0) # Request for very old stale block header should now fail self.send_header_request(stale_hash, node0) time.sleep(3) assert not self.last_header_equals(stale_hash, node0) # Verify we can fetch very old blocks and headers on the active chain block_hash = int(block_hashes[2], 16) self.send_block_request(block_hash, node0) self.send_header_request(block_hash, node0) node0.sync_with_ping() self.send_block_request(block_hash, node0) test_function = lambda: self.last_block_equals(block_hash, node0) wait_until(test_function, timeout=3) self.send_header_request(block_hash, node0) test_function = lambda: self.last_header_equals(block_hash, node0) wait_until(test_function, timeout=3)
def get_tests(self): # shorthand for functions block = self.chain.next_block node = self.nodes[0] self.chain.set_genesis_hash( int(node.getbestblockhash(), 16) ) block(0) yield self.accepted() test, out, _ = prepare_init_chain(self.chain, 101, 100) yield test ########## SCENARIO 1 assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2) # Create and send tx1 and tx2 that are valid before genesis. tx1 = create_transaction(out[0].tx, out[0].n, b'', 100000, CScript([OP_IF, OP_0, OP_ELSE, OP_1, OP_ELSE, OP_2, OP_ENDIF])) tx2 = create_transaction(tx1, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(tx1)) self.test.connections[0].send_message(msg_tx(tx2)) # wait for transactions to be accepted. wait_until(lambda: len(node.getrawmempool()) == 2, timeout=5) assert_equal(True, tx1.hash in node.getrawmempool()) assert_equal(True, tx2.hash in node.getrawmempool()) # Generate an empty block, height is then 103 and mempool is cleared. block103 = block(1, spend=out[1]) yield self.accepted() assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) assert_equal(len(node.getrawmempool()), 0) # Send transactions tx1 and tx2 once again, this time with Genesis rules (mempool height is 104). self.test.connections[0].send_message(msg_tx(tx1)) self.test.connections[0].send_message(msg_tx(tx2)) # wait for transaction processing # Tx2 should not be valid anymore. wait_until(lambda: len(node.getrawmempool()) == 1, timeout=5) assert_equal(True, tx1.hash in node.getrawmempool()) assert_equal(False, tx2.hash in node.getrawmempool()) # Now send tx1 and tx2 again, but this time in block. Block should be rejected. block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(block, [tx1,tx2]) rejected_blocks = [] rejected_txs = [] def on_reject(conn, msg): if (msg.message == b'block'): rejected_blocks.append(msg) assert_equal(msg.reason, b'blk-bad-inputs') if msg.message == b'tx': rejected_txs.append(msg) self.test.connections[0].cb.on_reject = on_reject self.test.connections[0].send_message(msg_block(block)) wait_until(lambda: len(rejected_blocks) > 0, timeout=5, lock=mininode_lock) assert_equal(rejected_blocks[0].data, block.sha256) assert_equal(False, block.hash == node.getbestblockhash()) ########## SCENARIO 2 assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Create and send tx3 and tx4 that are valid after genesis. tx3 = create_transaction(out[2].tx, out[2].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1])) tx4 = create_transaction(tx3, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(tx3)) self.test.connections[0].send_message(msg_tx(tx4)) # wait for transaction in mempool wait_until(lambda: tx3.hash in node.getrawmempool(), timeout=5) wait_until(lambda: tx4.hash in node.getrawmempool(), timeout=5) # Invalidate block --> we are then at state before Genesis. Mempool is cleared. node.invalidateblock(hashToHex(block103.sha256)) assert_equal(False, tx3.hash in node.getrawmempool()) assert_equal(False, tx4.hash in node.getrawmempool()) assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2) # Send tx3 again, this time in pre-genesis rules. It is accepted to mempool. self.test.connections[0].send_message(msg_tx(tx3)) wait_until(lambda: tx3.hash in node.getrawmempool(), timeout=5) # Generate a block (height 103) with tx3 in it. node.generate(1) assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) assert_equal(True, tx3.hash in node.getblock(node.getbestblockhash())['tx']) # Send tx4 again, again with Genesis rules. It should not be accepted to mempool. self.test.connections[0].send_message(msg_tx(tx4)) wait_until(lambda: tx4.sha256 in [msg.data for msg in rejected_txs], timeout=5, lock=mininode_lock) assert_equal(len(node.getrawmempool()), 0) # Send tx4 again, this time in block. Block should be rejected. block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(block, [tx4]) self.test.connections[0].send_message(msg_block(block)) # Wait for hash in rejected blocks wait_until(lambda: block.sha256 in [msg.data for msg in rejected_blocks], timeout=5, lock=mininode_lock) assert_equal(False, block.hash == node.getbestblockhash()) ########## SCENARIO 3 assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Generate a block (height 104) with tx5 and tx6 (valid after genesis). tx5 = create_transaction(out[3].tx, out[3].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1])) tx6 = create_transaction(tx5, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) blockGenesis = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(blockGenesis, [tx5, tx6]) self.test.connections[0].send_message(msg_block(blockGenesis)) wait_until(lambda: blockGenesis.hash == node.getbestblockhash(), timeout=5) assert_equal(True, tx5.hash in node.getblock(node.getbestblockhash())['tx']) assert_equal(True, tx6.hash in node.getblock(node.getbestblockhash())['tx']) # Invalidate block 104. tx5 and tx6 are in now in mempool. node.invalidateblock(hashToHex(blockGenesis.sha256)) assert_equal(True, tx5.hash in node.getrawmempool()) assert_equal(True, tx6.hash in node.getrawmempool()) assert_equal(node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Invalidate block 103. tx5 and tx6 are not in mempool anymore. node.invalidateblock(node.getbestblockhash()) assert_equal(False, tx5.hash in node.getrawmempool()) assert_equal(False, tx6.hash in node.getrawmempool())
def run_test(self): with self.run_node_with_connections("Scenario 1: Low fee, non-whitelisted peer", 0, ["-blockmintxfee=0.00001"], number_of_connections=1) as (conn,): mining_fee = 1.01 # in satoshi per byte relayfee = float(conn.rpc.getnetworkinfo()["relayfee"] * COIN / 1000) + 0.01 # in satoshi per byte # create block with coinbase coinbase = create_coinbase(height=1) first_block = create_block(int(conn.rpc.getbestblockhash(), 16), coinbase=coinbase) first_block.solve() conn.send_message(msg_block(first_block)) wait_until(lambda: conn.rpc.getbestblockhash() == first_block.hash, check_interval=0.3) #mature the coinbase conn.rpc.generate(150) funding_tx = self.create_tx([(coinbase, 0)], 10, 1.5) conn.send_message(msg_tx(funding_tx)) check_mempool_equals(conn.rpc, [funding_tx]) conn.rpc.generate(1) last_block_info = conn.rpc.getblock(conn.rpc.getbestblockhash()) block = create_block(int(last_block_info["hash"], 16), coinbase=create_coinbase(height=last_block_info["height"] + 1), nTime=last_block_info["time"] + 1) low_fee_tx = self.create_tx([(funding_tx, 0)], 2, relayfee) block.vtx.append(low_fee_tx) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() block.solve() conn.send_message(msg_block(block)) wait_until(lambda: conn.rpc.getbestblockhash() == block.hash, check_interval=0.3) tx_pays_relay1 = self.create_tx([(low_fee_tx, 0)], 2, relayfee) tx_pays_relay2 = self.create_tx([(tx_pays_relay1, 0)], 1, relayfee) tx_pays_enough_for_itself = self.create_tx([(tx_pays_relay1, 1)], 1, mining_fee) tx_pays_for_ancestors = self.create_tx([(tx_pays_relay2, 0)], 1, 3.5 * mining_fee) tx_pays_relay3 = self.create_tx([(tx_pays_for_ancestors, 0)], 1, relayfee) conn.send_message(msg_tx(tx_pays_relay1)) check_mempool_equals(conn.rpc, [tx_pays_relay1]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only" conn.send_message(msg_tx(tx_pays_relay2)) check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only" conn.send_message(msg_tx(tx_pays_enough_for_itself)) check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only" # this will trigger cpfp for two unpaying antcestors (tx_pays_relay1 and tx_pays_relay1) then # after that tx_pays_enough_for_itself will be free of ancestor debt and it will be accepted also conn.send_message(msg_tx(tx_pays_for_ancestors)) check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 5), #"all tx plus coinbase" # still, non paying child of the tx_pays_for_ancestors will not be accepted conn.send_message(msg_tx(tx_pays_relay3)) check_mempool_equals(conn.rpc, [tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors, tx_pays_relay3]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 5), #"all tx, except tx_pays_relay3 plus coinbase" # we will mine a new block, all transactions from the journal will end up in new block, mempool will contain # only tx_pays_relay3 conn.rpc.generate(1) check_mempool_equals(conn.rpc, [tx_pays_relay3]) # now we invalidate block two blocks, at this point tx_pays_for_ancestors does not pay enough for # all non-paying children, nothing will end up in the journal. # non paying children are: low_fee_tx, tx_pays_relay1, tx_pays_relay2 conn.rpc.invalidateblock(block.hash) check_mempool_equals(conn.rpc, [low_fee_tx, tx_pays_relay1, tx_pays_relay2, tx_pays_enough_for_itself, tx_pays_for_ancestors, tx_pays_relay3]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only" # when we reconsider invalidate block, everything should be the same conn.rpc.reconsiderblock(block.hash) check_mempool_equals(conn.rpc, [tx_pays_relay3]) wait_until(lambda: conn.rpc.getminingcandidate()["num_tx"] == 1) #"should be coinbase only"
def run_test(self): # Connect to node0 node0 = BaseNode() connections = [] connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) NetworkThread().start() # Start up network handling in another thread node0.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 2100 deep for i in range(2100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.nodes.append(self.start_node(1, self.options.tmpdir, ["-assumevalid=" + hex(block102.sha256)])) node1 = BaseNode() # connects to node1 connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1)) node1.add_connection(connections[1]) node1.wait_for_verack() self.nodes.append(self.start_node(2, self.options.tmpdir, ["-assumevalid=" + hex(block102.sha256)])) node2 = BaseNode() # connects to node2 connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2)) node2.add_connection(connections[2]) node2.wait_for_verack() # send header lists to all three nodes node0.send_header_for_blocks(self.blocks[0:2000]) node0.send_header_for_blocks(self.blocks[2000:]) node1.send_header_for_blocks(self.blocks[0:2000]) node1.send_header_for_blocks(self.blocks[2000:]) node2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(node0) self.assert_blockchain_height(self.nodes[0], 101) # Send all blocks to node1. All blocks will be accepted. for i in range(2202): node1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. node1.sync_with_ping(120) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(node2) self.assert_blockchain_height(self.nodes[2], 101)
def run_test(self): block_count = 0 # Create a P2P connections node0 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0) node0.add_connection(connection) node1 = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node1) node1.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() node1.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) _, outs, block_count = prepare_init_chain(self.chain, 101, 1, block_0=False, start_block=0, node=node0) out = outs[0] self.log.info("waiting for block height 101 via rpc") self.nodes[0].waitforblockheight(101) tip_block_num = block_count - 1 # adding extra transactions to get different block hashes block2_hard = self.chain.next_block(block_count, spend=out, extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3_easier = self.chain.next_block(block_count, spend=out, extra_txns=2) block_count += 1 mining_candidate = self.nodes[0].getminingcandidate() block4_hard = self.chain.next_block(block_count) block4_hard.hashPrevBlock = int(mining_candidate["prevhash"], 16) block4_hard.nTime = mining_candidate["time"] block4_hard.nVersion = mining_candidate["version"] block4_hard.solve() mining_solution = {"id": mining_candidate["id"], "nonce": block4_hard.nNonce, "coinbase": ToHex(block4_hard.vtx[0]), "time": mining_candidate["time"], "version": mining_candidate["version"]} # send three "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block2 hash: {block2_hard.hash}") self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add") self.log.info(f"hard block4 hash: {block4_hard.hash}") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) # send one block via p2p and one via rpc node0.send_message(msg_block(block2_hard)) # making rpc call submitminingsolution in a separate thread because waitaftervalidation is blocking # the return of submitminingsolution submitminingsolution_thread = threading.Thread(target=self.nodes[0].submitminingsolution, args=(mining_solution,)) submitminingsolution_thread.start() # because self.nodes[0] rpc is blocked we use another rpc client rpc_client = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0), 0, coveragedir=self.options.coveragedir) wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, rpc_client, self.log) self.log.info(f"easy block3 hash: {block3_easier.hash}") node1.send_message(msg_block(block3_easier)) rpc_client.waitforblockheight(102) assert_equal(block3_easier.hash, rpc_client.getbestblockhash()) # now we can remove waiting status from blocks and finish their validation rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove") rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove") submitminingsolution_thread.join() # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
def test_nonnull_locators(self, test_node, inv_node): tip = int(self.nodes[0].getbestblockhash(), 16) # PART 1 # 1. Mine a block; expect inv announcements each time self.log.info("Part 1: headers don't start before sendheaders message...") for i in range(4): old_tip = tip tip = self.mine_blocks(1) inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(inv=[tip], headers=[]) # Try a few different responses; none should affect next announcement if i == 0: # first request the block test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # next try requesting header and block test_node.send_get_headers(locator=[old_tip], hashstop=tip) test_node.send_get_data([tip]) test_node.wait_for_block(tip) test_node.clear_last_announcement() # since we requested headers... elif i == 2: # this time announce own block via headers height = self.nodes[0].getblockcount() last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 new_block = create_block(tip, create_coinbase(height + 1), block_time) new_block.nVersion = 0x20000000 new_block.solve() test_node.send_header_for_blocks([new_block]) test_node.wait_for_getdata([new_block.sha256]) test_node.send_message(msg_block(new_block)) test_node.sync_with_ping() # make sure this block is processed inv_node.clear_last_announcement() test_node.clear_last_announcement() self.log.info("Part 1: success!") self.log.info("Part 2: announce blocks with headers after sendheaders message...") # PART 2 # 2. Send a sendheaders message and test that headers announcements # commence and keep working. test_node.send_message(msg_sendheaders()) prev_tip = int(self.nodes[0].getbestblockhash(), 16) test_node.send_get_headers(locator=[prev_tip], hashstop=0) test_node.sync_with_ping() # Now that we've synced headers, headers announcements should work tip = self.mine_blocks(1) inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(headers=[tip]) height = self.nodes[0].getblockcount() + 1 block_time += 10 # Advance far enough ahead for i in range(10): # Mine i blocks, and alternate announcing either via # inv (of tip) or via headers. After each, new blocks # mined by the node should successfully be announced # with block header, even though the blocks are never requested for j in range(2): blocks = [] for b in range(i + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 if j == 0: # Announce via inv test_node.send_block_inv(tip) test_node.wait_for_getheaders() # Should have received a getheaders now test_node.send_header_for_blocks(blocks) # Test that duplicate inv's won't result in duplicate # getdata requests, or duplicate headers announcements [inv_node.send_block_inv(x.sha256) for x in blocks] test_node.wait_for_getdata([x.sha256 for x in blocks]) inv_node.sync_with_ping() else: # Announce via headers test_node.send_header_for_blocks(blocks) test_node.wait_for_getdata([x.sha256 for x in blocks]) # Test that duplicate headers won't result in duplicate # getdata requests (the check is further down) inv_node.send_header_for_blocks(blocks) inv_node.sync_with_ping() [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() inv_node.sync_with_ping() # This block should not be announced to the inv node (since it also # broadcast it) assert "inv" not in inv_node.last_message assert "headers" not in inv_node.last_message tip = self.mine_blocks(1) inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(headers=[tip]) height += 1 block_time += 1 self.log.info("Part 2: success!") self.log.info("Part 3: headers announcements can stop after large reorg, and resume after headers/inv from peer...") # PART 3. Headers announcements can stop after large reorg, and resume after # getheaders or inv from peer. for j in range(2): # First try mining a reorg that can propagate with header announcement new_block_hashes = self.mine_reorg(length=7) tip = new_block_hashes[-1] inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(headers=new_block_hashes) block_time += 8 # Mine a too-large reorg, which should be announced with a single inv new_block_hashes = self.mine_reorg(length=8) tip = new_block_hashes[-1] inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(inv=[tip], headers=[]) block_time += 9 fork_point = self.nodes[0].getblock("%02x" % new_block_hashes[0])["previousblockhash"] fork_point = int(fork_point, 16) # Use getblocks/getdata test_node.send_getblocks(locator=[fork_point]) test_node.check_last_announcement(inv=new_block_hashes, headers=[]) test_node.send_get_data(new_block_hashes) test_node.wait_for_block(new_block_hashes[-1]) for i in range(3): # Mine another block, still should get only an inv tip = self.mine_blocks(1) inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(inv=[tip], headers=[]) if i == 0: # Just get the data -- shouldn't cause headers announcements to resume test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # Send a getheaders message that shouldn't trigger headers announcements # to resume (best header sent will be too old) test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 2: test_node.send_get_data([tip]) test_node.wait_for_block(tip) # This time, try sending either a getheaders to trigger resumption # of headers announcements, or mine a new block and inv it, also # triggering resumption of headers announcements. if j == 0: test_node.send_get_headers(locator=[tip], hashstop=0) test_node.sync_with_ping() else: test_node.send_block_inv(tip) test_node.sync_with_ping() # New blocks should now be announced with header tip = self.mine_blocks(1) inv_node.check_last_announcement(inv=[tip], headers=[]) test_node.check_last_announcement(headers=[tip]) self.log.info("Part 3: success!") self.log.info("Part 4: Testing direct fetch behavior...") tip = self.mine_blocks(1) height = self.nodes[0].getblockcount() + 1 last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 # Create 2 blocks. Send the blocks, then send the headers. blocks = [] for b in range(2): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 inv_node.send_message(msg_block(blocks[-1])) inv_node.sync_with_ping() # Make sure blocks are processed test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() # should not have received any getdata messages with mininode_lock: assert "getdata" not in test_node.last_message # This time, direct fetch should work blocks = [] for b in range(3): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME) [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() # Now announce a header that forks the last two blocks tip = blocks[0].sha256 height -= 1 blocks = [] # Create extra blocks for later for b in range(20): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 # Announcing one block on fork should not trigger direct fetch # (less work than tip) test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks[0:1]) test_node.sync_with_ping() with mininode_lock: assert "getdata" not in test_node.last_message # Announcing one more block on fork should trigger direct fetch for # both blocks (same work as tip) test_node.send_header_for_blocks(blocks[1:2]) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 16 more headers should trigger direct fetch for 14 more # blocks test_node.send_header_for_blocks(blocks[2:18]) test_node.sync_with_ping() test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 1 more header should not trigger any response test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks[18:19]) test_node.sync_with_ping() with mininode_lock: assert "getdata" not in test_node.last_message self.log.info("Part 4: success!") # Now deliver all those blocks we announced. [test_node.send_message(msg_block(x)) for x in blocks] self.log.info("Part 5: Testing handling of unconnecting headers") # First we test that receipt of an unconnecting header doesn't prevent # chain sync. for i in range(10): test_node.last_message.pop("getdata", None) blocks = [] # Create two more blocks. for j in range(2): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 # Send the header of the second block -> this won't connect. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[1]]) test_node.wait_for_getheaders() test_node.send_header_for_blocks(blocks) test_node.wait_for_getdata([x.sha256 for x in blocks]) [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256) blocks = [] # Now we test that if we repeatedly don't send connecting headers, we # don't go into an infinite loop trying to get them to connect. MAX_UNCONNECTING_HEADERS = 10 for j in range(MAX_UNCONNECTING_HEADERS + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].nVersion = 0x20000000 blocks[-1].solve() tip = blocks[-1].sha256 block_time += 1 height += 1 for i in range(1, MAX_UNCONNECTING_HEADERS): # Send a header that doesn't connect, check that we get a getheaders. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[i]]) test_node.wait_for_getheaders() # Next header will connect, should re-set our count: test_node.send_header_for_blocks([blocks[0]]) # Remove the first two entries (blocks[1] would connect): blocks = blocks[2:] # Now try to see how many unconnecting headers we can send # before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS for i in range(5 * MAX_UNCONNECTING_HEADERS - 1): # Send a header that doesn't connect, check that we get a getheaders. with mininode_lock: test_node.last_message.pop("getheaders", None) test_node.send_header_for_blocks([blocks[i % len(blocks)]]) test_node.wait_for_getheaders() # Eventually this stops working. test_node.send_header_for_blocks([blocks[-1]]) # Should get disconnected test_node.wait_for_disconnect() self.log.info("Part 5: success!") # Finally, check that the inv node never received a getdata request, # throughout the test assert "getdata" not in inv_node.last_message
def get_tests(self): # shorthand for functions block = self.chain.next_block node = self.nodes[0] self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) # Create a new block block(0) self.chain.save_spendable_output() yield self.accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(101): block(5000 + i) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(self.chain.get_spendable_output()) ########## SCENARIO 1 assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2) # Create and send tx1 and tx2 that are valid before genesis. tx1 = create_transaction( out[0].tx, out[0].n, b'', 100000, CScript([OP_IF, OP_0, OP_ELSE, OP_1, OP_ELSE, OP_2, OP_ENDIF])) tx2 = create_transaction(tx1, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(tx1)) self.test.connections[0].send_message(msg_tx(tx2)) # wait for transaction processing sleep(1) # Both transactions are accepted. assert_equal(True, tx1.hash in node.getrawmempool()) assert_equal(True, tx2.hash in node.getrawmempool()) # Generate an empty block, height is then 103 and mempool is cleared. block103 = block(1, spend=out[1]) yield self.accepted() assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) assert_equal(len(node.getrawmempool()), 0) # Send transactions tx1 and tx2 once again, this time with Genesis rules (mempool height is 104). self.test.connections[0].send_message(msg_tx(tx1)) self.test.connections[0].send_message(msg_tx(tx2)) # wait for transaction processing sleep(1) # Tx2 should not be valid anymore. assert_equal(len(node.getrawmempool()), 1) assert_equal(True, tx1.hash in node.getrawmempool()) assert_equal(False, tx2.hash in node.getrawmempool()) # Now send tx1 and tx2 again, but this time in block. Block should be rejected. block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(block, [tx1, tx2]) rejected_blocks = [] def on_reject(conn, msg): if (msg.message == b'block'): rejected_blocks.append(msg) assert_equal(msg.reason, b'blk-bad-inputs') self.test.connections[0].cb.on_reject = on_reject self.test.connections[0].send_message(msg_block(block)) sleep(1) assert_equal(rejected_blocks[0].data, block.sha256) assert_equal(False, block.hash == node.getbestblockhash()) ########## SCENARIO 2 assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Create and send tx3 and tx4 that are valid after genesis. tx3 = create_transaction( out[2].tx, out[2].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1])) tx4 = create_transaction(tx3, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(tx3)) self.test.connections[0].send_message(msg_tx(tx4)) # wait for transaction processing sleep(1) # Both transactions are accepted. assert_equal(True, tx3.hash in node.getrawmempool()) assert_equal(True, tx4.hash in node.getrawmempool()) # Invalidate block --> we are then at state before Genesis. Mempool is cleared. node.invalidateblock(format(block103.sha256, 'x')) assert_equal(False, tx3.hash in node.getrawmempool()) assert_equal(False, tx4.hash in node.getrawmempool()) assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 2) # Send tx3 again, this time in pre-genesis rules. It is accepted to mempool. self.test.connections[0].send_message(msg_tx(tx3)) sleep(1) assert_equal(True, tx3.hash in node.getrawmempool()) # Generate a block (height 103) with tx3 in it. node.generate(1) assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) assert_equal(True, tx3.hash in node.getblock(node.getbestblockhash())['tx']) # Send tx4 again, again with Genesis rules. It should not be accepted to mempool. self.test.connections[0].send_message(msg_tx(tx4)) sleep(1) assert_equal(len(node.getrawmempool()), 0) # Send tx4 again, this time in block. Block should be rejected. block = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(block, [tx4]) self.test.connections[0].send_message(msg_block(block)) sleep(1) assert_equal(rejected_blocks[1].data, block.sha256) assert_equal(False, block.hash == node.getbestblockhash()) ########## SCENARIO 3 assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Generate a block (height 104) with tx5 and tx6 (valid after genesis). tx5 = create_transaction( out[3].tx, out[3].n, b'', 100000, CScript([OP_IF, OP_2, OP_2MUL, OP_ENDIF, OP_1])) tx6 = create_transaction(tx5, 0, CScript([OP_0]), 1, CScript([OP_TRUE])) blockGenesis = create_block(int("0x" + node.getbestblockhash(), 16), create_coinbase(height=1, outputValue=25)) add_tx_to_block(blockGenesis, [tx5, tx6]) self.test.connections[0].send_message(msg_block(blockGenesis)) sleep(1) assert_equal(True, tx5.hash in node.getblock(node.getbestblockhash())['tx']) assert_equal(True, tx6.hash in node.getblock(node.getbestblockhash())['tx']) # Invalidate block 104. tx5 and tx6 are in now in mempool. node.invalidateblock(format(blockGenesis.sha256, 'x')) assert_equal(True, tx5.hash in node.getrawmempool()) assert_equal(True, tx6.hash in node.getrawmempool()) assert_equal( node.getblock(node.getbestblockhash())['height'], self.genesisactivationheight - 1) # Invalidate block 103. tx5 and tx6 are not in mempool anymore. node.invalidateblock(node.getbestblockhash()) assert_equal(False, tx5.hash in node.getrawmempool()) assert_equal(False, tx6.hash in node.getrawmempool())
def run_test(self): node0 = self.nodes[0].add_p2p_connection(P2PInterface()) # Set node time to 60 days ago self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60) # Generating a chain of 10 blocks block_hashes = self.nodes[0].generate(nblocks=10) # Create longer chain starting 2 blocks before current tip height = len(block_hashes) - 2 block_hash = block_hashes[height - 1] block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1 new_blocks = self.build_chain(5, block_hash, height, block_time) # Force reorg to a longer chain node0.send_message(msg_headers(new_blocks)) node0.wait_for_getdata() for block in new_blocks: node0.send_and_ping(msg_block(block)) # Check that reorg succeeded assert_equal(self.nodes[0].getblockcount(), 13) stale_hash = int(block_hashes[-1], 16) # Check that getdata request for stale block succeeds self.send_block_request(stale_hash, node0) test_function = lambda: self.last_block_equals(stale_hash, node0) wait_until(test_function, timeout=3) # Check that getheader request for stale block header succeeds self.send_header_request(stale_hash, node0) test_function = lambda: self.last_header_equals(stale_hash, node0) wait_until(test_function, timeout=3) # Longest chain is extended so stale is much older than chain tip self.nodes[0].setmocktime(0) tip = self.nodes[0].generate(nblocks=1)[0] assert_equal(self.nodes[0].getblockcount(), 14) # Send getdata & getheaders to refresh last received getheader message block_hash = int(tip, 16) self.send_block_request(block_hash, node0) self.send_header_request(block_hash, node0) node0.sync_with_ping() # Request for very old stale block should now fail self.send_block_request(stale_hash, node0) time.sleep(3) assert not self.last_block_equals(stale_hash, node0) # Request for very old stale block header should now fail self.send_header_request(stale_hash, node0) time.sleep(3) assert not self.last_header_equals(stale_hash, node0) # Verify we can fetch very old blocks and headers on the active chain block_hash = int(block_hashes[2], 16) self.send_block_request(block_hash, node0) self.send_header_request(block_hash, node0) node0.sync_with_ping() self.send_block_request(block_hash, node0) test_function = lambda: self.last_block_equals(block_hash, node0) wait_until(test_function, timeout=3) self.send_header_request(block_hash, node0) test_function = lambda: self.last_header_equals(block_hash, node0) wait_until(test_function, timeout=3)
def _test_soft_consensus_freeze_competing_chains(self, spendable_txo, node): self.log.info( "*** Performing soft consensus freeze with competing chains") frozen_tx = self._create_tx_mine_block_and_freeze_tx( node, spendable_txo) root_chain_tip = self.get_chain_tip() # mine 5 blocks on valid chain (one less than is needed for the frozen chain to become active) self.log.info("Mining blocks on valid chain") self._mine_and_send_block(None, node) self._mine_and_send_block(None, node) self._mine_and_send_block(None, node) self._mine_and_send_block(None, node) last_valid_block = self._mine_and_send_block(None, node) valid_chain_tip = self.get_chain_tip() self.set_chain_tip(root_chain_tip) self.log.info("Mining blocks on frozen chain") # block should not become new tip as it contains transaction spending frozen TXO spend_frozen_tx = self._create_tx( PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE])) frozen_block = self._mine_block(spend_frozen_tx) self.submit_block_and_check_tip(node, frozen_block, last_valid_block.hash) # next 4 blocks are also considered soft consensus frozen and must not become new tip self.submit_block_and_check_tip(node, self._mine_block(None), last_valid_block.hash) self.submit_block_and_check_tip(node, self._mine_block(None), last_valid_block.hash) self.submit_block_and_check_tip(node, self._mine_block(None), last_valid_block.hash) self.submit_block_and_check_tip(node, self._mine_block(None), last_valid_block.hash) # this block is high enough for the frozen chain to become active and should become new tip new_frozen_tip = self._mine_and_send_block(None, node) frozen_chain_tip = self.get_chain_tip() self.log.info("Mining blocks on valid chain") # 2 new blocks on valid chain should trigger reorg back to valid chain self.set_chain_tip(valid_chain_tip) next_frozen_tip = self._mine_block(None) self.submit_block_and_check_tip(node, next_frozen_tip, new_frozen_tip.hash) new_valid_tip = self._mine_block(None) node.p2p.send_and_ping(msg_block(new_valid_tip)) assert_equal(new_valid_tip.hash, node.rpc.getbestblockhash()) assert ( node.check_frozen_tx_log(next_frozen_tip.hash) ) # NOTE: Reject is expected because transaction spending frozen TXO is added back to mempool and its validation must fail when checked against new tip. self.log.info("Mining blocks on frozen chain") # 2 new blocks on frozen chain should trigger reorg back to frozen chain self.set_chain_tip(frozen_chain_tip) self.submit_block_and_check_tip(node, self._mine_block(None), new_valid_tip.hash) self._mine_and_send_block(None, node)