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)) _, 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 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) easier_block_num = block_count 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 # make child block of easier block self.chain.set_tip(easier_block_num) block5 = self.chain.next_block(block_count) block5_num = block_count 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) # send blocks via different p2p connection 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) # send easier block through different p2p connection too node2.send_message(msg_block(block3_easier)) self.log.info(f"easier block hash: {block3_easier.hash}") self.nodes[0].waitforblockheight(102) assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash()) # child block of block3_easier self.log.info(f"child block hash: {block5.hash}") self.nodes[0].waitaftervalidatingblock(block5.hash, "add") # make sure child block is in waiting list and then send it wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log) node2.send_message(msg_block(block5)) # make sure we started validating child block wait_for_validating_blocks({block5.hash}, self.nodes[0], self.log) # finish validation on block2_hard self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "remove") wait_for_not_validating_blocks({block2_hard.hash}, self.nodes[0], self.log) # finish validation on child block self.nodes[0].waitaftervalidatingblock(block5.hash, "remove") wait_for_not_validating_blocks({block5.hash}, self.nodes[0], self.log) # block5 should be active at this point assert_equal(block5.hash, self.nodes[0].getbestblockhash()) # finish validation on block4_hard self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "remove") wait_for_not_validating_blocks({block4_hard.hash}, self.nodes[0], self.log) # block5 should still be active at this point assert_equal(block5.hash, self.nodes[0].getbestblockhash()) # Make three siblings and send them via same p2p connection. block6_hard = self.chain.next_block(block_count, spend=out[1], extra_txns=8) block_count += 1 self.chain.set_tip(block5_num) block7_easier = self.chain.next_block(block_count, spend=out[1], extra_txns=2) block_count += 1 self.chain.set_tip(block5_num) block8_hard = self.chain.next_block(block_count, spend=out[1], extra_txns=10) block_count += 1 self.log.info(f"hard block6 hash: {block6_hard.hash}") self.nodes[0].waitaftervalidatingblock(block6_hard.hash, "add") self.log.info(f"hard block8 hash: {block8_hard.hash}") self.nodes[0].waitaftervalidatingblock(block8_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block6_hard.hash, block8_hard.hash}, self.nodes[0], self.log) # sending blocks via same p2p connection node0.send_message(msg_block(block6_hard)) node0.send_message(msg_block(block8_hard)) # make sure we started validating blocks wait_for_validating_blocks({block6_hard.hash, block8_hard.hash}, self.nodes[0], self.log) # send easier block through same p2p connection too node0.send_message(msg_block(block7_easier)) self.nodes[0].waitforblockheight(104) assert_equal(block7_easier.hash, self.nodes[0].getbestblockhash()) # now we can remove waiting status from blocks and finish their validation self.nodes[0].waitaftervalidatingblock(block6_hard.hash, "remove") self.nodes[0].waitaftervalidatingblock(block8_hard.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block7_easier.hash, self.nodes[0].getbestblockhash())
def get_tests(self): # shorthand for functions block = self.chain.next_block node = get_rpc_proxy(self.nodes[0].url, 1, timeout=6000, coveragedir=self.nodes[0].coverage_dir) self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) block(0) yield self.accepted() test, out, _ = prepare_init_chain(self.chain, 200, 200) yield test # Create transaction that will almost fill block file when next block will be generated (~130 MB) tx1 = create_transaction( out[0].tx, out[0].n, b"", ONE_MEGABYTE * 120, CScript( [OP_TRUE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE * 120))])) self.test.connections[0].send_message(msg_tx(tx1)) # Wait for transaction processing self.check_mempool(node, [tx1], timeout=6000) # Mine block with new transaction. minedBlock1 = node.generate(1) # Send 4 large (~1GB) transactions that will go into next block for i in range(4): txLarge = create_transaction( out[1 + i].tx, out[1 + i].n, b"", ONE_GIGABYTE, CScript([ OP_TRUE, OP_RETURN, bytearray([42] * (ONE_GIGABYTE - ONE_MEGABYTE)) ])) self.test.connections[0].send_message(msg_tx(txLarge)) self.check_mempool(node, [txLarge], timeout=6000) # Send overflow txOverflow = create_transaction( out[5].tx, out[5].n, b"", ONE_MEGABYTE * 305, CScript( [OP_TRUE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE * 305))])) self.test.connections[0].send_message(msg_tx(txOverflow)) self.check_mempool(node, [txOverflow], timeout=6000) # Mine block with new transactions. minedBlock2 = node.generate(1) txLast = create_transaction( out[6].tx, out[6].n, b"", ONE_MEGABYTE, CScript([OP_TRUE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE))])) self.test.connections[0].send_message(msg_tx(txLast)) self.check_mempool(node, [txLast], timeout=6000) # Mine block with new transaction. minedBlock3 = node.generate(1) # Restart node to make sure that the index is written to disk self.stop_nodes() self.nodes[0].rpc_timeout = 6000 self.start_nodes(self.extra_args) # Get proxy with bigger timeout node = get_rpc_proxy(self.nodes[0].url, 1, timeout=6000, coveragedir=self.nodes[0].coverage_dir) # Verify that blocks were correctly written / read blockDetails1 = node.getblock(minedBlock1[0]) blockDetails2 = node.getblock(minedBlock2[0]) blockDetails3 = node.getblock(minedBlock3[0]) assert_equal(minedBlock1[0], blockDetails1['hash']) assert_equal(minedBlock2[0], blockDetails2['hash']) assert_equal(minedBlock3[0], blockDetails3['hash']) for txId in blockDetails1['tx']: txCopy = node.getrawtransaction(txId, 1) assert_equal(txId, txCopy['txid']) for txId in blockDetails2['tx']: txCopy = node.getrawtransaction(txId, 1) assert_equal(txId, txCopy['txid']) for txId in blockDetails3['tx']: txCopy = node.getrawtransaction(txId, 1) assert_equal(txId, txCopy['txid'])
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) 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 # left branch block2 = self.chain.next_block(block_count, spend=out[0], extra_txns=8) block2_num = block_count block_count += 1 node0.send_message(msg_block(block2)) self.log.info(f"block2 hash: {block2.hash}") self.nodes[0].waitforblockheight(102) # send blocks 3,4 for parallel validation on left branch block3 = self.chain.next_block(block_count, spend=out[1], extra_txns=10) block_count += 1 self.chain.set_tip(block2_num) block4 = self.chain.next_block(block_count, spend=out[1], 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.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({block3.hash, block4.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block3)) node2.send_message(msg_block(block4)) # make sure we started validating blocks wait_for_validating_blocks({block3.hash, block4.hash}, self.nodes[0], self.log) # right branch self.chain.set_tip(tip_block_num) block5 = self.chain.next_block(block_count, spend=out[0], extra_txns=10) block_count += 1 node1.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" block6 = self.chain.next_block(block_count) node1.send_message(msg_block(block6)) self.log.info(f"block6 hash: {block6.hash}") block_count += 1 block7 = self.chain.next_block(block_count) node1.send_message(msg_block(block7)) self.log.info(f"block7 hash: {block7.hash}") block_count += 1 self.nodes[0].waitforblockheight(104) 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(block3.hash, "remove") 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())
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): 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 get_tests(self): rejected_txs = [] def on_reject(conn, msg): if msg.message == b'tx': rejected_txs.append(msg) self.test.connections[0].cb.on_reject = on_reject # 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, 150, 150) yield test # Create transaction with OP_ADD in the locking script which should be banned txOpAdd1 = create_transaction(out[0].tx, out[0].n, b'', 100000, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd1]) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd2)) # wait for transaction processing wait_until(lambda: txOpAdd2.sha256 in [msg.data for msg in rejected_txs], timeout=5, lock=mininode_lock) assert_equal(len(rejected_txs), 1) # rejected assert_equal(rejected_txs[0].reason, b'max-script-num-length-policy-limit-violated (Script number overflow)') wait_until(lambda: len(self.nodes[0].listbanned()) == 1, timeout=5) # and banned self.nodes[0].clearbanned() wait_until(lambda: len(self.nodes[0].listbanned()) == 0, timeout=5) # and not banned rejected_txs = [] # node was banned we need to restart the node self.restart_network() # TODO: This sleep needs to be replaced with a proper wait_until function sleep(3) self.test.connections[0].cb.on_reject = on_reject # generate a block, height is genesis gracefull height - 2 block(1) # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block txOpElse1 = create_transaction(out[1].tx, out[1].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) #update Block with OP_ELSE transaction self.chain.update_block(1, [txOpElse1]) yield self.accepted() # generate a block, height is genesis gracefull height - 1 block(2) # Create transaction that spends the previous transaction txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE])) # Update block with new transactions. self.chain.update_block(2, [txOpElse2]) yield self.accepted() assert_equal(len(rejected_txs), 0) #not rejected assert len(self.nodes[0].listbanned()) == 0 # and not banned # Create transaction with OP_ELSE in the locking script and send it to mempool which should be accepted txOpElse1 = create_transaction(out[2].tx, out[2].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.test.connections[0].send_message(msg_tx(txOpElse1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse1]) txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElse2)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse2]) assert_equal(len(rejected_txs), 0) # not rejected assert len(self.nodes[0].listbanned()) == 0 # and not banned # generate a block to move into genesis gracefull height block(3) # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block and will be spent later when we move into Genesis txOpElseIsSpentInGenesis1 = create_transaction(out[3].tx, out[3].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.chain.update_block(3, [txOpElseIsSpentInGenesis1]) yield self.accepted() # Create transaction with OP_ELSE in the locking script and send it to mempool which should be accepted txOpElse1 = create_transaction(out[4].tx, out[4].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.test.connections[0].send_message(msg_tx(txOpElse1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse1]) txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElse2)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse2]) assert_equal(len(rejected_txs), 0) #not rejected assert len(self.nodes[0].listbanned()) == 0 # and not banned # Create transaction with OP_ADD in the locking script and send it to mempool # which should not be banned (but should be rejected instead), because we are in Genesis gracefull period now txOpAdd1 = create_transaction(out[5].tx, out[5].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd1]) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd2)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Script number overflow)') assert len(self.nodes[0].listbanned()) == 0 # and not banned rejected_txs = [] # generate a block, height is genesis gracefull height + 1 block(4) # Create transaction with OP_ELSE in the locking script which should be accepted to block txOpElse1 = create_transaction(out[6].tx, out[6].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) #update Block with OP_ELSE transaction self.chain.update_block(4, [txOpElse1]) yield self.accepted() # generate a block, height is genesis gracefull height + 2 block(5) # Create transaction that spends the previous transaction txOpElse2 = create_transaction(txOpElse1, 0, b'', 1, CScript([OP_TRUE])) # Update block with new transactions. self.chain.update_block(5, [txOpElse2]) yield self.accepted() assert_equal(len(rejected_txs), 0) # not rejected assert len(self.nodes[0].listbanned()) == 0 # not banned # Create transaction that will check if CheckRegularTransaction method inside TxnValidation method will reject instead ban the node txPubKeys = create_transaction(out[7].tx, out[7].n, b'', 100000, CScript(([OP_1] + makePubKeys(1) + [5000, OP_CHECKMULTISIG])*1001)) self.test.connections[0].send_message(msg_tx(txPubKeys)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'flexible-bad-txn-sigops') assert len(self.nodes[0].listbanned()) == 0 # not banned rejected_txs = [] #now we need to raise block count so we are in genesis height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'] for x in range(height, self.genesisactivationheight): block(6000 + x) test.blocks_and_transactions.append([self.chain.tip, True]) yield test height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'] assert_equal(height, self.genesisactivationheight) # check if we are in right height # Create transaction with OP_ELSE in the locking script and send it to mempool # which should not be banned but should be rejected now txOpElse1 = create_transaction(out[8].tx, out[8].n, b'', 100004, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.test.connections[0].send_message(msg_tx(txOpElse1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse1]) # Create transaction that spends the previous transaction txOpElse2 = create_transaction(txOpElse1, 0, b'', 4, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElse2)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Invalid OP_IF construction)') assert len(self.nodes[0].listbanned()) == 0 # not banned rejected_txs = [] # generate an empty block, height is Genesis + 1 block(6) #Create transaction with OP_ADD in the locking script that should be accepted to block txOpAdd1 = create_transaction(out[9].tx, out[9].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.chain.update_block(6, [txOpAdd1]) yield self.accepted() # generate an empty block, height is Genesis + 2 and make block(7) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE])) self.chain.update_block(7, [txOpAdd2]) yield self.accepted() assert_equal(len(rejected_txs), 0) #not rejected assert len(self.nodes[0].listbanned()) == 0 # not banned # Create transaction with OP_ADD in the locking script and send it to mempool # which should not be banned (but should be rejected instead), because we are in genesis gracefull period now txOpAdd1 = create_transaction(out[10].tx, out[10].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd1]) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd2)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd2]) assert_equal(len(rejected_txs), 0) # not rejected assert len(self.nodes[0].listbanned()) == 0 # not banned # Create transaction that spends the OP_ELSE transaction that was put into block before Genesis txOpElseIsSpentInGenesis2 = create_transaction(txOpElseIsSpentInGenesis1, 0, b'', 4, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElseIsSpentInGenesis2)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElseIsSpentInGenesis2]) assert_equal(len(rejected_txs), 0) #not rejected assert len(self.nodes[0].listbanned()) == 0 # not banned # generate a block block(8) # Create transaction with multiple OP_ELSE in the locking script which should be accepted to block and will be spent later when we move into Genesis txOpElseIsSpentInGenesis3 = create_transaction(out[11].tx, out[11].n, b'', 100000, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.chain.update_block(8, [txOpElseIsSpentInGenesis3]) yield self.accepted() # Create transaction that spends the OP_ELSE transaction that was put into block after Genesis txOpElseIsSpentInGenesis4 = create_transaction(txOpElseIsSpentInGenesis3, 0, b'', 4, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElseIsSpentInGenesis4)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'genesis-script-verify-flag-failed (Invalid OP_IF construction)') assert len(self.nodes[0].listbanned()) == 0 # not banned rejected_txs = [] height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'] genesisHeightNoGracefullPeriod = self.genesisactivationheight + int(GENESIS_GRACEFULL_ACTIVATION_PERIOD) #now we need to raise block count so we are in genesis but before gracefull period is over for x in range(height, genesisHeightNoGracefullPeriod): block(6000 + x) test.blocks_and_transactions.append([self.chain.tip, True]) yield test height = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'] assert_equal(height, self.genesisactivationheight + int(GENESIS_GRACEFULL_ACTIVATION_PERIOD)) # check if we are in right height # generate an empty block, height is Genesis + gracefull period + 1, we moved beyond gracefull period now block(9, spend=out[12]) yield self.accepted() # generate a block, height is Genesis + gracefull period + 2 block(10) #Create transaction with OP_ADD in the locking script that should be accepted to block txOpAdd1 = create_transaction(out[13].tx, out[13].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.chain.update_block(10, [txOpAdd1]) yield self.accepted() # generate a block, height is Genesis + gracefull period + 3 block(11) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE])) self.chain.update_block(11, [txOpAdd2]) yield self.accepted() assert_equal(len(rejected_txs), 0) # accepted assert len(self.nodes[0].listbanned()) == 0 # not banned # Create transaction with OP_ADD in the locking script and send it to mempool # which should not be banned (but should be rejected instead), because we are in genesis gracefull period now txOpAdd1 = create_transaction(out[14].tx, out[14].n, b'', 100003, CScript([b'\xFF'*4, b'\xFF'*4, OP_ADD, OP_4, OP_ADD, OP_DROP, OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd1]) # Create transaction that spends the previous transaction txOpAdd2 = create_transaction(txOpAdd1, 0, b'', 3, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpAdd2)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpAdd2]) assert_equal(len(rejected_txs), 0) # accepted assert len(self.nodes[0].listbanned()) == 0 # not banned # Create transaction with OP_ELSE in the locking script which should now be banned txOpElse1 = create_transaction(out[15].tx, out[15].n, b'', 100004, CScript([OP_FALSE, OP_IF, OP_FALSE, OP_ELSE, OP_TRUE, OP_ELSE, OP_FALSE, OP_ENDIF])) self.test.connections[0].send_message(msg_tx(txOpElse1)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txOpElse1]) # Create transaction that spends the previous transaction txOpElse2 = create_transaction(txOpElse1, 0, b'', 4, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txOpElse2)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) #rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'mandatory-script-verify-flag-failed (Invalid OP_IF construction)') wait_until(lambda: len(self.nodes[0].listbanned()) == 1, timeout=5) # banned rejected_txs = [] self.nodes[0].clearbanned() wait_until(lambda: len(self.nodes[0].listbanned()) == 0, timeout=5) # and not banned rejected_txs = [] # node was banned we need to restart the node self.restart_network() # TODO: This sleep needs to be replaced with a proper wait_until function sleep(3) self.test.connections[0].cb.on_reject = on_reject # Create transaction with OP_NOP that exceeds policy limits to check that node does not get banned for exceeding our policy limit txPubKeys = create_transaction(out[16].tx, out[16].n, b'', 100004, CScript([OP_TRUE] + [OP_NOP] * 1001)) self.test.connections[0].send_message(msg_tx(txPubKeys)) # wait for transaction processing self.check_mempool(self.test.connections[0].rpc, [txPubKeys]) # Create transaction that spends the previous transaction txPubKeys2 = create_transaction(txPubKeys, 0, b'', 4, CScript([OP_TRUE])) self.test.connections[0].send_message(msg_tx(txPubKeys2)) # wait for transaction processing wait_until(lambda: len(rejected_txs) > 0, timeout=5, lock=mininode_lock) # rejected assert_equal(len(rejected_txs), 1) assert_equal(rejected_txs[0].reason, b'non-mandatory-script-verify-flag (Operation limit exceeded)') assert len(self.nodes[0].listbanned()) == 0 # banned
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): 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): 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)) _, 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 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 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, 105, 100) yield test # Block with height 107. block(1, spend=out[0]) yield self.accepted() # Create block with height 108 (genesis NOT activated). block(2, spend=out[1]) tx0 = create_transaction(out[2].tx, out[2].n, b"", 100000, CScript([OP_RETURN])) self.chain.update_block(2, [tx0]) # \x51 is OP_TRUE tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE])) b108_rejected = self.chain.update_block(2, [tx1]) self.log.info( "Created block %s on height %d that tries to spend from block on height %d.", b108_rejected.hash, self.genesisactivationheight - 1, self.genesisactivationheight - 1) yield self.rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) # Rewind bad block (height is 107). self.chain.set_tip(1) # Create block on height 108 (genesis NOT activated). block(3, spend=out[3]) tx0 = create_transaction(out[4].tx, out[4].n, b"", 100000, CScript([OP_RETURN])) b108 = self.chain.update_block(3, [tx0]) self.log.info("Created block %s on height %d.", b108.hash, self.genesisactivationheight - 1) yield self.accepted() # Create block on height 109 and try to spend from block on height 108 block(4, spend=out[5]) # \x51 is OP_TRUE tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE])) b109_rejected = self.chain.update_block(4, [tx1]) self.log.info( "Created block %s on height %d that tries to spend from block on height %d.", b109_rejected.hash, self.genesisactivationheight, self.genesisactivationheight - 1) yield self.rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) # Rewind bad block (height is 108). self.chain.set_tip(3) # Create block on height 109 and try to spend from block on height 109. block(5, spend=out[6]) tx0 = create_transaction(out[7].tx, out[7].n, b"", 100000, CScript([OP_RETURN])) self.chain.update_block(5, [tx0]) # \x51 is OP_TRUE tx1 = create_transaction(tx0, 0, b'\x51', 1, CScript([OP_TRUE])) b109_accepted = self.chain.update_block(5, [tx1]) self.log.info( "Created block %s on height %d that tries to spend from block on height %d.", b109_accepted.hash, self.genesisactivationheight, self.genesisactivationheight) yield self.accepted() ############# # At this point, we have tx0 and tx1 in cache script marked as valid. # Now, invalidate blocks 109 and 108 so that we are in state before genesis. node.invalidateblock(hashToHex(b109_accepted.sha256)) sleep(1) # tx0 and tx1 are in mempool (currently valid because it was sent after genesis) assert_equal(True, tx0.hash in node.getrawmempool()) assert_equal(True, tx1.hash in node.getrawmempool()) node.invalidateblock(hashToHex(b108.sha256)) # tx0 and tx1 are not in mempool (mempool is deleted when 108 is invalidated) assert_equal(False, tx0.hash in node.getrawmempool()) assert_equal(False, tx1.hash in node.getrawmempool()) # So we are at height 107. assert_equal(node.getblock(node.getbestblockhash())['height'], 107) self.nodes[0].generate(1) tx = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['tx'] assert_equal(False, tx0.hash in tx) assert_equal(False, tx1.hash in tx) # Now we are at height 108. assert_equal(node.getblock(node.getbestblockhash())['height'], 108)
def get_tests(self): # Shorthand for functions block = self.chain.next_block # Get proxy with bigger timeout node = get_rpc_proxy(self.nodes[0].url, 1, timeout=6000, coveragedir=self.nodes[0].coverage_dir) self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) block(0) yield self.accepted() test, out, _ = prepare_init_chain(self.chain, 200, 200) yield test # Create transaction that will almost fill block file when next block will be generated (~130 MB) tx1 = create_transaction( out[0].tx, out[0].n, b"", ONE_MEGABYTE * 120, CScript( [OP_FALSE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE * 120))])) self.test.connections[0].send_message(msg_tx(tx1)) # Wait for transaction processing self.check_mempool(node, [tx1], timeout=6000) # Mine block with new transaction. minedBlock1 = node.generate(1) # Send 4 large (~1GB) transactions that will go into next block for i in range(4): txLarge = create_transaction( out[1 + i].tx, out[1 + i].n, b"", ONE_GIGABYTE, CScript([ OP_FALSE, OP_RETURN, bytearray([42] * (ONE_GIGABYTE - ONE_MEGABYTE)) ])) self.test.connections[0].send_message(msg_tx(txLarge)) self.check_mempool(node, [txLarge], timeout=6000) # Send transaction with size that will overflow 32 bit size txOverflow = create_transaction( out[5].tx, out[5].n, b"", ONE_MEGABYTE * 305, CScript( [OP_FALSE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE * 305))])) self.test.connections[0].send_message(msg_tx(txOverflow)) self.check_mempool(node, [txOverflow], timeout=6000) # Mine block with new transactions with size > 4GB. This will write to new block file on disk. minedBlock2 = node.generate(1) # Make sure that block is larger than 32 bit max assert_greater_than(node.getblock(minedBlock2[0], True)['size'], 2**32) # Generate another transaction for next block tx2 = create_transaction( out[10].tx, out[10].n, b"", ONE_MEGABYTE, CScript([OP_FALSE, OP_RETURN, bytearray([42] * (ONE_MEGABYTE))])) self.test.connections[0].send_message(msg_tx(tx2)) self.check_mempool(node, [tx2], timeout=6000) # Mine block with new transactions. This will write to new block file on disk. minedBlock3 = node.generate(1) # Get block count blockcount = node.getblockcount() # Restart node with reindex option self.stop_nodes() self.extra_args[0].append("-reindex") self.start_nodes(self.extra_args) # Get proxy with bigger timeout node = get_rpc_proxy(self.nodes[0].url, 1, timeout=6000, coveragedir=self.nodes[0].coverage_dir) # Get block count after reindex and compare it to block count before node shutdown - should be equal while node.getblockcount() < blockcount: sleep(0.1) assert_equal(node.getblockcount(), blockcount)
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)) _, 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 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)