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 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"]] 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 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 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()
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 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)) 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 = 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, extra_txns=8) block_count += 1 self.chain.set_tip(tip_block_num) block3_easier = self.chain.next_block(block_count, spend=out, extra_txns=2) block_count += 1 self.chain.set_tip(tip_block_num) block4_hard = self.chain.next_block(block_count, spend=out, extra_txns=10) block_count += 1 # send three "hard" blocks, with waitaftervalidatingblock we artificially # extend validation time. self.log.info(f"hard block2 hash: {block2_hard.hash}") self.nodes[0].waitaftervalidatingblock(block2_hard.hash, "add") self.log.info(f"hard block4 hash: {block4_hard.hash}") self.nodes[0].waitaftervalidatingblock(block4_hard.hash, "add") # make sure block hashes are in waiting list wait_for_waiting_blocks({block2_hard.hash, block4_hard.hash}, self.nodes[0], self.log) # *** Complete early announcement setup by sending getheaders message # with a non-null locator (pointing to the last block that we know # of on python side - we claim that we know of all the blocks that # bitcoind node knows of) # # We also set on_cmpctblock handler as early announced blocks are # announced via compact block messages instead of inv messages node2.send_and_ping(msg_getheaders(locator_have=[int(self.nodes[0].getbestblockhash(), 16)])) receivedAnnouncement = False waiting_for_announcement_block_hash = block2_hard.sha256 def on_cmpctblock(conn, message): nonlocal receivedAnnouncement message.header_and_shortids.header.calc_sha256() if message.header_and_shortids.header.sha256 == waiting_for_announcement_block_hash: receivedAnnouncement = True node2.on_cmpctblock = on_cmpctblock # send one block via p2p and one via rpc node0.send_message(msg_block(block2_hard)) # *** make sure that we receive announcement of the block before it has # been validated wait_until(lambda: receivedAnnouncement) # making rpc call submitblock in a separate thread because waitaftervalidation is blocking # the return of submitblock submitblock_thread = threading.Thread(target=self.nodes[0].submitblock, args=(ToHex(block4_hard),)) submitblock_thread.start() # because self.nodes[0] rpc is blocked we use another rpc client rpc_client = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0), 0, coveragedir=self.options.coveragedir) wait_for_validating_blocks({block2_hard.hash, block4_hard.hash}, rpc_client, self.log) # *** prepare to intercept block3_easier announcement - it will not be # announced before validation is complete as early announcement is # limited to announcing one block per height (siblings are ignored) # but after validation is complete we should still get the announcing # compact block message receivedAnnouncement = False waiting_for_announcement_block_hash = block3_easier.sha256 self.log.info(f"easy block3 hash: {block3_easier.hash}") node1.send_message(msg_block(block3_easier)) # *** Make sure that we receive compact block announcement of the block # after the validation is complete even though it was not the first # block that was received by bitcoind node. # # Also make sure that we receive inv announcement of the block after # the validation is complete by the nodes that are not using early # announcement functionality. wait_until(lambda: receivedAnnouncement) node0.wait_for_inv([CInv(2, block3_easier.sha256)]) # 2 == GetDataMsg::MSG_BLOCK # node 1 was the sender but receives inv for block non the less # (with early announcement that's not the case - sender does not receive the announcement) node1.wait_for_inv([CInv(2, block3_easier.sha256)]) # 2 == GetDataMsg::MSG_BLOCK rpc_client.waitforblockheight(102) assert_equal(block3_easier.hash, rpc_client.getbestblockhash()) # now we can remove waiting status from blocks and finish their validation rpc_client.waitaftervalidatingblock(block2_hard.hash, "remove") rpc_client.waitaftervalidatingblock(block4_hard.hash, "remove") submitblock_thread.join() # wait till validation of block or blocks finishes node0.sync_with_ping() # easier block should still be on tip assert_equal(block3_easier.hash, self.nodes[0].getbestblockhash())
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 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 FrozenTXOReindex(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = ChainManager() 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 return create_transaction(tx_out.tx, tx_out.n, unlock_script, 1, lock) 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 return block.hash 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 _mine_and_check_rejected(self, tx, node): 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 rejected_block_hash = 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)) # remove rejected block from test node - the only remaining copy after this point is on remote node disk self._remove_last_block() return rejected_block_hash def _create_policy_freeze_block(self, spendable_out, node): 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"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 accepted as consensus freeze is not in effect assert_equal(node.rpc.getbestblockhash(), self.chain.tip.hash) def _create_consensus_freeze_block(self, spendable_out, node): 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"], []) spend_frozen_tx = self._create_tx( PreviousSpendableOutput(freeze_tx, 0), b'', CScript([OP_TRUE])) # block is rejected as consensus freeze is in effect rejected_block_hash = self._mine_and_check_rejected( spend_frozen_tx, node) return (freeze_tx.hash, rejected_block_hash) def run_test(self): node = self._init() out_policy_freeze_txo = self.chain.get_spendable_output() out_consensus_freeze_txo = self.chain.get_spendable_output() send_node = Send_node(self.options.tmpdir, self.log, 0, node, self.nodes[0]) self._create_policy_freeze_block(out_policy_freeze_txo, send_node) [freeze_tx_hash, rejected_block_hash ] = self._create_consensus_freeze_block(out_consensus_freeze_txo, send_node) node_chain_info = send_node.rpc.getblockchaininfo() old_tip_hash = node_chain_info['bestblockhash'] old_tip_height = node_chain_info['blocks'] assert (rejected_block_hash != old_tip_hash) # Make sure that we get to the same height: # best block with transactions policy frozen - should get to this point # best block with transactions consensus frozen - should not get to this block self.stop_node(0) self.start_node(0, extra_args=["-reindex=1"]) send_node.rpc.waitforblockheight(old_tip_height) assert_equal(send_node.rpc.getbestblockhash(), old_tip_hash) # Unfreeze and reconsider block to show that the block was still stored on disk result = self.nodes[0].clearBlacklists({"removeAllEntries": True}) assert_equal(result["numRemovedEntries"], 2) self.stop_node(0) self.start_node(0, extra_args=["-reindex=1"]) send_node.rpc.waitforblockheight(old_tip_height + 1) self.log.info(send_node.rpc.getblockchaininfo()) assert_equal(send_node.rpc.getbestblockhash(), rejected_block_hash)
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 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)) 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 = 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, 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 GetBlockTemplateRPCTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.chain = ChainManager() def check_mempool(self, rpc, should_be_in_mempool): wait_until(lambda: {t.hash for t in should_be_in_mempool}.issubset( set(rpc.getrawmempool()))) def createLargeTransaction(self, size, depends): tx = CTransaction() for depend in depends: tx.vin.append(CTxIn(COutPoint(depend.sha256, 0), b'')) tx.vout.append(CTxOut(int(100), CScript([OP_RETURN, b"a" * size]))) tx.rehash() return tx def create_bad_block(self, template): coinbase_tx = create_coinbase(height=int(template["height"]) + 1) coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = template["version"] block.nTime = template["curtime"] block.nBits = int(template["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] # Make this block incorrect. block.hashPrevBlock = 123 block.hashMerkleRoot = block.calc_merkle_root() return block def checkBlockTemplate(self, template, txs, dependingTx): assert 'capabilities' in template assert_equal('proposal', template['capabilities'][0]) assert 'version' in template assert 'previousblockhash' in template assert_equal(self.nodes[0].getbestblockhash(), template['previousblockhash']) assert 'transactions' in template # check if hex data was parsed correctly txs_data = [tx['data'] for tx in template['transactions']] assert (ToHex(dependingTx) in txs_data) for tx in txs: assert (ToHex(tx) in txs_data) # check dependencies depending_indices = [] depending_txs_hash = [tx.hash for tx in txs] for i in range(len(template['transactions'])): if template['transactions'][i]['hash'] in depending_txs_hash: depending_indices.append(i + 1) for tmpl_tx in template['transactions']: if tmpl_tx['hash'] == dependingTx.hash: assert_equal(2, len(tmpl_tx['depends'])) assert_equal(set(tmpl_tx['depends']), set(depending_indices)) break assert 'coinbaseaux' in template assert 'coinbasevalue' in template assert 'longpollid' in template assert 'target' in template assert 'mintime' in template assert 'mutable' in template assert 'noncerange' in template assert 'sizelimit' in template assert 'curtime' in template assert 'bits' in template assert 'height' in template assert_equal( self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'] + 1, template['height']) def run_test(self): self.stop_node(0) with self.run_node_with_connections("test getblocktemplate RPC call", 0, ["-blockmintxfee=0.0000001"], 1) as connections: connection = connections[0] # Preparation. self.chain.set_genesis_hash( int(self.nodes[0].getbestblockhash(), 16)) starting_blocks = 101 block_count = 0 for i in range(starting_blocks): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() connection.cb.send_message(msg_block(block)) out = [] for i in range(starting_blocks): out.append(self.chain.get_spendable_output()) self.nodes[0].waitforblockheight(starting_blocks) # Create and send 2 transactions. transactions = [] for i in range(2): tx = create_transaction(out[i].tx, out[i].n, b"", 100000, CScript([OP_TRUE])) connection.cb.send_message(msg_tx(tx)) transactions.append(tx) self.check_mempool(self.nodes[0], transactions) # Create large transaction that depends on previous two transactions. # If transaction pubkey contains 1/2 of BUFFER_SIZE_HttpTextWriter of data, it means that final result will for sure be chunked. largeTx = self.createLargeTransaction( int(BUFFER_SIZE_HttpTextWriter / 2), transactions) connection.cb.send_message(msg_tx(largeTx)) self.check_mempool(self.nodes[0], [largeTx]) # Check getblocktemplate response. template = self.nodes[0].getblocktemplate() self.checkBlockTemplate(template, transactions, largeTx) # Check getblocktemplate with invalid reponse block = self.create_bad_block(template) rsp = self.nodes[0].getblocktemplate({ 'data': b2x(block.serialize()), 'mode': 'proposal' }) assert_equal(rsp, "inconclusive-not-best-prevblk") assert_raises_rpc_error(-22, "Block decode failed", self.nodes[0].getblocktemplate, { 'data': b2x(block.serialize()[:-1]), 'mode': 'proposal' }) # Test getblocktemplate in a batch batch = self.nodes[0].batch([ self.nodes[0].getblockbyheight.get_request(100), self.nodes[0].getblocktemplate.get_request(), self.nodes[0].getblockcount.get_request(), self.nodes[0].undefinedmethod.get_request() ]) assert_equal(batch[0]["error"], None) assert_equal(batch[1]["error"], None) assert_equal(batch[1]["result"], template) assert_equal(batch[2]["error"], None) assert_equal(batch[3]["error"]["message"], "Method not found") assert_equal(batch[3]["result"], None)
class GetRawMempoolTest(BitcoinTestFramework): FORMAT_SEPARATOR = "." def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.extra_args = [['-genesisactivationheight=1']] self.chain = ChainManager() def check_mempool(self, rpc, should_be_in_mempool): wait_until(lambda: {t.hash for t in should_be_in_mempool}.issubset(set(rpc.getrawmempool())), timeout=6000) def createLargeTransaction(self, size, depends): tx = CTransaction() for depend in depends: tx.vin.append(CTxIn(COutPoint(depend.sha256, 0), b'')) tx.vout.append(CTxOut(int(100), CScript([OP_RETURN, b"a" * size]))) tx.rehash() return tx def check_fieldsMempoolEntry(self, mempoolEntry): assert 'size' in mempoolEntry assert 'fee' in mempoolEntry assert 'modifiedfee' in mempoolEntry assert 'time' in mempoolEntry assert 'height' in mempoolEntry assert_equal(self.nodes[0].getblock(self.nodes[0].getbestblockhash())['height'], mempoolEntry['height']) assert 'depends' in mempoolEntry def check_getRawMempool(self, mempool, transactions): for transaction in transactions: assert transaction.hash in mempool self.check_fieldsMempoolEntry(mempool[transaction.hash]) def run_test(self): url = urllib.parse.urlparse(self.nodes[0].url) self.stop_node(0) with self.run_node_with_connections("test getrawMempool RPC call", 0, [], 1) as connections: connection = connections[0] # Preparation. self.chain.set_genesis_hash(int(self.nodes[0].getbestblockhash(), 16)) starting_blocks = 101 block_count = 0 for i in range(starting_blocks): block = self.chain.next_block(block_count) block_count += 1 self.chain.save_spendable_output() connection.cb.send_message(msg_block(block)) out = [] for i in range(starting_blocks): out.append(self.chain.get_spendable_output()) self.nodes[0].waitforblockheight(starting_blocks) # Create and send 2 transactions. transactions = [] for i in range(2): tx = create_transaction(out[i].tx, out[i].n, b"", 100000, CScript([OP_TRUE])) connection.cb.send_message(msg_tx(tx)) transactions.append(tx) self.check_mempool(self.nodes[0], transactions) # Create large transaction that depends on previous two transactions. txSize = 1000 largeTx = self.createLargeTransaction(txSize, transactions) connection.cb.send_message(msg_tx(largeTx)) self.check_mempool(self.nodes[0], [largeTx]) # getrawmempool, verbosity = False assert_equal(set(self.nodes[0].getrawmempool()), set([tx.hash for tx in transactions] + [largeTx.hash])) # getrawmempool, verbosity = True mempool = self.nodes[0].getrawmempool(True) self.check_getRawMempool(mempool, transactions + [largeTx]) assert_equal(mempool[transactions[0].hash]["depends"], []) assert_equal(mempool[largeTx.hash]["depends"], [tx.hash for tx in transactions]) assert_equal(mempool[largeTx.hash]["size"] > txSize, True) # /rest/mempool/contents REST call json_string = http_get_call( url.hostname, url.port, '/rest/mempool/contents' + self.FORMAT_SEPARATOR + 'json') json_obj = json.loads(json_string) self.check_getRawMempool(json_obj, transactions + [largeTx]) assert_equal(mempool[transactions[0].hash]["depends"], []) assert_equal(mempool[largeTx.hash]["depends"], [tx.hash for tx in transactions]) assert_equal(mempool[largeTx.hash]["size"] > txSize, True) # getmempooldescendants, verbosity = False assert_equal(self.nodes[0].getmempooldescendants(transactions[0].hash), [largeTx.hash]) # getmempooldescendants, verbosity = True mempoolDescendants = self.nodes[0].getmempooldescendants(transactions[0].hash, True) self.check_getRawMempool(mempoolDescendants, [largeTx]) assert_equal(mempool[largeTx.hash], mempoolDescendants[largeTx.hash]) # getmempoolancestors, verbosity = False assert_equal(set(self.nodes[0].getmempoolancestors(largeTx.hash)), set([tx.hash for tx in transactions])) # getmempoolancestors, verbosity = True mempoolAncestors = self.nodes[0].getmempoolancestors(largeTx.hash, True) self.check_getRawMempool(mempoolAncestors, transactions) assert_equal(mempool[transactions[0].hash], mempoolAncestors[transactions[0].hash]) # getmempoolentry largeTxEntry = self.nodes[0].getmempoolentry(largeTx.hash) self.check_fieldsMempoolEntry(largeTxEntry) assert_equal(mempool[largeTx.hash], largeTxEntry) # Test those calls in a batch batch = self.nodes[0].batch([ self.nodes[0].getrawmempool.get_request(True), self.nodes[0].getrawmempool.get_request(), self.nodes[0].getmempooldescendants.get_request(transactions[0].hash, True), self.nodes[0].getmempoolancestors.get_request(largeTx.hash), self.nodes[0].getmempoolentry.get_request(largeTx.hash)]) assert_equal(batch[0]["error"], None) assert_equal(batch[0]["result"], mempool) assert_equal(batch[1]["error"], None) assert_equal(batch[2]["result"][largeTx.hash], mempool[largeTx.hash]) assert_equal(batch[2]["error"], None) assert_equal(batch[3]["error"], None) assert_equal(batch[4]["error"], None)
class BSVCheckTTORViolation(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [["-whitelist=127.0.0.1"]] self.chain = ChainManager() # generating transactions in order so first transaction's output will be input for second transaction def get_chained_transactions(self, spend, num_of_transactions): money_to_spend = 5000000000 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([OP_TRUE])) txns.append(tx2) money_to_spend = money_to_spend - 1 tx3 = create_transaction(tx2, 0, b"", money_to_spend, scriptPubKey=CScript([OP_TRUE])) txns.append(tx3) spend = PreviousSpendableOutput(tx3, 0) return txns 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)) getDataMessages = [] def on_getdata(conn, message): getDataMessages.append(message) node0.on_getdata = on_getdata # ***** 1. ***** # starting_blocks are needed to provide spendable outputs starting_blocks = MIN_TTOR_VALIDATION_DISTANCE + 1 for i in range(starting_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(starting_blocks): out.append(self.chain.get_spendable_output()) self.nodes[0].waitforblockheight(starting_blocks) tip_block_index = block_count - 1 self.log.info("Block tip height: %d " % block_count) # ***** 2. ***** # branch with blocks that do not violate TTOR valid_ttor_branch_height = MIN_TTOR_VALIDATION_DISTANCE + 1 for i in range(0, valid_ttor_branch_height): block = self.chain.next_block(block_count, spend=out[i], extra_txns=8) block_count += 1 node0.send_message(msg_block(block)) chaintip_valid_branch = block self.nodes[0].waitforblockheight(starting_blocks + valid_ttor_branch_height) self.log.info("Node's active chain height: %d " % (starting_blocks + valid_ttor_branch_height)) # ***** 3. ***** # branch with invalid transaction order that will try to cause a reorg self.chain.set_tip(tip_block_index) blocks_invalid_ttor = [] headers_message = msg_headers() headers_message.headers = [] invalid_ttor_branch_height = MIN_TTOR_VALIDATION_DISTANCE + 1 for i in range(0, invalid_ttor_branch_height): spend = out[i] block = self.chain.next_block(block_count) add_txns = self.get_chained_transactions(spend, num_of_transactions=10) # change order of transaction that output uses transaction that comes later (makes block violate TTOR) temp1 = add_txns[1] temp2 = add_txns[2] add_txns[1] = temp2 add_txns[2] = temp1 self.chain.update_block(block_count, add_txns) blocks_invalid_ttor.append(block) block_count += 1 if (i == 0): first_block = block # wait with sending header for the last block if (i != MIN_TTOR_VALIDATION_DISTANCE): headers_message.headers.append(CBlockHeader(block)) self.log.info("Sending %d headers..." % MIN_TTOR_VALIDATION_DISTANCE) node0.send_message(headers_message) # Wait to make sure we do not receive GETDATA messages yet. time.sleep(1) # Check that getData is not received until this chain is long at least as the active chain. assert_equal(len(getDataMessages), 0) self.log.info("Sending 1 more header...") # Send HEADERS message for the last block. headers_message.headers = [CBlockHeader(block)] node0.send_message(headers_message) node0.wait_for_getdata() self.log.info("Received GETDATA.") assert_equal(len(getDataMessages), 1) # Send the first block on invalid chain. Chain should be invalidated. node0.send_message(msg_block(first_block)) def wait_to_invalidate_fork(): chaintips = self.nodes[0].getchaintips() if len(chaintips) > 1: chaintips_status = [ chaintips[0]["status"], chaintips[1]["status"] ] if "active" in chaintips_status and "invalid" in chaintips_status: active_chain_tip_hash = chaintips[0]["hash"] if chaintips[ 0]["status"] == "active" else chaintips[1]["hash"] invalid_fork_tip_hash = chaintips[0]["hash"] if chaintips[ 0]["status"] == "invalid" else chaintips[1]["hash"] assert (active_chain_tip_hash != invalid_fork_tip_hash) for block in blocks_invalid_ttor: if block.hash == invalid_fork_tip_hash: return True return False else: return False else: return False wait_until(wait_to_invalidate_fork) # chaintip of valid branch should be active assert_equal(self.nodes[0].getbestblockhash(), chaintip_valid_branch.hash) # check log file that reorg didnt happen disconnect_block_log = False for line in open( glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if f"Disconnect block" in line: disconnect_block_log = True self.log.info("Found line: %s", line.strip()) break # we should not find information about disconnecting blocks assert_equal(disconnect_block_log, False) # check log file that contains information about TTOR violation ttor_violation_log = False for line in open( glob.glob(self.options.tmpdir + "/node0" + "/regtest/bitcoind.log")[0]): if f"violates TTOR order" in line: ttor_violation_log = True self.log.info("Found line: %s", line.strip()) break # we should find information about TTOR being violated assert_equal(ttor_violation_log, True)
class PBVReorgShutdown(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)) 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 to force a reorg self.chain.set_tip(tip_block_num) block3 = self.chain.next_block(block_count, spend=out[1], extra_txns=10) block_count += 1 block4 = self.chain.next_block(block_count, spend=out[1], extra_txns=8) block_count += 1 self.log.info(f"block3 hash: {block3.hash}") self.nodes[0].waitaftervalidatingblock(block3.hash, "add") # make sure block hash is in waiting list wait_for_waiting_blocks({block3.hash}, self.nodes[0], self.log) node0.send_message(msg_block(block3)) node0.send_message(msg_block(block4)) # make sure we started validating blocks wait_for_validating_blocks({block3.hash}, self.nodes[0], self.log) self.stop_node(0)