def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.chain = ChainManager() self.extra_args = [["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=legacy"], ["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=journaling"]] self.block_count = 0
class PBVSameBlock(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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() # send one to get out of IBD state self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 node0.send_message(msg_block(block)) self.nodes[0].waitforblockheight(1) block = self.chain.next_block(block_count) # set block validating status to wait after validation self.nodes[0].waitaftervalidatingblock(block.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block)) node1.send_message(msg_block(block)) # make sure we started validating blocks. # One is validating the other is ignored. wait_for_validating_blocks({block.hash}, self.nodes[0], self.log) # wait for the log of the ignored block. wait_until(lambda: check_for_log_msg(self, block.hash + " will not be considered by the current", "/node0")) # remove block validating status to finish validation self.nodes[0].waitaftervalidatingblock(block.hash, "remove") # wait till validation of block finishes node0.sync_with_ping() self.nodes[0].waitforblockheight(2) assert_equal(block.hash, self.nodes[0].getbestblockhash())
class PBVSameBlock(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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() # send one to get out of IBD state self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) ancestor_block_hash = self.nodes[0].getbestblockhash() parent_block = self.chain.next_block(block_count) block_count += 1 headers_message = msg_headers() headers_message.headers = [CBlockHeader(parent_block)] connection.cb.send_message(headers_message) child_block = self.chain.next_block(block_count) node0.send_message(msg_block(child_block)) # wait till validation of block finishes node0.sync_with_ping() assert_equal(ancestor_block_hash, self.nodes[0].getbestblockhash()) self.stop_node(0) self.start_node(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() assert_equal(ancestor_block_hash, self.nodes[0].getbestblockhash()) node0.send_message(msg_block(parent_block)) # wait till validation of block finishes node0.sync_with_ping() assert_equal(child_block.hash, self.nodes[0].getbestblockhash())
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [["-whitelist=127.0.0.1"]] self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.chain = ChainManager()
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-maxparallelblocks=3", "-maxparallelblocksperpeer=3" ]]
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999" ]] self.block_count = 0
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-softconsensusfreezeduration=4", "-disablesafemode=1" ]] self.block_count = 0
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-blockmintxfee=0", "-acceptnonstdtxn=1", "-minrelaytxfee=0", "-softconsensusfreezeduration=4" ], [ "-whitelist=127.0.0.1", "-blockmintxfee=0", "-acceptnonstdtxn=1", "-minrelaytxfee=0", "-softconsensusfreezeduration=4" ]] self.block_count = 0
class WaitAfterValidation(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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() 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 node0.send_message(msg_block(block)) block = self.chain.next_block(block_count) self.log.info(f"block hash: {block.hash}") self.nodes[0].waitaftervalidatingblock(block.hash, "add") # make sure block hash is in waiting list wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log) self.log.info("sending block") node0.send_message(msg_block(block)) # make sure we started validating block wait_for_validating_blocks({block.hash}, self.nodes[0], self.log) # sleep a bit and check that in the meantime validation hasn't proceeded time.sleep(1) assert (block.hash != self.nodes[0].getbestblockhash()) # after validating the block we release its waiting status self.nodes[0].waitaftervalidatingblock(block.hash, "remove") # wait till validation of block or blocks finishes node0.sync_with_ping() assert_equal(block.hash, self.nodes[0].getbestblockhash())
class FrozenTXOSoftConsensusFreeze(SoftConsensusFreezeBase): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999" ]] self.block_count = 0 def _test_default_freeze(self, spendable_out, node): self.log.info("*** Performing soft consensus freeze checks") first_frozen_tx = self._create_tx_mine_block_and_freeze_tx( node, spendable_out) # block is rejected as consensus freeze is in effect for parent transaction first_spend_frozen_tx = self._create_tx( PreviousSpendableOutput(first_frozen_tx, 0), b'', CScript([OP_TRUE])) first_frozen_block = self._mine_and_check_rejected( node, first_spend_frozen_tx) # both blocks are still frozen self._mine_and_send_block(None, node, False, node.rpc.getbestblockhash()) self._mine_and_send_block(None, node, False, node.rpc.getbestblockhash()) last_frozen_block = self._mine_and_send_block( None, node, False, node.rpc.getbestblockhash()) bestblockhash = node.rpc.getbestblockhash() # check that precious block does not have impact on soft freeze duration node.rpc.preciousblock(last_frozen_block.hash) # assert preciousblock did not change the tip assert (bestblockhash == node.rpc.getbestblockhash()) # all blocks are unfrozen self._mine_and_send_block(None, node) def run_test(self): node = self._init() send_node = Send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0]) spendable_out_1 = self.chain.get_spendable_output() self._test_default_freeze(spendable_out_1, send_node)
def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.block_count = 0
class PBVCallGetDataBeforeBlockIsValidated(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.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) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node0.wait_for_verack() # send one to get out of IBD state self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(block_count) block_count += 1 node0.send_message(msg_block(block)) self.nodes[0].waitforblockheight(1) block_count = self.__test_getdata(node0, block_count) block_count = self.__test_getblocks(node0, block_count) def __test_getdata(self, node0, block_count): block = self.chain.next_block(block_count) block_count += 1 self.log.info(f"block hash: {block.hash}") self.__send_blocking_validation_block(block, node0) receivedBlock = False def on_block(conn, message): nonlocal receivedBlock message.block.calc_sha256() if message.block.sha256 == block.sha256: receivedBlock = True node0.on_block = on_block node0.send_message(msg_getdata([CInv(CInv.BLOCK, int(block.hash, 16))])) wait_until(lambda: check_for_log_msg(self, block.hash + " is still waiting as a candidate", "/node0")) # remove block validating status to finish validation self.nodes[0].waitaftervalidatingblock(block.hash, "remove") def wait_for_getdata_reply(): return receivedBlock wait_until(wait_for_getdata_reply) return block_count def __test_getblocks(self, node0, block_count): block1 = self.chain.next_block(block_count) block_count += 1 self.log.info(f"block1 hash: {block1.hash}") self.__send_blocking_validation_block(block1, node0) receivedBlock = False def on_block(conn, message): nonlocal receivedBlock message.block.calc_sha256() if message.block.sha256 == block1.sha256: receivedBlock = True node0.on_block = on_block node0.send_message(msg_getblocks()) block2 = self.chain.next_block(block_count) block_count += 1 self.log.info(f"block2 hash: {block2.hash}") self.__send_blocking_validation_waiting_block(block2, node0) wait_until(lambda: check_for_log_msg(self, "Blocks that were received before getblocks message", "/node0")) # remove block validating status to finish validation self.nodes[0].waitaftervalidatingblock(block1.hash, "remove") def wait_for_getblocks_reply(): return receivedBlock wait_until(wait_for_getblocks_reply) # remove block validating status to finish validation self.nodes[0].waitaftervalidatingblock(block2.hash, "remove") # wait till validation of block finishes node0.sync_with_ping() return block_count def __send_blocking_validation_waiting_block(self, block, node0): # set block validating status to wait after validation self.nodes[0].waitaftervalidatingblock(block.hash, "add") # make sure block hash is in the waiting list wait_for_waiting_blocks({block.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block)) def __send_blocking_validation_block(self, block, node0): self.__send_blocking_validation_waiting_block(block, node0) wait_for_validating_blocks({block.hash}, self.nodes[0], self.log)
class FrozenTXOTransactionMining(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.chain = ChainManager() self.extra_args = [["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=legacy"], ["-disablesafemode=1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-blockmintxfee=0", "-blockassembler=journaling"]] self.block_count = 0 def init_(self, nodes_count): nodes = [] for no in range(0, nodes_count): # Create a P2P connections node = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(no), self.nodes[no], node) node.add_connection(connection) nodes.append(node) NetworkThread().start() for no in range(0, nodes_count): # wait_for_verack ensures that the P2P connection is fully up. nodes[no].wait_for_verack() self.init_chain_(nodes[0], nodes_count) return nodes def init_chain_(self, leading_node, nodes_count): self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) block = self.chain.next_block(self.block_count) self.block_count += 1 self.chain.save_spendable_output() leading_node.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(self.block_count) self.block_count += 1 self.chain.save_spendable_output() leading_node.send_message(msg_block(block)) self.log.info("Waiting for block height 101 via rpc") for no in range(0, nodes_count): self.nodes[no].waitforblockheight(101) def mine_and_send_block_(self, tx, node): block = self.chain.next_block(self.block_count) self.chain.update_block(self.block_count, [tx]) node.send_and_ping(msg_block(block)) self.block_count += 1 self.log.debug(f"attempted mining block: {block.hash}") assert_equal(block.hash, self.nodes[0].getbestblockhash()) def create_tx_(self, tx_out, unlock, lock): unlock_script = b'' if callable(unlock) else unlock tx = create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock) if callable(unlock): tx.vin[0].scriptSig = unlock(tx, tx_out.tx) tx.calc_sha256() return tx def check_log(self, node, line_text): for line in open(glob.glob(node.datadir + "/regtest/bitcoind.log")[0]): if re.search(line_text, line) is not None: self.log.debug("Found line in bitcoind.log: %s", line.strip()) return True return False def run_test(self): (node0, node1) = self.init_(2) out = self.chain.get_spendable_output() freeze_tx = self.create_tx_(out, b'', CScript([OP_TRUE])) self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later") self.mine_and_send_block_(freeze_tx, node0) spend_frozen_tx = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE])) self.log.info(f"Sending transaction {spend_frozen_tx.hash} spending TXO {freeze_tx.hash},0") node0.send_and_ping(msg_tx(spend_frozen_tx)) spend_frozen_tx2 = self.create_tx_(PreviousSpendableOutput(spend_frozen_tx, 0), b'', CScript([OP_TRUE])) self.log.info(f"Sending transaction {spend_frozen_tx2.hash} spending TXO {spend_frozen_tx.hash},0") node0.send_and_ping(msg_tx(spend_frozen_tx2)) sync_mempools(self.nodes) self.log.info("Checking that transactions were accepted on both nodes") for no in range(0, 2): mp = self.nodes[no].getrawmempool() assert_equal(len(mp), 2) assert(spend_frozen_tx.hash in mp and spend_frozen_tx2.hash in mp) template_txns = self.nodes[no].getblocktemplate()["transactions"] assert_equal(len(template_txns), 2) bt = [template_txns[0]['txid'], template_txns[1]['txid']] assert(spend_frozen_tx.hash in mp and spend_frozen_tx2.hash in bt) current_height = self.nodes[0].getblockcount() self.log.info(f"Current height: {current_height}") enforce_height = current_height + 2 self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist at height {enforce_height} on both nodes") for no in range(0, 2): self.nodes[no].addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height}], "policyExpiresWithConsensus": False }] }); self.log.info("Checking that both transactions were removed from mempool and block template on both nodes") for no in range(0, 2): assert_equal(self.nodes[no].getrawmempool(), []) assert_equal(self.nodes[no].getblocktemplate()["transactions"], []) enforce_stop_height = enforce_height + 1 self.log.info(f"Unfreezing TXO {freeze_tx.hash},0 from consensus and policy blacklists at height {enforce_stop_height} on both nodes") for no in range(0, 2): self.nodes[no].addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height, "stop": enforce_stop_height}], "policyExpiresWithConsensus": True }] }); self.log.info(f"Generating blocks so that mempool reaches height {enforce_stop_height+1}") while self.nodes[0].getblockcount() < enforce_stop_height: self.nodes[0].generate(1) sync_blocks(self.nodes) spend_unfrozen_tx3 = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_NOP, OP_TRUE])) self.log.info(f"Sending transaction {spend_unfrozen_tx3.hash} spending now unfrozen TXO {freeze_tx.hash},0") node0.send_and_ping(msg_tx(spend_unfrozen_tx3)) spend_unfrozen_tx4 = self.create_tx_(PreviousSpendableOutput(spend_unfrozen_tx3, 0), b'', CScript([OP_NOP, OP_TRUE])) self.log.info(f"Sending transaction {spend_unfrozen_tx4.hash} spending TXO {spend_unfrozen_tx3.hash},0") node0.send_and_ping(msg_tx(spend_unfrozen_tx4)) sync_mempools(self.nodes) self.log.info("Checking that transactions were accepted on both nodes") for no in range(0, 2): mp = self.nodes[no].getrawmempool() assert_equal(len(mp), 2) assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in mp) template_txns = self.nodes[no].getblocktemplate()["transactions"] assert_equal(len(template_txns), 2) bt = [template_txns[0]['txid'], template_txns[1]['txid']] assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in bt) self.log.info("Invalidating chain tip on both nodes to force reorg back one block") for no in range(0, 2): self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() ) assert(self.nodes[no].getblockcount() == enforce_height) mempool_scan_check_log_string = "Removing any transactions that spend TXOs, which were previously not considered policy frozen" self.log.info("Checking that transactions are still in mempool on both nodes") for no in range(0, 2): mp = self.nodes[no].getrawmempool() assert_equal(len(mp), 2) assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in mp) template_txns = self.nodes[no].getblocktemplate()["transactions"] assert_equal(len(template_txns), 2) bt = [template_txns[0]['txid'], template_txns[1]['txid']] assert(spend_unfrozen_tx3.hash in mp and spend_unfrozen_tx4.hash in bt) # bitcoind sould not unnecessarily scan whole mempool to find transactions that spend TXOs, which could become frozen again. assert( not self.check_log(self.nodes[no], mempool_scan_check_log_string) ) self.log.info("Invalidating chain tip on both nodes to force reorg back to height where TXO is still frozen") for no in range(0, 2): self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() ) assert(self.nodes[no].getblockcount() == enforce_height - 1) self.log.info("Checking that both transactions were removed from mempool and block template on both nodes") for no in range(0, 2): assert_equal(self.nodes[no].getrawmempool(), []) assert_equal(self.nodes[no].getblocktemplate()["transactions"], []) # bitcoind now should scan whole mempool. assert( self.check_log(self.nodes[no], mempool_scan_check_log_string) ) self.log.info("Unfreezing all frozen outputs on both nodes") for no in range(0, 2): self.nodes[no].invalidateblock( self.nodes[no].getbestblockhash() ) result = self.nodes[no].clearBlacklists({ "removeAllEntries" : True }) assert_equal(result["numRemovedEntries"], 1) self.log.info(f"Freezing TXO {freeze_tx.hash},0 on policy blacklist on node0 (but not on node1)") result = self.nodes[0].addToPolicyBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 } }] }); assert_equal(result["notProcessed"], []) spend_frozen_tx1a = self.create_tx_(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_NOP, OP_NOP, OP_TRUE])) self.log.info(f"Sending transaction {spend_frozen_tx1a.hash} spending not frozen TXO {freeze_tx.hash},0 to node1") node1.send_and_ping(msg_tx(spend_frozen_tx1a)) spend_frozen_tx2a = self.create_tx_(PreviousSpendableOutput(spend_frozen_tx1a, 0), b'', CScript([OP_NOP, OP_NOP, OP_TRUE])) self.log.info(f"Sending transaction {spend_frozen_tx2a.hash} spending TXO {spend_frozen_tx1a.hash},0 to node1") node1.send_and_ping(msg_tx(spend_frozen_tx2a)) self.log.info("Checking that transactions were accepted on node1") mp = self.nodes[1].getrawmempool() assert_equal(len(mp), 2) assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in mp) time.sleep(6) # need to wait >5s for the block assembler to create new block template_txns = self.nodes[1].getblocktemplate()["transactions"] assert_equal(len(template_txns), 2) bt = [template_txns[0]['txid'], template_txns[1]['txid']] assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in bt) self.log.info("Checking that transactions are not present in mempool on node0") assert_equal(self.nodes[0].getrawmempool(), []) self.log.info("Generate block that contains both transactions on node1") height_before_block_spending_policy_frozen_txo = self.nodes[1].getblockcount() hash_block_spending_policy_frozen_txo = self.nodes[1].generate(1)[0] sync_blocks(self.nodes) for no in range(0, 2): assert(self.nodes[no].getblockcount() == height_before_block_spending_policy_frozen_txo + 1) assert_equal(self.nodes[no].getrawmempool(), []) assert_equal(self.nodes[no].getblocktemplate()["transactions"], []) self.log.info("Invalidating chain tip on both nodes to force reorg back one block") for no in range(0, 2): self.nodes[no].invalidateblock( hash_block_spending_policy_frozen_txo ) assert(self.nodes[no].getblockcount() == height_before_block_spending_policy_frozen_txo) self.log.info("Checking that transactions are not present in mempool on node0") assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getblocktemplate()["transactions"], []) self.log.info("Checking that transactions were put back to mempool on node1") mp = self.nodes[1].getrawmempool() assert_equal(len(mp), 2) assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in mp) template_txns = self.nodes[1].getblocktemplate()["transactions"] assert_equal(len(template_txns), 2) bt = [template_txns[0]['txid'], template_txns[1]['txid']] assert(spend_frozen_tx1a.hash in mp and spend_frozen_tx2a.hash in bt)
class PBVReorg(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1", "-relaypriority=0"]] 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))
class FrozenTXOTransactionFreeze(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999"]] self.block_count = 0 def _init(self): node_no = 0 # Create a P2P connections node = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), self.nodes[node_no], node) node.add_connection(connection) NetworkThread().start() # wait_for_verack ensures that the P2P connection is fully up. node.wait_for_verack() self.chain.set_genesis_hash(int(self.nodes[node_no].getbestblockhash(), 16)) block = self.chain.next_block(self.block_count) self.block_count += 1 self.chain.save_spendable_output() node.send_message(msg_block(block)) for i in range(100): block = self.chain.next_block(self.block_count) self.block_count += 1 self.chain.save_spendable_output() node.send_message(msg_block(block)) self.log.info("Waiting for block height 101 via rpc") self.nodes[node_no].waitforblockheight(101) return node def _create_tx(self, tx_out, unlock, lock): unlock_script = b'' if callable(unlock) else unlock tx = create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock) if callable(unlock): tx.vin[0].scriptSig = unlock(tx, tx_out.tx) tx.calc_sha256() return tx def _mine_and_send_block(self, tx, node, expect_reject = False): block = self.chain.next_block(self.block_count) self.chain.update_block(self.block_count, [tx] if tx else []) self.log.debug(f"attempting mining block: {block.hash}") node.send_block(block, expect_reject) self.block_count += 1 def _remove_last_block(self): # remove last block from chain manager del self.chain.block_heights[self.chain.blocks[self.block_count-1].sha256] del self.chain.blocks[self.block_count-1] self.block_count -= 1 self.chain.set_tip(self.block_count-1) def _test_policy_freeze(self, spendable_out, node): self.log.info("*** Performing policy freeze checks") freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE])) self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later") self._mine_and_send_block(freeze_tx, node) self.log.info(f"Freezing TXO {freeze_tx.hash},0 on policy blacklist") result = node.rpc.addToPolicyBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 } }] }); assert_equal(result["notProcessed"], []) spend_frozen_tx = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE])) self.log.info(f"Sending transaction spending frozen TXO {freeze_tx.hash},0 and checking that it is rejected") # must not be accepted as parent transaction is frozen node.send_tx(spend_frozen_tx, True) assert_equal(node.rpc.getrawmempool(), []) assert(node.check_frozen_tx_log(spend_frozen_tx.hash)); assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+spend_frozen_tx.hash)); self.log.info(f"Mining block with transaction {spend_frozen_tx.hash} spending frozen TXO {freeze_tx.hash},0 and checking that is accepted") self._mine_and_send_block(spend_frozen_tx, node) # block is still accepted as consensus freeze is not in effect assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash) spend_frozen_tx2 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx, 0), b'', CScript([OP_TRUE])) self.log.info(f"Sending transaction {spend_frozen_tx2.hash} spending TXO {spend_frozen_tx.hash},0 that is not yet frozen") node.send_tx(spend_frozen_tx2) assert_equal(node.rpc.getrawmempool(), [spend_frozen_tx2.hash]) self.log.info(f"Freezing TXO {spend_frozen_tx.hash},0 on policy blacklist") result = node.rpc.addToPolicyBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx.hash, "vout" : 0 } }] }); assert_equal(result["notProcessed"], []) self.log.info(f"Checking that transaction {spend_frozen_tx2.hash} is removed from mempool") assert_equal(node.rpc.getrawmempool(), []) assert(node.check_frozen_tx_log(spend_frozen_tx2.hash)); assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+spend_frozen_tx2.hash)); self.log.info(f"Unfreezing TXO {spend_frozen_tx.hash},0 from policy blacklist") result = node.rpc.removeFromPolicyBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx.hash, "vout" : 0 } }] }); assert_equal(result["notProcessed"], []) self.log.info(f"Sending transaction {spend_frozen_tx2.hash} again and checking that it is accepted") node.send_tx(spend_frozen_tx2) assert_equal(node.rpc.getrawmempool(), [spend_frozen_tx2.hash]) self.log.info(f"Checking that transaction {spend_frozen_tx2.hash} is removed from mempool if TXO is re-frozen") result = node.rpc.addToPolicyBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx.hash, "vout" : 0 } }] }); assert_equal(result["notProcessed"], []) assert_equal(node.rpc.getrawmempool(), []) def _test_consensus_freeze(self, spendable_out, node): self.log.info("*** Performing consensus freeze checks") # Helper to send tx and check it is rejected because of frozen inputs def SendTxAndCheckRejected(tx): self.log.info(f"Sending transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is rejected") node.send_tx(tx, True) assert_equal(node.rpc.getrawmempool(), []) assert(node.check_frozen_tx_log(tx.hash)); assert(node.check_log("Transaction was rejected because it tried to spend a frozen transaction output.*"+tx.hash)); # Helper to send tx and check it is accepted def SendTxAndCheckAccepted(tx): self.log.info(f"Sending transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is accepted") node.send_tx(tx) assert_equal(node.rpc.getrawmempool(), [tx.hash]) # Helper to mine block with tx and check it is rejected because of frozen inputs def MineAndCheckRejected(tx): self.log.info(f"Mining block with transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that it is rejected") old_tip = self.chain.tip self._mine_and_send_block(tx, node, True) assert_equal(node.rpc.getbestblockhash(), old_tip.hash) assert(node.check_frozen_tx_log(self.chain.tip.hash)); assert(node.check_log("Block was rejected because it included a transaction, which tried to spend a frozen transaction output.*"+self.chain.tip.hash)); self._remove_last_block() # Helper to mine block with tx and check it is accepted def MineAndCheckAccepted(tx): self.log.info(f"Mining block with transaction {tx.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n} and checking that is accepted") self._mine_and_send_block(tx, node) assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash) def MineEmptyBlock(): self.log.info(f"Mining block with no transactions to increase height") self._mine_and_send_block(None, node) assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash) freeze_tx = self._create_tx(spendable_out, b'', CScript([OP_TRUE])) self.log.info(f"Mining block with transaction {freeze_tx.hash} whose output will be frozen later") self._mine_and_send_block(freeze_tx, node) self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 }, "enforceAtHeight": [{"start": 0}], "policyExpiresWithConsensus": False }] }); assert_equal(result["notProcessed"], []) # must not be accepted as parent transaction is frozen spend_frozen_tx = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE])) SendTxAndCheckRejected(spend_frozen_tx) # block is rejected as consensus freeze is in effect MineAndCheckRejected(spend_frozen_tx) current_height = node.rpc.getblockcount() self.log.info(f"Current height: {current_height}") enforce_height = current_height + 2 self.log.info(f"Freezing TXO {freeze_tx.hash},0 on consensus blacklist at height {enforce_height}") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : freeze_tx.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height}], "policyExpiresWithConsensus": False }] }); assert_equal(result["notProcessed"], []) # must not be accepted even if consensus blacklist is not yet enforced spend_frozen_tx2 = self._create_tx(PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE, OP_NOP])) SendTxAndCheckRejected(spend_frozen_tx2) # block is accepted as consensus freeze is not yet enforced at this height MineAndCheckAccepted(spend_frozen_tx2) self.log.info(f"Freezing TXO {spend_frozen_tx2.hash},0 on consensus blacklist at height {enforce_height}") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx2.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height}], "policyExpiresWithConsensus": False }] }); assert_equal(result["notProcessed"], []) # block is rejected as consensus freeze is enforced at this height spend_frozen_tx3 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE])) MineAndCheckRejected(spend_frozen_tx3) self.log.info(f"Unfreezing TXO {spend_frozen_tx2.hash},0 from consensus blacklist at height {enforce_height+2}") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx2.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height, "stop": enforce_height+2}], "policyExpiresWithConsensus": False }] }); assert_equal(result["notProcessed"], []) MineEmptyBlock() # block is rejected as consensus freeze is still enforced at this height spend_frozen_tx3_1 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP])) MineAndCheckRejected(spend_frozen_tx3_1) MineEmptyBlock() # must not be accepted because policy blacklist enforcement does not expire with consensus spend_frozen_tx3_2 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP])) SendTxAndCheckRejected(spend_frozen_tx3_2) self.log.info(f"Unfreezing TXO {spend_frozen_tx2.hash},0 from consensus and policy blacklist at height {enforce_height+2}") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : spend_frozen_tx2.hash, "vout" : 0 }, "enforceAtHeight": [{"start": enforce_height, "stop": enforce_height+2}], "policyExpiresWithConsensus": True }] }); assert_equal(result["notProcessed"], []) # must be accepted because policy blacklist enforcement expires with consensus at this height spend_frozen_tx3_3 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP, OP_NOP])) SendTxAndCheckAccepted(spend_frozen_tx3_3) # block is accepted as consensus freeze is not enforced anymore at this height spend_frozen_tx3_4 = self._create_tx(PreviousSpendableOutput(spend_frozen_tx2, 0), b'', CScript([OP_TRUE, OP_NOP, OP_NOP, OP_NOP, OP_NOP])) MineAndCheckAccepted(spend_frozen_tx3_4) self.log.info("*** Performing consensus freeze checks with several block height enforcement intervals") # Helper to freeze output 0 of given tx on heights [h+1,h+3), [h+5,h+7), where h is current block height # and return a tx that spends that output. def FreezeTXO0(tx): h = node.rpc.getblockcount() self.log.info(f"Current height: {h}") self.log.info(f"Freezing TXO {tx.hash},0 on consensus blacklist at heights [{h+1}, {h+3}), [{h+5}, {h+7})") result=node.rpc.addToConsensusBlacklist({ "funds": [ { "txOut" : { "txId" : tx.hash, "vout" : 0 }, "enforceAtHeight": [{"start": h+1, "stop": h+3}, {"start": h+5, "stop": h+7}], "policyExpiresWithConsensus": False }] }); assert_equal(result["notProcessed"], []) tx2=self._create_tx(PreviousSpendableOutput(tx, 0), b'', CScript([OP_TRUE])) self.log.info(f"Creating transaction {tx2.hash} spending TXO {tx.vin[0].prevout.hash:064x},{tx.vin[0].prevout.n}") return tx2 tx = spend_frozen_tx3_4 # Check first interval tx = FreezeTXO0(tx) MineAndCheckRejected(tx) # block is rejected in first interval MineEmptyBlock() MineAndCheckRejected(tx) MineEmptyBlock() SendTxAndCheckRejected(tx) # tx is rejected because policy freeze also applies in gaps between enforcement intervals MineAndCheckAccepted(tx) # block is accepted as consensus freeze is not enforced in a gap between enforcement intervals # Same as above, but check the second block in a gap between enforcement intervals tx=FreezeTXO0(tx) MineAndCheckRejected(tx) MineEmptyBlock() MineAndCheckRejected(tx) MineEmptyBlock() MineEmptyBlock() SendTxAndCheckRejected(tx) MineAndCheckAccepted(tx) # Check second interval tx=FreezeTXO0(tx) MineAndCheckRejected(tx) MineEmptyBlock() MineAndCheckRejected(tx) MineEmptyBlock() MineEmptyBlock() MineEmptyBlock() MineAndCheckRejected(tx) # block is rejected in second interval MineEmptyBlock() MineAndCheckRejected(tx) MineEmptyBlock() SendTxAndCheckRejected(tx) # tx is rejected because policy freeze also applies after enforcement intervals if policyExpiresWithConsensus=false MineAndCheckAccepted(tx) # block is accepted after the last interval def run_test(self): node = self._init() out_policy_freeze_txo_p2p = self.chain.get_spendable_output() out_consensus_freeze_txo_p2p = self.chain.get_spendable_output() out_policy_freeze_txo_rpc = self.chain.get_spendable_output() out_consensus_freeze_txo_rpc = self.chain.get_spendable_output() # p2p send test p2p_send_node = P2P_send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0]) self._test_policy_freeze(out_policy_freeze_txo_p2p, p2p_send_node) self._test_consensus_freeze(out_consensus_freeze_txo_p2p, p2p_send_node) # rpc send test rpc_send_node = RPC_send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0]) self._test_policy_freeze(out_policy_freeze_txo_rpc, rpc_send_node) self._test_consensus_freeze(out_consensus_freeze_txo_rpc, rpc_send_node)
class PBVWaitAfterValidation(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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)) 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 # 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())
class PBVInvalidate(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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())
class PBVPreciousBlock(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.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)) _, 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) 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())
class PBVProcessingOrder(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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())
class FrozenTXOSoftConsensusFreezeStartup(SoftConsensusFreezeBase): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-blockmintxfee=0", "-acceptnonstdtxn=1", "-minrelaytxfee=0", "-softconsensusfreezeduration=4" ], [ "-whitelist=127.0.0.1", "-blockmintxfee=0", "-acceptnonstdtxn=1", "-minrelaytxfee=0", "-softconsensusfreezeduration=4" ]] self.block_count = 0 def _init(self): # stop node that will be later used to test IDB self.stop_node(1) return super()._init() def run_test(self): node_cb = self._init() node = Send_node(self.options.tmpdir, self.log, 0, node_cb, self.nodes[0]) self.log.info( "*** Testing soft consensus freeze during node startup/IBD") spendable_out = self.chain.get_spendable_output() frozen_tx = self._create_tx_mine_block_and_freeze_tx( node, spendable_out) last_valid_tip_hash = node.rpc.getbestblockhash() last_valid_tip_height = node.rpc.getblockcount() # this block must not become tip because it contains a transaction trying to spend consensus frozen output spend_frozen_tx = self._create_tx( PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE])) self._mine_and_check_rejected(node, spend_frozen_tx) # child blocks are still considered frozen self._mine_and_send_block(None, node, False, last_valid_tip_hash) self._mine_and_send_block(None, node, False, last_valid_tip_hash) self._mine_and_send_block(None, node, False, last_valid_tip_hash) node.restart_node() # must remain at the same tip as before assert_equal(last_valid_tip_hash, node.rpc.getbestblockhash()) self.log.info("Starting second node") self.start_node(1) self.log.info( f"Freezing TXO {frozen_tx.hash},0 on consensus blacklist on second node" ) result = self.nodes[1].addToConsensusBlacklist({ "funds": [{ "txOut": { "txId": frozen_tx.hash, "vout": 0 }, "enforceAtHeight": [{ "start": 0 }], "policyExpiresWithConsensus": False }] }) assert_equal(result["notProcessed"], []) self.log.info("Connecting first and second node") connect_nodes(self.nodes, 1, 0) self.log.info( f"Waiting for block height {last_valid_tip_height} via rpc on second node" ) self.nodes[1].waitforblockheight(last_valid_tip_height) self.log.info( "Checking that tip on second node stays on the last valid block") time.sleep(2) assert_equal(last_valid_tip_hash, self.nodes[1].getbestblockhash()) self.log.info("Disconnecting first and second node") disconnect_nodes(self.nodes[1], 0) # mine another block that should still be frozen self._mine_and_send_block(None, node, False, last_valid_tip_hash) self.log.info("Connecting first and second node") connect_nodes(self.nodes, 1, 0) self.log.info( "Checking that tip on second node stays on the last valid block") time.sleep(2) assert_equal(last_valid_tip_hash, self.nodes[1].getbestblockhash()) # all blocks are unfrozen new_valid_tip = self._mine_and_send_block(None, node) self.nodes[1].waitforblockheight(last_valid_tip_height + 6) assert_equal(new_valid_tip.hash, self.nodes[0].getbestblockhash()) assert_equal(new_valid_tip.hash, self.nodes[1].getbestblockhash())
class PBVWithSigOps(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [["-whitelist=127.0.0.1"]] self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.chain = ChainManager() def sign_expensive_tx(self, tx, spend_tx, n, sigChecks): sighash = SignatureHashForkId( spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue) tx.vin[0].scriptSig = CScript( [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])), self.coinbase_pubkey] * sigChecks + [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])), self.coinbase_pubkey]) def get_hard_transactions(self, spend, money_to_spend, num_of_transactions, num_of_sig_checks, expensive_script): txns = [] for _ in range(0, num_of_transactions): money_to_spend = money_to_spend - 1 # one satoshi to fee tx2 = create_transaction(spend.tx, spend.n, b"", money_to_spend, CScript(expensive_script)) sign_tx(tx2, spend.tx, spend.n, self.coinbase_key) tx2.rehash() txns.append(tx2) money_to_spend = money_to_spend - 1 tx3 = create_transaction(tx2, 0, b"", money_to_spend, scriptPubKey=CScript([OP_TRUE])) self.sign_expensive_tx(tx3, tx2, 0, num_of_sig_checks) tx3.rehash() txns.append(tx3) spend = PreviousSpendableOutput(tx3, 0) return txns 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()
class PBVFirstValidActive(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.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)) _, 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())
class PBVSubmitMiningSolution(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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())
class PBVTerminate(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-maxparallelblocks=3", "-maxparallelblocksperpeer=3" ]] 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)
class PBVProcessingOrder(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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)) _, out, block_count = prepare_init_chain(self.chain, 101, 100, start_block=0, block_0=False, node=node0) 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}") wait_until(lambda: check_for_log_msg( self, block2.hash + " will not be considered by the current", "/node0")) 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())
class PBVSubmitBlock(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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(CInv.BLOCK, block3_easier.sha256)]) # 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(CInv.BLOCK, block3_easier.sha256)]) 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())
class PBVReorg(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]] 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)) 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 # 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())
class PBVWithSigOps(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [["-whitelist=127.0.0.1"]] self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.chain = ChainManager() def sign_tx(self, tx, spend_tx, n): scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend tx.vin[0].scriptSig = CScript() return sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue) tx.vin[0].scriptSig = CScript([ self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) ]) def sign_expensive_tx(self, tx, spend_tx, n, sigChecks): sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue) tx.vin[0].scriptSig = CScript([ self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])), self.coinbase_pubkey ] * sigChecks + [ self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])), self.coinbase_pubkey ]) def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE])): if self.chain.tip == None: base_block_hash = self.chain._genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.chain.tip.sha256 block_time = self.chain.tip.nTime + 1 # First create the coinbase height = self.chain.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value coinbase.rehash() if spend == None: block = create_block(base_block_hash, coinbase, block_time) else: # All but one satoshi for each txn to fees for s in spend: coinbase.vout[0].nValue += s.tx.vout[s.n].nValue - 1 coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # Add as many txns as required for s in spend: # Spend 1 satoshi tx = create_transaction(s.tx, s.n, b"", 1, script) self.sign_tx(tx, s.tx, s.n) self.chain.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() # Do PoW, which is very inexpensive on regnet block.solve() self.chain.tip = block self.chain.block_heights[block.sha256] = height assert number not in self.chain.blocks self.chain.blocks[number] = block return block def get_hard_transactions(self, spend, money_to_spend, num_of_transactions, num_of_sig_checks, expensive_script): txns = [] for _ in range(0, num_of_transactions): money_to_spend = money_to_spend - 1 # one satoshi to fee tx2 = create_transaction(spend.tx, spend.n, b"", money_to_spend, CScript(expensive_script)) self.sign_tx(tx2, spend.tx, spend.n) tx2.rehash() txns.append(tx2) money_to_spend = money_to_spend - 1 tx3 = create_transaction(tx2, 0, b"", money_to_spend, scriptPubKey=CScript([OP_TRUE])) self.sign_expensive_tx(tx3, tx2, 0, num_of_sig_checks) tx3.rehash() txns.append(tx3) spend = PreviousSpendableOutput(tx3, 0) return txns 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)) 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.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) 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.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.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 set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [["-whitelist=127.0.0.1"]]
class FrozenTXOSoftConsensusFreeze(SoftConsensusFreezeBase): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() self.extra_args = [[ "-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-softconsensusfreezeduration=0" ]] self.block_count = 0 def _test_minimal_freeze(self, spendable_out, node): self.log.info( "*** Performing soft consensus freeze checks (freeze for one block)" ) node.restart_node([ "-whitelist=127.0.0.1", "-minrelaytxfee=0", "-limitfreerelay=999999", "-softconsensusfreezeduration=1" ]) frozen_tx = self._create_tx_mine_block_and_freeze_tx( node, spendable_out) # block is rejected as consensus freeze is in effect for parent transaction spend_frozen_tx = self._create_tx( PreviousSpendableOutput(frozen_tx, 0), b'', CScript([OP_TRUE])) self._mine_and_check_rejected(node, spend_frozen_tx) # this block will still be frozen self._mine_and_send_block(None, node, False, node.rpc.getbestblockhash()) # blocks are unfrozen self._mine_and_send_block(None, node) def _test_unlimited_freeze(self, spendable_out, node): self.log.info( "*** Performing soft consensus freeze checks (unlimited)") node.restart_node() first_frozen_tx = self._create_tx_mine_block_and_freeze_tx( node, spendable_out) last_valid_tip_hash = node.rpc.getbestblockhash() # this block is rejected as consensus freeze is in effect for parent transaction first_spend_frozen_tx = self._create_tx( PreviousSpendableOutput(first_frozen_tx, 0), b'', CScript([OP_TRUE])) self.log.info( f"Mining block with transaction {first_spend_frozen_tx.hash} spending TXO {first_spend_frozen_tx.vin[0].prevout.hash:064x},{first_spend_frozen_tx.vin[0].prevout.n}" ) first_frozen_block = self._mine_block(first_spend_frozen_tx) self.log.info(f"Mining descendants of block {first_frozen_block.hash}") subsequent_frozen_blocks = [] for i in range( 14 ): # since we cannot check unlimited number of blocks, number 14 was chosen arbitrarily and we assume it is close enough to infinity for the purpose of this test subsequent_frozen_blocks.append(self._mine_block(None)) # Send block headers first to check that they are also all correctly marked as frozen after the actual block is received. self.log.info( f"Sending headers for block {first_frozen_block.hash} and descendants" ) msg_hdrs = msg_headers() msg_hdrs.headers.append(first_frozen_block) msg_hdrs.headers.extend(subsequent_frozen_blocks) node.p2p.send_and_ping(msg_hdrs) # check that headers were received for tip in node.rpc.getchaintips(): if tip["status"] == "active" and tip["hash"] == last_valid_tip_hash: continue if tip["status"] == "headers-only" and tip[ "hash"] == subsequent_frozen_blocks[-1].hash: continue assert False, "Unexpected tip: " + str(tip) self.log.info( f"Sending block {first_frozen_block.hash} and checking that it is rejected" ) node.send_block(first_frozen_block, last_valid_tip_hash, True) assert (node.check_frozen_tx_log(first_frozen_block.hash)) assert (node.check_log( "Block was rejected because it included a transaction, which tried to spend a frozen transaction output.*" + first_frozen_block.hash)) self.log.info( f"Sending descendants of block {first_frozen_block.hash} and checking that they do not become tip" ) for b in subsequent_frozen_blocks: node.send_block(b, last_valid_tip_hash, False) def run_test(self): node = self._init() send_node = Send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0]) spendable_out_1 = self.chain.get_spendable_output() spendable_out_2 = self.chain.get_spendable_output() self._test_minimal_freeze(spendable_out_1, send_node) self._test_unlimited_freeze(spendable_out_2, send_node)