class PVQTimeoutTest(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.genesisactivationheight = 600 # The coinbase key used. self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() # Locking scripts used in the test. self.locking_script_1 = CScript([self.coinbase_pubkey, OP_CHECKSIG]) self.locking_script_2 = CScript([1, 1, OP_ADD, OP_DROP]) self.locking_script_3 = CScript([ bytearray([42] * DEFAULT_SCRIPT_NUM_LENGTH_POLICY_AFTER_GENESIS), bytearray([42] * 200 * 1000), OP_MUL, OP_DROP ]) self.default_args = [ '-debug', '-maxgenesisgracefulperiod=0', '-genesisactivationheight=%d' % self.genesisactivationheight ] self.extra_args = [self.default_args] * self.num_nodes def run_test(self): self.test.run() def check_rejected(self, rejected_txs, should_be_rejected_tx_set): wait_until(lambda: {tx.data for tx in rejected_txs} == {o.sha256 for o in should_be_rejected_tx_set}, timeout=20) def check_mempool(self, rpc, should_be_in_mempool, timeout=20): wait_until(lambda: set(rpc.getrawmempool()) == {t.hash for t in should_be_in_mempool}, timeout=timeout) # Sign a transaction, using the key we know about. # This signs input 0 in tx, which is assumed to be spending output n in spend_tx def sign_tx(self, tx, spend_tx, n): scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) 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])) ]) # A helper function to generate new txs spending all outpoints from prev_txs set. def generate_transactons(self, prev_txs, unlocking_script, locking_script, fee=2000000, factor=10): generated_txs = [] for prev_tx in prev_txs: for n, vout in enumerate(prev_tx.vout): tx = CTransaction() out_val = vout.nValue - fee tx.vout.extend((CTxOut(out_val, locking_script), ) * factor) tx.vin.append( CTxIn(COutPoint(prev_tx.sha256, n), unlocking_script, 0xffffffff)) tx.calc_sha256() generated_txs.append(tx) return generated_txs # Generate transactions in order so the first transaction's output will be an input for the second transaction. def get_chained_txs(self, spend, num_of_txs, unlocking_script, locking_script, money_to_spend, vout_size): txns = [] for _ in range(0, num_of_txs): # Create a new transaction. tx = create_transaction(spend.tx, spend.n, unlocking_script, money_to_spend, locking_script) # Extend the number of outputs to the required vout_size size. tx.vout.extend(tx.vout * (vout_size - 1)) # Sign txn. self.sign_tx(tx, spend.tx, spend.n) tx.rehash() txns.append(tx) # Use the first outpoint to spend in the second iteration. spend = PreviousSpendableOutput(tx, 0) return txns # Create a required number of chains with equal length. # - each tx is configured to have vout_size outpoints with the same locking_script. def get_txchains_n(self, num_of_chains, chain_length, spend, unlocking_script, locking_script, money_to_spend, vout_size): if num_of_chains > len(spend): raise Exception('Insufficient number of spendable outputs.') txchains = [] for x in range(0, num_of_chains): txchains += self.get_chained_txs(spend[x], chain_length, unlocking_script, locking_script, money_to_spend, vout_size) return txchains # A helper function to create and send a set of tx chains. def generate_and_send_txchains_n(self, conn, num_of_chains, chain_length, spend, locking_script, money_to_spend=2000000, vout_size=10, timeout=60): # Create and send txs. In this case there will be num_txs_to_create txs of chain length equal 1. txchains = self.get_txchains_n(num_of_chains, chain_length, spend, CScript(), locking_script, money_to_spend, vout_size) for tx in range(len(txchains)): conn.send_message(msg_tx(txchains[tx])) # Check if the validation queues are empty. wait_until( lambda: conn.rpc.getblockchainactivity()["transactions"] == 0, timeout=timeout) return txchains # # Pre-defined testing scenarios. # # This scenario is being used to generate and send a set of standard txs in test cases. # - there will be num_txs_to_create txs of chain length equal 1. def run_scenario1(self, conn, spend, num_txs_to_create, locking_script, money_to_spend=2000000, vout_size=10, timeout=60): return self.generate_and_send_txchains_n(conn, num_txs_to_create, 1, spend, locking_script, money_to_spend, vout_size, timeout) # This scenario is being used to generate and send a set of non-standard txs in test cases. # - there will be num_txs_to_create txs of chain length equal 1. def run_scenario2(self, conn, spend, num_txs_to_create, locking_script, additional_txs=[], shuffle_txs=False, money_to_spend=2000000, timeout=60): # A handler to catch any reject messages. # - it is expected to get only 'too-long-validation-time' reject msgs. rejected_txs = [] def on_reject(conn, msg): assert_equal(msg.reason, b'too-long-validation-time') rejected_txs.append(msg) conn.cb.on_reject = on_reject # Create and send tx chains with non-std outputs. # - one tx with vout_size=num_txs_to_create outpoints will be created txchains = self.generate_and_send_txchains_n(conn, 1, 1, spend, locking_script, money_to_spend, num_txs_to_create, timeout) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, txchains, timeout) # Create a new block # - having an empty mempool (before submitting non-std txs) will simplify further checks. conn.rpc.generate(1) # Create and send transactions spending non-std outputs. nonstd_txs = self.generate_transactons(txchains, CScript([OP_TRUE]), locking_script) all_txs = nonstd_txs + additional_txs if shuffle_txs: random.shuffle(all_txs) for tx in all_txs: conn.send_message(msg_tx(tx)) # Check if the validation queues are empty. wait_until( lambda: conn.rpc.getblockchainactivity()["transactions"] == 0, timeout=timeout) return nonstd_txs + additional_txs, rejected_txs def get_tests(self): # Shorthand for functions block = self.chain.next_block node = self.nodes[0] self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) # Create a new block block(0, coinbase_pubkey=self.coinbase_pubkey) self.chain.save_spendable_output() yield self.accepted() # Now we need that block to mature so we can spend the coinbase. # Also, move block height on beyond Genesis activation. test = TestInstance(sync_every_block=False) for i in range(600): block(5000 + i, coinbase_pubkey=self.coinbase_pubkey) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on. out = [] for i in range(200): out.append(self.chain.get_spendable_output()) self.stop_node(0) # # Test Case 1 (TC1). # # - 10 standard txs used # - 1 peer connected to node0 # All txs emplaced initially in the standard validation queue are processed and accepted by the mempool. # - None txn is rejected with a reason 'too-long-validation-time' (not moved into the non-std queue). # # The number of txs used in the test case. tc1_txs_num = 10 # Select funding transactions to use: # - tc1_txs_num funding transactions are needed in this test case. spend_txs = out[0:tc1_txs_num] args = [ '-checkmempool=0', '-persistmempool=0', '-maxstdtxvalidationduration=500', # increasing max validation time ensures that timeout doesn't occur for standard txns, even on slower machines and on debug build '-maxnonstdtxnsperthreadratio=0' ] # setting it to zero ensures that non-standard txs won't be processed (if there are any queued). with self.run_node_with_connections( 'TC1: {} txs detected as std and then accepted.'.format( tc1_txs_num), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. std_txs = self.run_scenario1(conn, spend_txs, tc1_txs_num, self.locking_script_1) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, std_txs, timeout=30) assert_equal(conn.rpc.getmempoolinfo()['size'], tc1_txs_num) # # Test Case 2 (TC2). # # - 10 non-standard txs (with a simple locking script) used. # - 1 peer connected to node0. # The test case creates rejected txns with a reason 'too-long-validation-time' for all txs initially emplaced into the standard queue. # - those rejects are not taken into account to create reject messages (see explanation - point 6) # All txns are then forwarded to the non-standard validation queue where the validation timeout is longer (sufficient). # # The number of txs used in the test case. tc2_txs_num = 10 # Select funding transactions to use: # - one funding transaction is needed in this test case. spend_txs = out[tc1_txs_num:tc1_txs_num + 1] args = ['-checkmempool=0', '-persistmempool=0'] with self.run_node_with_connections( 'TC2: {} txs with small bignums detected as non-std txs and then finally accepted.' .format(tc2_txs_num), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. nonstd_txs, rejected_txs = self.run_scenario2( conn, spend_txs, tc2_txs_num, self.locking_script_2) # No transactions should be rejected assert_equal(len(rejected_txs), 0) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, nonstd_txs, timeout=30) assert_equal(conn.rpc.getmempoolinfo()['size'], tc2_txs_num) # # Test Case 3 (TC3). # # - 10 non-standard txs (with a complex locking script) used. # - 1 peer connected to node0 # The test case creates rejected txns with a reason 'too-long-validation-time' for all txs initially emplaced into the standard queue. # - those rejects are not taken into account to create reject messages (see explanation - point 6) # All txns are then forwarded to the non-standard validation queue where the validation timeout is longer (sufficient). # # The number of txs used in the test case. tc3_txs_num = 10 # Select funding transactions to use: # - one funding transaction is needed in this test case. spend_txs = out[tc1_txs_num + 1:tc1_txs_num + 2] args = [ '-checkmempool=0', '-persistmempool=0', '-maxnonstdtxvalidationduration=100000', # On slow/busy machine txn validation times have to be high '-maxtxnvalidatorasynctasksrunduration=100001', # This needs to mehigher then maxnonstdtxvalidationduration '-maxscriptsizepolicy=0' ] with self.run_node_with_connections( 'TC3: {} txs with large bignums detected as non-std txs and then finally accepted.' .format(tc3_txs_num), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. nonstd_txs, rejected_txs = self.run_scenario2( conn, spend_txs, tc3_txs_num, self.locking_script_3) # No transactions should be rejected assert_equal(len(rejected_txs), 0) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, nonstd_txs, timeout=30) assert_equal(conn.rpc.getmempoolinfo()['size'], tc3_txs_num) # # Test Case 4 (TC4). # # - 10 non-standard txs (with a complex locking script) used. # - 1 peer connected to node0 # The test case creates rejected txns with a reason 'too-long-validation-time' for all txs initially emplaced into the standard queue. # - those rejects are not taken into account to create reject messages (see explanation - point 6) # All txns are then forwarded to the non-standard validation queue. # - due to insufficient timeout config all txs are rejected again with 'too-long-validation-time' reject reason. # - reject messages are created for each and every txn. # # The number of txs used in the test case. tc4_txs_num = 10 # Select funding transactions to use: # - one funding transaction is needed in this test case. spend_txs = out[tc1_txs_num + 2:tc1_txs_num + 3] args = [ '-checkmempool=0', '-persistmempool=0', '-maxscriptsizepolicy=0' ] with self.run_node_with_connections( 'TC4: {} txs with large bignums detected as non-std txs and then finally rejected.' .format(tc4_txs_num), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. nonstd_txs, rejected_txs = self.run_scenario2( conn, spend_txs, tc4_txs_num, self.locking_script_3) # Check rejected transactions. self.check_rejected(rejected_txs, nonstd_txs) assert_equal(len(rejected_txs), tc4_txs_num) # The mempool should be empty at this stage. assert_equal(conn.rpc.getmempoolinfo()['size'], 0) # # Test Case 5 (TC5). # # - 100 standard txs used. # - 10 non-standard (with a simple locking script) txs used. # - 1 peer connected to node0. # This test case is a combination of TC1 & TC2 # - the set of std and non-std txs is shuffled before sending it to the node. # # The number of txs used in the test case. tc5_1_txs_num = 100 tc5_2_txs_num = 10 # Select funding transactions to use: # - tc5_1_txs_num+1 funding transactions are needed in this test case. spend_txs = out[tc1_txs_num + 3:tc1_txs_num + 3 + tc5_1_txs_num] spend_txs2 = out[tc1_txs_num + 3 + tc5_1_txs_num:tc1_txs_num + 4 + tc5_1_txs_num] args = ['-checkmempool=0', '-persistmempool=0'] with self.run_node_with_connections( 'TC5: The total of {} std and nonstd txs processed and accepted.' .format(tc5_1_txs_num + tc5_2_txs_num), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. std_txs = self.get_txchains_n(tc5_1_txs_num, 1, spend_txs, CScript(), self.locking_script_1, 2000000, 10) std_and_nonstd_txs, rejected_txs = self.run_scenario2( conn, spend_txs2, tc5_2_txs_num, self.locking_script_2, std_txs, shuffle_txs=True) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, std_and_nonstd_txs, timeout=30) assert_equal(conn.rpc.getmempoolinfo()['size'], tc5_1_txs_num + tc5_2_txs_num)
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()
def new_key(): k = CECKey() global _cntr k.set_secretbytes(_cntr.to_bytes(6, byteorder='big')) _cntr += 1 return k
class FullBlockTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. def __init__(self): super().__init__() self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} def setup_network(self): self.extra_args = [['-allowfreetx=0']] self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args, binary=[self.options.testbinary]) def add_options(self, parser): super().add_options(parser) parser.add_option("--runbarelyexpensive", dest="runbarelyexpensive", default=True) def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) # Start up network handling in another thread NetworkThread().start() self.test.run() def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = create_transaction(spend_tx, n, b"", value, script) return tx # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output n in # spend_tx 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 create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, n, value, script) self.sign_tx(tx, spend_tx, n) tx.rehash() return tx def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): if self.tip == None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 600 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(absoluteHeight=height, pubkey=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: coinbase.vout[0].nValue += spend.tx.vout[ spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) tx = create_transaction(spend.tx, spend.n, b"", 1, script) # spend 1 satoshi self.sign_tx(tx, spend.tx, spend.n) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() if solve: block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block create_tx = self.create_tx # shorthand for variables node = self.nodes[0] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(33): out.append(get_spendable_output()) # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Creates a new transaction using a p2sh transaction as input def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append( CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx_to_spend.vout[0].nValue) sig = self.coinbase_key.sign(sighash) + bytes( bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # P2SH tests # Create a p2sh transaction value = out[0].tx.vout[out[0].n].nValue # absurdly high fee p2sh_tx = self.create_and_sign_transaction(out[0].tx, out[0].n, value, p2sh_script) # Add the transaction to the block block(1) update_block(1, [p2sh_tx]) yield accepted() # Sigops p2sh limit for the mempool test p2sh_sigops_limit_mempool = MAX_STANDARD_TX_SIGOPS - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh script too_many_p2sh_sigops_mempool = CScript([OP_CHECKSIG] * (p2sh_sigops_limit_mempool + 1)) # A transaction with this output script can't get into the mempool try: node.sendrawtransaction( ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool))) except JSONRPCException as exp: assert_equal(exp.error["message"], RPC_TXNS_TOO_MANY_SIGOPS_ERROR) else: assert (False) # The transaction is rejected, so the mempool should still be empty assert_equal(set(node.getrawmempool()), set()) # Max sigops in one p2sh txn max_p2sh_sigops_mempool = CScript([OP_CHECKSIG] * (p2sh_sigops_limit_mempool)) # A transaction with this output script can get into the mempool max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool) max_p2sh_sigops_txn_id = node.sendrawtransaction( ToHex(max_p2sh_sigops_txn), True) assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id}) # Mine the transaction block(2, spend=out[1]) update_block(2, [max_p2sh_sigops_txn]) yield accepted() # The transaction has been mined, it's not in the mempool anymore assert_equal(set(node.getrawmempool()), set())
def run_test(self): p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 2100 deep for i in range(2100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 self.nodes[0].disconnect_p2ps() # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)]) p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) p2p1 = self.nodes[1].add_p2p_connection(BaseNode()) p2p2 = self.nodes[2].add_p2p_connection(BaseNode()) # send header lists to all three nodes p2p0.send_header_for_blocks(self.blocks[0:2000]) p2p0.send_header_for_blocks(self.blocks[2000:]) p2p1.send_header_for_blocks(self.blocks[0:2000]) p2p1.send_header_for_blocks(self.blocks[2000:]) p2p2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p0) self.assert_blockchain_height(self.nodes[0], 101) # Send all blocks to node1. All blocks will be accepted. for i in range(2202): p2p1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. p2p1.sync_with_ping(120) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p2) self.assert_blockchain_height(self.nodes[2], 101)
class FullBlockTest(ComparisonTestFramework): ''' Can either run this test as 1 node with expected answers, or two and compare them. Change the "outcome" variable from each TestInstance object to only do the comparison. ''' def __init__(self): super().__init__() self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.block_time = int(time.time())+1 self.tip = None self.blocks = {} def run_test(self): test = TestManager(self, self.options.tmpdir) test.add_all_connections(self.nodes) NetworkThread().start() # Start up network handling in another thread test.run() def add_transactions_to_block(self, block, tx_list): [ tx.rehash() for tx in tx_list ] block.vtx.extend(tx_list) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() return block # Create a block on top of self.tip, and advance self.tip to point to the new block # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, # and rest will go to fees. def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): if self.tip == None: base_block_hash = self.genesis_hash else: base_block_hash = self.tip.sha256 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, self.block_time) if (spend != None): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) # no signature yet # This copies the java comparison tool testing behavior: the first # txout has a garbage scriptPubKey, "to make sure we're not # pre-verifying too much" (?) tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) # Now sign it if necessary scriptSig = b"" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it (sighash, err) = SignatureHash( spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL, spend.tx.vout[spend.n].nValue, SAPLING_BRANCH_ID, ) scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block block = self.add_transactions_to_block(block, [tx]) block.solve() self.tip = block self.block_heights[block.sha256] = height self.block_time += 1 assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previous marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(): return TestInstance([[self.tip, False]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # creates a new block and advances the tip to that block block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(100): block(1000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Start by building a couple of blocks on top (which output is spent is in parentheses): # genesis -> b1 (0) -> b2 (1) out0 = get_spendable_output() block(1, spend=out0) save_spendable_output() yield accepted() out1 = get_spendable_output() block(2, spend=out1) # Inv again, then deliver twice (shouldn't break anything). yield accepted() # so fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes priority. tip(1) block(3, spend=out1) # Deliver twice (should still not break anything) yield rejected() # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) out2 = get_spendable_output() block(4, spend=out2) yield accepted() # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) tip(2) block(5, spend=out2) save_spendable_output() yield rejected() out3 = get_spendable_output() block(6, spend=out3) yield accepted() # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) tip(5) block(7, spend=out2) yield rejected() out4 = get_spendable_output() block(8, spend=out4) yield rejected() # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) tip(6) block(9, spend=out4, additional_coinbase_value=1) yield rejected() # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) tip(5) block(10, spend=out3) yield rejected() block(11, spend=out4, additional_coinbase_value=1) yield rejected() # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) # (b12 added last) # \-> b3 (1) -> b4 (2) tip(5) b12 = block(12, spend=out3) save_spendable_output() #yield TestInstance([[b12, False]]) b13 = block(13, spend=out4) # Deliver the block header for b12, and the block b13. # b13 should be accepted but the tip won't advance until b12 is delivered. yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) save_spendable_output() out5 = get_spendable_output() # b14 is invalid, but the node won't know that until it tries to connect # Tip still can't advance because b12 is missing block(14, spend=out5, additional_coinbase_value=1) yield rejected() yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. # Test that a block with a lot of checksigs is okay lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50 - 1)) tip(13) block(15, spend=out5, script=lots_of_checksigs) yield accepted() # Test that a block with too many checksigs is rejected out6 = get_spendable_output() too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50)) block(16, spend=out6, script=too_many_checksigs) yield rejected()
class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True # Node0 = pre-segwit, node1 = segwit-aware self.num_nodes = 2 # This test was written assuming SegWit is activated using BIP9 at height 432 (3x confirmation window). # TODO: Rewrite this test to support SegWit being always active. self.extra_args = [["-vbparams=segwit:0:0"], [ "-vbparams=segwit:0:999999999999", "-txindex", "-deprecatedrpc=addwitnessaddress" ]] self.utxos = [] self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() def build_block_on_tip(self, node, segwit=False): height = node.getblockcount() tip = node.getbestblockhash() mtp = node.getblockheader(tip)['mediantime'] block = create_block( int(tip, 16), create_coinbase(height + 1, self.coinbase_pubkey, mtp), mtp + 1) block.nVersion = 1 if segwit: add_witness_commitment(block) block.solve() block.vchBlockSig = self.coinbase_key.sign( bytes.fromhex(block.hash)[::-1]) return block # Create 10 more anyone-can-spend utxo's for testing. def make_utxos(self): # Doesn't matter which node we use, just use node0. block = self.build_block_on_tip(self.nodes[0]) self.test_node.send_and_ping(msg_block(block)) assert (int(self.nodes[0].getbestblockhash(), 16) == block.sha256) self.nodes[0].generate(100) total_value = block.vtx[0].vout[0].nValue out_value = total_value // 10 tx = CTransaction() tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b'')) for i in range(10): tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) tx.rehash() block2 = self.build_block_on_tip(self.nodes[0]) block2.vtx.append(tx) block2.hashMerkleRoot = block2.calc_merkle_root() block2.solve() self.test_node.send_and_ping(msg_block(block2)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) return # Test "sendcmpct" (between peers preferring the same version): # - No compact block announcements unless sendcmpct is sent. # - If sendcmpct is sent with version > preferred_version, the message is ignored. # - If sendcmpct is sent with boolean 0, then block announcements are not # made with compact blocks. # - If sendcmpct is then sent with boolean 1, then new block announcements # are made with compact blocks. # If old_node is passed in, request compact blocks with version=preferred-1 # and verify that it receives block announcements via compact block. def test_sendcmpct(self, node, test_node, preferred_version, old_node=None): # Make sure we get a SENDCMPCT message from our peer def received_sendcmpct(): return (len(test_node.last_sendcmpct) > 0) wait_until(received_sendcmpct, timeout=30, lock=mininode_lock) with mininode_lock: # Check that the first version received is the preferred one assert_equal(test_node.last_sendcmpct[0].version, preferred_version) # And that we receive versions down to 1. assert_equal(test_node.last_sendcmpct[-1].version, 1) test_node.last_sendcmpct = [] tip = int(node.getbestblockhash(), 16) def check_announcement_of_new_block(node, peer, predicate): peer.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) peer.wait_for_block_announcement(block_hash, timeout=30) assert (peer.block_announced) with mininode_lock: assert predicate(peer), ( "block_hash={!r}, cmpctblock={!r}, inv={!r}".format( block_hash, peer.last_message.get("cmpctblock", None), peer.last_message.get("inv", None))) # We shouldn't get any block announcements via cmpctblock yet. check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Try one more time, this time after requesting headers. test_node.request_headers_and_sync(locator=[tip]) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message and "inv" in p.last_message) # Test a few ways of using sendcmpct that should NOT # result in compact block announcements. # Before each test, sync the headers chain. test_node.request_headers_and_sync(locator=[tip]) # Now try a SENDCMPCT message with too-high version sendcmpct = msg_sendcmpct() sendcmpct.version = preferred_version + 1 sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Now try a SENDCMPCT message with valid version, but announce=False sendcmpct.version = preferred_version sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Finally, try a SENDCMPCT message with announce=True sendcmpct.version = preferred_version sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time (no headers sync should be needed!) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after turning on sendheaders test_node.send_and_ping(msg_sendheaders()) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after sending a version-1, announce=false message. sendcmpct.version = preferred_version - 1 sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Now turn off announcements sendcmpct.version = preferred_version sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message) if old_node is not None: # Verify that a peer using an older protocol version can receive # announcements from this node. sendcmpct.version = preferred_version - 1 sendcmpct.announce = True old_node.send_and_ping(sendcmpct) # Header sync old_node.request_headers_and_sync(locator=[tip]) check_announcement_of_new_block( node, old_node, lambda p: "cmpctblock" in p.last_message) # This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. def test_invalid_cmpctblock_message(self): self.nodes[0].generate(101) block = self.build_block_on_tip(self.nodes[0]) cmpct_block = P2PHeaderAndShortIDs() cmpct_block.header = CBlockHeader(block) cmpct_block.prefilled_txn_length = 1 # This index will be too high prefilled_txn = PrefilledTransaction(1, block.vtx[0]) cmpct_block.prefilled_txn = [prefilled_txn] self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) # Compare the generated shortids to what we expect based on BIP 152, given # bitcoind's choice of nonce. def test_compactblock_construction(self, node, test_node, version, use_witness_address): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. address = node.addwitnessaddress(address) value_to_send = node.getbalance() node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1))) node.generate(1) segwit_tx_generated = False for i in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True if use_witness_address: assert (segwit_tx_generated) # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node, version) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert ("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() inv = CInv(4, block_hash) # 4 == "CompactBlock" test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert ("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block) def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block): # Check that we got the right block! header_and_shortids.header.calc_sha256() assert_equal(header_and_shortids.header.sha256, block_hash) # Make sure the prefilled_txn appears to have included the coinbase assert (len(header_and_shortids.prefilled_txn) >= 1) assert_equal(header_and_shortids.prefilled_txn[0].index, 0) # Check that all prefilled_txn entries match what's in the block. for entry in header_and_shortids.prefilled_txn: entry.tx.calc_sha256() # This checks the non-witness parts of the tx agree assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) # And this checks the witness wtxid = entry.tx.calc_sha256(True) if version == 2: assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True)) else: # Shouldn't have received a witness assert (entry.tx.wit.is_null()) # Check that the cmpctblock message announced all the transactions. assert_equal( len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx)) # And now check that all the shortids are as expected as well. # Determine the siphash keys to use. [k0, k1] = header_and_shortids.get_siphash_keys() index = 0 while index < len(block.vtx): if (len(header_and_shortids.prefilled_txn) > 0 and header_and_shortids.prefilled_txn[0].index == index): # Already checked prefilled transactions above header_and_shortids.prefilled_txn.pop(0) else: tx_hash = block.vtx[index].sha256 if version == 2: tx_hash = block.vtx[index].calc_sha256(True) shortid = calculate_shortid(k0, k1, tx_hash) assert_equal(shortid, header_and_shortids.shortids[0]) header_and_shortids.shortids.pop(0) index += 1 # Test that bitcoind requests compact blocks when we announce new blocks # via header or inv, and that responding to getblocktxn causes the block # to be successfully reconstructed. # Post-segwit: upgraded nodes would only make this request of cb-version-2, # NODE_WITNESS peers. Unupgraded nodes would still make this request of # any cb-version-1-supporting peer. def test_compactblock_requests(self, node, test_node, version, segwit): # Try announcing a block with an inv or header, expect a compactblock # request for announce in ["inv", "header"]: block = self.build_block_on_tip(node, segwit=segwit) with mininode_lock: test_node.last_message.pop("getdata", None) if announce == "inv": test_node.send_message(msg_inv([CInv(2, block.sha256)])) wait_until(lambda: "getheaders" in test_node.last_message, timeout=30, lock=mininode_lock) test_node.send_header_for_blocks([block]) else: test_node.send_header_for_blocks([block]) wait_until(lambda: "getdata" in test_node.last_message, timeout=30, lock=mininode_lock) assert_equal(len(test_node.last_message["getdata"].inv), 1) assert_equal(test_node.last_message["getdata"].inv[0].type, 4) assert_equal(test_node.last_message["getdata"].inv[0].hash, block.sha256) # Send back a compactblock message that omits the coinbase comp_block = HeaderAndShortIDs() comp_block.header = CBlockHeader(block) comp_block.nonce = 0 [k0, k1] = comp_block.get_siphash_keys() coinbase_hash = block.vtx[0].sha256 if version == 2: coinbase_hash = block.vtx[0].calc_sha256(True) comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)] test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # Expect a getblocktxn message. with mininode_lock: assert ("getblocktxn" in test_node.last_message) absolute_indexes = test_node.last_message[ "getblocktxn"].block_txn_request.to_absolute() assert_equal(absolute_indexes, [0]) # should be a coinbase request # Send the coinbase, and verify that the tip advances. if version == 2: msg = msg_witness_blocktxn() else: msg = msg_blocktxn() msg.block_transactions.blockhash = block.sha256 msg.block_transactions.transactions = [block.vtx[0]] test_node.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), block.sha256) # Create a chain of transactions from given utxo, and add to a new block. def build_block_with_transactions(self, node, utxo, num_transactions): block = self.build_block_on_tip(node) for i in range(num_transactions): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) tx.vout.append( CTxOut(utxo[2] - 1000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) tx.rehash() utxo = [tx.sha256, 0, tx.vout[0].nValue] block.vtx.append(tx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() return block # Test that we only receive getblocktxn requests for transactions that the # node needs, and that responding to them causes the block to be # reconstructed. def test_getblocktxn_requests(self, node, test_node, version): with_witness = (version == 2) def test_getblocktxn_response(compact_block, peer, expected_result): msg = msg_cmpctblock(compact_block.to_p2p()) peer.send_and_ping(msg) with mininode_lock: assert ("getblocktxn" in peer.last_message) absolute_indexes = peer.last_message[ "getblocktxn"].block_txn_request.to_absolute() assert_equal(absolute_indexes, expected_result) def test_tip_after_message(node, peer, msg, tip): peer.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), tip) # First try announcing compactblocks that won't reconstruct, and verify # that we receive getblocktxn messages back. utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block, use_witness=with_witness) test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) msg_bt = msg_blocktxn() if with_witness: msg_bt = msg_witness_blocktxn() # serialize with witnesses msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256) utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) # Now try interspersing the prefilled transactions comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness) test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5]) test_tip_after_message(node, test_node, msg_bt, block.sha256) # Now try giving one transaction ahead of time. utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) test_node.send_and_ping(msg_tx(block.vtx[1])) assert (block.vtx[1].hash in node.getrawmempool()) # Prefill 4 out of the 6 transactions, and verify that only the one # that was not in the mempool is requested. comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness) test_getblocktxn_response(comp_block, test_node, [5]) msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]]) test_tip_after_message(node, test_node, msg_bt, block.sha256) # Now provide all transactions to the node before the block is # announced and verify reconstruction happens immediately. utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 10) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) for tx in block.vtx[1:]: test_node.send_message(msg_tx(tx)) test_node.sync_with_ping() # Make sure all transactions were accepted. mempool = node.getrawmempool() for tx in block.vtx[1:]: assert (tx.hash in mempool) # Clear out last request. with mininode_lock: test_node.last_message.pop("getblocktxn", None) # Send compact block comp_block.initialize_from_block(block, prefill_list=[0], use_witness=with_witness) test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256) with mininode_lock: # Shouldn't have gotten a request for any transaction assert ("getblocktxn" not in test_node.last_message) # Incorrectly responding to a getblocktxn shouldn't cause the block to be # permanently failed. def test_incorrect_blocktxn_response(self, node, test_node, version): if (len(self.utxos) == 0): self.make_utxos() utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 10) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) # Relay the first 5 transactions from the block in advance for tx in block.vtx[1:6]: test_node.send_message(msg_tx(tx)) test_node.sync_with_ping() # Make sure all transactions were accepted. mempool = node.getrawmempool() for tx in block.vtx[1:6]: assert (tx.hash in mempool) # Send compact block comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block, prefill_list=[0], use_witness=(version == 2)) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) absolute_indexes = [] with mininode_lock: assert ("getblocktxn" in test_node.last_message) absolute_indexes = test_node.last_message[ "getblocktxn"].block_txn_request.to_absolute() assert_equal(absolute_indexes, [6, 7, 8, 9, 10]) # Now give an incorrect response. # Note that it's possible for bitcoind to be smart enough to know we're # lying, since it could check to see if the shortid matches what we're # sending, and eg disconnect us for misbehavior. If that behavior # change were made, we could just modify this test by having a # different peer provide the block further down, so that we're still # verifying that the block isn't marked bad permanently. This is good # enough for now. msg = msg_blocktxn() if version == 2: msg = msg_witness_blocktxn() msg.block_transactions = BlockTransactions( block.sha256, [block.vtx[5]] + block.vtx[7:]) test_node.send_and_ping(msg) # Tip should not have updated assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # We should receive a getdata request wait_until(lambda: "getdata" in test_node.last_message, timeout=10, lock=mininode_lock) assert_equal(len(test_node.last_message["getdata"].inv), 1) assert (test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2 | MSG_WITNESS_FLAG) assert_equal(test_node.last_message["getdata"].inv[0].hash, block.sha256) # Deliver the block if version == 2: test_node.send_and_ping(msg_witness_block(block)) else: test_node.send_and_ping(msg_block(block)) assert_equal(int(node.getbestblockhash(), 16), block.sha256) def test_getblocktxn_handler(self, node, test_node, version): # bitcoind will not send blocktxn responses for blocks whose height is # more than 10 blocks deep. MAX_GETBLOCKTXN_DEPTH = 10 chain_height = node.getblockcount() current_height = chain_height while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): block_hash = node.getblockhash(current_height) block = FromHex(CBlock(), node.getblock(block_hash, False)) msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), []) num_to_request = random.randint(1, len(block.vtx)) msg.block_txn_request.from_absolute( sorted(random.sample(range(len(block.vtx)), num_to_request))) test_node.send_message(msg) wait_until(lambda: "blocktxn" in test_node.last_message, timeout=10, lock=mininode_lock) [tx.calc_sha256() for tx in block.vtx] with mininode_lock: assert_equal( test_node.last_message["blocktxn"].block_transactions. blockhash, int(block_hash, 16)) all_indices = msg.block_txn_request.to_absolute() for index in all_indices: tx = test_node.last_message[ "blocktxn"].block_transactions.transactions.pop(0) tx.calc_sha256() assert_equal(tx.sha256, block.vtx[index].sha256) if version == 1: # Witnesses should have been stripped assert (tx.wit.is_null()) else: # Check that the witness matches assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True)) test_node.last_message.pop("blocktxn", None) current_height -= 1 # Next request should send a full block response, as we're past the # allowed depth for a blocktxn response. block_hash = node.getblockhash(current_height) msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), [0]) with mininode_lock: test_node.last_message.pop("block", None) test_node.last_message.pop("blocktxn", None) test_node.send_and_ping(msg) with mininode_lock: test_node.last_message["block"].block.calc_sha256() assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16)) assert "blocktxn" not in test_node.last_message def test_compactblocks_not_at_tip(self, node, test_node): # Test that requesting old compactblocks doesn't work. MAX_CMPCTBLOCK_DEPTH = 5 new_blocks = [] for i in range(MAX_CMPCTBLOCK_DEPTH + 1): test_node.clear_block_announcement() new_blocks.append(node.generate(1)[0]) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) test_node.clear_block_announcement() test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30, lock=mininode_lock) test_node.clear_block_announcement() node.generate(1) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) test_node.clear_block_announcement() with mininode_lock: test_node.last_message.pop("block", None) test_node.send_message(msg_getdata([CInv(4, int(new_blocks[0], 16))])) wait_until(lambda: "block" in test_node.last_message, timeout=30, lock=mininode_lock) with mininode_lock: test_node.last_message["block"].block.calc_sha256() assert_equal(test_node.last_message["block"].block.sha256, int(new_blocks[0], 16)) # Generate an old compactblock, and verify that it's not accepted. cur_height = node.getblockcount() hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) block = self.build_block_on_tip(node) block.hashPrevBlock = hashPrevBlock block.solve() comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) tips = node.getchaintips() found = False for x in tips: if x["hash"] == block.hash: assert_equal(x["status"], "headers-only") found = True break assert (found) # Requesting this block via getblocktxn should silently fail # (to avoid fingerprinting attacks). msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0]) with mininode_lock: test_node.last_message.pop("blocktxn", None) test_node.send_and_ping(msg) with mininode_lock: assert "blocktxn" not in test_node.last_message def activate_segwit(self, node): node.generate(144 * 3) assert_equal(get_bip9_status(node, "segwit")["status"], 'active') def test_end_to_end_block_relay(self, node, listeners): utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 10) [l.clear_block_announcement() for l in listeners] # ToHex() won't serialize with witness, but this block has no witnesses # anyway. TODO: repeat this test with witness tx's to a segwit node. node.submitblock(ToHex(block)) for l in listeners: wait_until(lambda: l.received_block_announcement(), timeout=30, lock=mininode_lock) with mininode_lock: for l in listeners: assert "cmpctblock" in l.last_message l.last_message[ "cmpctblock"].header_and_shortids.header.calc_sha256() assert_equal( l.last_message["cmpctblock"].header_and_shortids.header. sha256, block.sha256) # Test that we don't get disconnected if we relay a compact block with valid header, # but invalid transactions. def test_invalid_tx_in_compactblock(self, node, test_node, use_segwit): assert (len(self.utxos)) utxo = self.utxos[0] block = self.build_block_with_transactions(node, utxo, 5) del block.vtx[3] block.hashMerkleRoot = block.calc_merkle_root() if use_segwit: # If we're testing with segwit, also drop the coinbase witness, # but include the witness commitment. add_witness_commitment(block) block.vtx[0].wit.vtxinwit = [] block.solve() # Now send the compact block with all transactions prefilled, and # verify that we don't get disconnected. comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=use_segwit) msg = msg_cmpctblock(comp_block.to_p2p()) test_node.send_and_ping(msg) # Check that the tip didn't advance assert (int(node.getbestblockhash(), 16) is not block.sha256) test_node.sync_with_ping() # Helper for enabling cb announcements # Send the sendcmpct request and sync headers def request_cb_announcements(self, peer, node, version): tip = node.getbestblockhash() peer.get_headers(locator=[int(tip, 16)], hashstop=0) msg = msg_sendcmpct() msg.version = version msg.announce = True peer.send_and_ping(msg) def test_compactblock_reconstruction_multiple_peers( self, node, stalling_peer, delivery_peer): assert (len(self.utxos)) def announce_cmpct_block(node, peer): utxo = self.utxos.pop(0) block = self.build_block_with_transactions(node, utxo, 5) cmpct_block = HeaderAndShortIDs() cmpct_block.initialize_from_block(block) msg = msg_cmpctblock(cmpct_block.to_p2p()) peer.send_and_ping(msg) with mininode_lock: assert "getblocktxn" in peer.last_message return block, cmpct_block block, cmpct_block = announce_cmpct_block(node, stalling_peer) for tx in block.vtx[1:]: delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() mempool = node.getrawmempool() for tx in block.vtx[1:]: assert (tx.hash in mempool) delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.sha256) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) # Now test that delivering an invalid compact block won't break relay block, cmpct_block = announce_cmpct_block(node, stalling_peer) for tx in block.vtx[1:]: delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [CTxInWitness()] cmpct_block.prefilled_txn[0].tx.wit.vtxinwit[0].scriptWitness.stack = [ ser_uint256(0) ] cmpct_block.use_witness = True delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) assert (int(node.getbestblockhash(), 16) != block.sha256) msg = msg_blocktxn() msg.block_transactions.blockhash = block.sha256 msg.block_transactions.transactions = block.vtx[1:] stalling_peer.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), block.sha256) def run_test(self): # Setup the p2p connections and start up the network thread. self.test_node = self.nodes[0].add_p2p_connection(TestNode()) self.segwit_node = self.nodes[1].add_p2p_connection( TestNode(), services=NODE_NETWORK | NODE_WITNESS) self.old_node = self.nodes[1].add_p2p_connection(TestNode(), services=NODE_NETWORK) network_thread_start() self.test_node.wait_for_verack() # We will need UTXOs to construct transactions in later tests. self.make_utxos() self.log.info("Running tests, pre-segwit activation:") self.log.info("Testing SENDCMPCT p2p message... ") self.test_sendcmpct(self.nodes[0], self.test_node, 1) sync_blocks(self.nodes) self.test_sendcmpct(self.nodes[1], self.segwit_node, 2, old_node=self.old_node) sync_blocks(self.nodes) self.log.info("Testing compactblock construction...") self.test_compactblock_construction(self.nodes[0], self.test_node, 1, False) sync_blocks(self.nodes) self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, False) sync_blocks(self.nodes) self.log.info("Testing compactblock requests... ") self.test_compactblock_requests(self.nodes[0], self.test_node, 1, False) sync_blocks(self.nodes) self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, False) sync_blocks(self.nodes) self.log.info("Testing getblocktxn requests...") self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) sync_blocks(self.nodes) self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2) sync_blocks(self.nodes) self.log.info("Testing getblocktxn handler...") self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1) sync_blocks(self.nodes) self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2) self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) sync_blocks(self.nodes) self.log.info( "Testing compactblock requests/announcements not at chain tip...") self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node) sync_blocks(self.nodes) self.test_compactblocks_not_at_tip(self.nodes[1], self.segwit_node) self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node) sync_blocks(self.nodes) self.log.info("Testing handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1) sync_blocks(self.nodes) self.test_incorrect_blocktxn_response(self.nodes[1], self.segwit_node, 2) sync_blocks(self.nodes) # End-to-end block relay tests self.log.info("Testing end-to-end block relay...") self.request_cb_announcements(self.test_node, self.nodes[0], 1) self.request_cb_announcements(self.old_node, self.nodes[1], 1) self.request_cb_announcements(self.segwit_node, self.nodes[1], 2) self.test_end_to_end_block_relay( self.nodes[0], [self.segwit_node, self.test_node, self.old_node]) self.test_end_to_end_block_relay( self.nodes[1], [self.segwit_node, self.test_node, self.old_node]) self.log.info("Testing handling of invalid compact blocks...") self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node, False) self.test_invalid_tx_in_compactblock(self.nodes[1], self.segwit_node, False) self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node, False) self.log.info( "Testing reconstructing compact blocks from all peers...") self.test_compactblock_reconstruction_multiple_peers( self.nodes[1], self.segwit_node, self.old_node) sync_blocks(self.nodes) # Advance to segwit activation self.log.info("Advancing to segwit activation") self.activate_segwit(self.nodes[1]) self.log.info("Running tests, post-segwit activation...") self.log.info("Testing compactblock construction...") self.test_compactblock_construction(self.nodes[1], self.old_node, 1, True) self.test_compactblock_construction(self.nodes[1], self.segwit_node, 2, True) sync_blocks(self.nodes) self.log.info("Testing compactblock requests (unupgraded node)... ") self.test_compactblock_requests(self.nodes[0], self.test_node, 1, True) self.log.info("Testing getblocktxn requests (unupgraded node)...") self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) # Need to manually sync node0 and node1, because post-segwit activation, # node1 will not download blocks from node0. self.log.info("Syncing nodes...") assert (self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()) while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()): block_hash = self.nodes[0].getblockhash( self.nodes[1].getblockcount() + 1) self.nodes[1].submitblock(self.nodes[0].getblock( block_hash, False)) assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash()) self.log.info("Testing compactblock requests (segwit node)... ") self.test_compactblock_requests(self.nodes[1], self.segwit_node, 2, True) self.log.info("Testing getblocktxn requests (segwit node)...") self.test_getblocktxn_requests(self.nodes[1], self.segwit_node, 2) sync_blocks(self.nodes) self.log.info( "Testing getblocktxn handler (segwit node should return witnesses)..." ) self.test_getblocktxn_handler(self.nodes[1], self.segwit_node, 2) self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) # Test that if we submitblock to node1, we'll get a compact block # announcement to all peers. # (Post-segwit activation, blocks won't propagate from node0 to node1 # automatically, so don't bother testing a block announced to node0.) self.log.info("Testing end-to-end block relay...") self.request_cb_announcements(self.test_node, self.nodes[0], 1) self.request_cb_announcements(self.old_node, self.nodes[1], 1) self.request_cb_announcements(self.segwit_node, self.nodes[1], 2) self.test_end_to_end_block_relay( self.nodes[1], [self.segwit_node, self.test_node, self.old_node]) self.log.info("Testing handling of invalid compact blocks...") self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node, False) self.test_invalid_tx_in_compactblock(self.nodes[1], self.segwit_node, True) self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node, True) self.log.info("Testing invalid index in cmpctblock message...") self.test_invalid_cmpctblock_message()
class FullBlockTest(ComparisonTestFramework): ''' Can either run this test as 1 node with expected answers, or two and compare them. Change the "outcome" variable from each TestInstance object to only do the comparison. ''' def __init__(self): self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(bytes("horsebattery")) self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.block_time = int(time.time())+1 self.tip = None self.blocks = {} def run_test(self): test = TestManager(self, self.options.tmpdir) test.add_all_connections(self.nodes) NetworkThread().start() # Start up network handling in another thread test.run() def add_transactions_to_block(self, block, tx_list): [ tx.rehash() for tx in tx_list ] block.vtx.extend(tx_list) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() return block # Create a block on top of self.tip, and advance self.tip to point to the new block # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, # and rest will go to fees. def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): if self.tip == None: base_block_hash = self.genesis_hash else: base_block_hash = self.tip.sha256 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, self.block_time) if (spend != None): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), "", 0xffffffff)) # no signature yet # This copies the java comparison tool testing behavior: the first # txout has a garbage scriptPubKey, "to make sure we're not # pre-verifying too much" (?) tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) # Now sign it if necessary scriptSig = "" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL) scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block block = self.add_transactions_to_block(block, [tx]) block.solve() self.tip = block self.block_heights[block.sha256] = height self.block_time += 1 assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previous marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(): return TestInstance([[self.tip, False]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # creates a new block and advances the tip to that block block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(100): block(1000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Start by bulding a couple of blocks on top (which output is spent is in parentheses): # genesis -> b1 (0) -> b2 (1) out0 = get_spendable_output() block(1, spend=out0) save_spendable_output() yield accepted() out1 = get_spendable_output() block(2, spend=out1) # Inv again, then deliver twice (shouldn't break anything). yield accepted() # so fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes priority. tip(1) block(3, spend=out1) # Deliver twice (should still not break anything) yield rejected() # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) out2 = get_spendable_output() block(4, spend=out2) yield accepted() # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) tip(2) block(5, spend=out2) save_spendable_output() yield rejected() out3 = get_spendable_output() block(6, spend=out3) yield accepted() # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) tip(5) block(7, spend=out2) yield rejected() out4 = get_spendable_output() block(8, spend=out4) yield rejected() # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) tip(6) block(9, spend=out4, additional_coinbase_value=1) yield rejected() # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) tip(5) block(10, spend=out3) yield rejected() block(11, spend=out4, additional_coinbase_value=1) yield rejected() # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) # (b12 added last) # \-> b3 (1) -> b4 (2) tip(5) b12 = block(12, spend=out3) save_spendable_output() #yield TestInstance([[b12, False]]) b13 = block(13, spend=out4) # Deliver the block header for b12, and the block b13. # b13 should be accepted but the tip won't advance until b12 is delivered. yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) save_spendable_output() out5 = get_spendable_output() # b14 is invalid, but the node won't know that until it tries to connect # Tip still can't advance because b12 is missing block(14, spend=out5, additional_coinbase_value=1) yield rejected() yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. # Test that a block with a lot of checksigs is okay lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1)) tip(13) block(15, spend=out5, script=lots_of_checksigs) yield accepted() # Test that a block with too many checksigs is rejected out6 = get_spendable_output() too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50)) block(16, spend=out6, script=too_many_checksigs) yield rejected()
def run_test(self): node, = self.nodes self.bootstrap_p2p() tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] self.log.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) self.log.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair privkeybytes = b"Schnorr!" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): spendfrom = spendable_outputs.pop() script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_tx_with_script(spendfrom, 0, b'', value, script) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(value - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = SIGHASH_ALL | SIGHASH_FORKID hashbyte = bytes([sighashtype & 0xff]) sighash = SignatureHashForkId(script, txspend, 0, sighashtype, value) if sigtype == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sigtype == 'ecdsa': txsig = private_key.sign(sighash) + hashbyte txspend.vin[0].scriptSig = CScript([dummy, txsig]) txspend.rehash() return txspend # This is valid. ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') # This is invalid. ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') # This is invalid. schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') # This is valid. schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') tip = self.build_block(tip, fundings) node.p2p.send_blocks_and_test([tip], node) self.log.info("Send a legacy ECDSA multisig into mempool.") node.p2p.send_txs_and_test([ecdsa0tx], node) assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) self.log.info("Trying to mine a non-null-dummy ECDSA.") self.check_for_ban_on_rejected_block(self.build_block(tip, [ecdsa1tx]), BADINPUTS_ERROR) self.log.info( "If we try to submit it by mempool or RPC, it is rejected and we are banned" ) assert_raises_rpc_error(-26, ECDSA_NULLDUMMY_ERROR, node.sendrawtransaction, ToHex(ecdsa1tx)) self.check_for_ban_on_rejected_tx(ecdsa1tx, ECDSA_NULLDUMMY_ERROR) self.log.info( "Submitting a Schnorr-multisig via net, and mining it in a block") node.p2p.send_txs_and_test([schnorr1tx], node) assert_equal(set(node.getrawmempool()), {ecdsa0tx.hash, schnorr1tx.hash}) tip = self.build_block(tip, [schnorr1tx]) node.p2p.send_blocks_and_test([tip], node) self.log.info( "That legacy ECDSA multisig is still in mempool, let's mine it") assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) tip = self.build_block(tip, [ecdsa0tx]) node.p2p.send_blocks_and_test([tip], node) assert_equal(node.getrawmempool(), []) self.log.info( "Trying Schnorr in legacy multisig is invalid and banworthy.") self.check_for_ban_on_rejected_tx(schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR)
class FullBlockTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} def setup_network(self): self.extra_args = [['-norelaypriority']] self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() def add_options(self, parser): super().add_options(parser) parser.add_option( "--runbarelyexpensive", dest="runbarelyexpensive", default=True) def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) # Start up network handling in another thread NetworkThread().start() self.test.run() def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = create_transaction(spend_tx, n, b"", value, script) return tx # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output n in # spend_tx 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 create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, n, value, script) self.sign_tx(tx, spend_tx, n) tx.rehash() return tx def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE])): if self.tip == None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.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 to fees coinbase.vout[0].nValue += spend.tx.vout[ spend.n].nValue - 1 coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # spend 1 satoshi tx = create_transaction(spend.tx, spend.n, b"", 1, script) self.sign_tx(tx, spend.tx, spend.n) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() # Do PoW, which is very inexpensive on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block create_tx = self.create_tx # shorthand for variables node = self.nodes[0] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(33): out.append(get_spendable_output()) # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [ OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Creates a new transaction using a p2sh transaction as input def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append( CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId( redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx_to_spend.vout[0].nValue) sig = self.coinbase_key.sign( sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # P2SH tests # Create a p2sh transaction p2sh_tx = self.create_and_sign_transaction( out[0].tx, out[0].n, 1, p2sh_script) # Add the transaction to the block block(1) update_block(1, [p2sh_tx]) yield accepted() # Sigops p2sh limit for the mempool test p2sh_sigops_limit_mempool = MAX_STANDARD_TX_SIGOPS - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh script too_many_p2sh_sigops_mempool = CScript( [OP_CHECKSIG] * (p2sh_sigops_limit_mempool + 1)) # A transaction with this output script can't get into the mempool assert_raises_rpc_error(-26, RPC_TXNS_TOO_MANY_SIGOPS_ERROR, node.sendrawtransaction, ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool))) # The transaction is rejected, so the mempool should still be empty assert_equal(set(node.getrawmempool()), set()) # Max sigops in one p2sh txn max_p2sh_sigops_mempool = CScript( [OP_CHECKSIG] * (p2sh_sigops_limit_mempool)) # A transaction with this output script can get into the mempool max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool) max_p2sh_sigops_txn_id = node.sendrawtransaction( ToHex(max_p2sh_sigops_txn)) assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id}) # Mine the transaction block(2, spend=out[1]) update_block(2, [max_p2sh_sigops_txn]) yield accepted() # The transaction has been mined, it's not in the mempool anymore assert_equal(set(node.getrawmempool()), set())
def run_test(self): # Generate enough blocks to trigger certain block votes self.nodes[0].generate(1150) self.sync_all() logging.info("not on chain tip") badtip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16) height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) cur_time = int(time.time()) self.nodes[0].setmocktime(cur_time) self.nodes[1].setmocktime(cur_time) block = create_block(badtip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: does not build on chain tip") logging.info("time too far in the past") block = create_block(tip, coinbase, cur_time) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: time-too-old") logging.info("time too far in the future") block = create_block(tip, coinbase, cur_time + 10000000) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: time-too-new") logging.info("bad version 1") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 1 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad version 2") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 2 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad version 3") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 3 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad coinbase height") tip = int(self.nodes[0].getblockhash(height), 16) block = create_block(tip, create_coinbase(height), cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate( hexblk), JSONRPCException, "invalid block: bad-cb-height") logging.info("bad merkle root") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.hashMerkleRoot = 0x12345678 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txnmrklroot") logging.info("no tx") block = create_block(tip, None, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-blk-length") logging.info("good block") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) # ------ self.nodes[0].validateblocktemplate(hexblk) block.solve() hexblk = ToHex(block) self.nodes[0].submitblock(hexblk) self.sync_all() prev_block = block # out_value is less than 50BTC because regtest halvings happen every 150 blocks, and is in Satoshis out_value = block.vtx[0].vout[0].nValue tx1 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 2), int(out_value / 2)]) height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = cur_time + 1200 logging.info("no coinbase") block = create_block(tip, None, next_time, [tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-cb-missing") logging.info("double coinbase") coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() coinbase2 = create_coinbase(height + 1, coinbase_pubkey) block = create_block(tip, coinbase, next_time, [coinbase2, tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-cb-multiple") logging.info("premature coinbase spend") block = create_block(tip, coinbase, next_time, [tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-premature-spend-of-coinbase") self.nodes[0].generate(100) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = cur_time + 1200 logging.info("inputs below outputs") tx6 = create_transaction(prev_block.vtx[0], 0, b'\x51', [out_value + 1000]) block = create_block(tip, coinbase, next_time, [tx6]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-in-belowout") tx5 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(21000001 * COIN)]) logging.info("money range") block = create_block(tip, coinbase, next_time, [tx5]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-vout-toolarge") logging.info("bad tx offset") tx_bad = create_broken_transaction(prev_block.vtx[0], 1, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx_bad]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") logging.info("bad tx offset largest number") tx_bad = create_broken_transaction(prev_block.vtx[0], 0xffffffff, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx_bad]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") logging.info("double tx") tx2 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx2, tx2]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") tx3 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 9), int(out_value / 10)]) tx4 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 8), int(out_value / 7)]) logging.info("double spend") block = create_block(tip, coinbase, next_time, [tx3, tx4]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") tx_good = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 50)] * 50) logging.info("good tx") block = create_block(tip, coinbase, next_time, [tx_good]) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) self.nodes[0].validateblocktemplate(hexblk) self.nodes[0].submitblock(hexblk) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = next_time + 600 coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() coinbase3 = create_coinbase(height + 1, coinbase_pubkey) txl = [] for i in range(0, 50): ov = block.vtx[1].vout[i].nValue txl.append(create_transaction(block.vtx[1], i, b'\x51', [int(ov / 50)] * 50)) block = create_block(tip, coinbase, next_time, txl) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) for n in self.nodes: n.validateblocktemplate(hexblk) logging.info("excessive") self.nodes[0].setminingmaxblock(1000) self.nodes[0].setexcessiveblock(1000, 12) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: excessive") self.nodes[0].setexcessiveblock(16 * 1000 * 1000, 12) self.nodes[0].setminingmaxblock(1000 * 1000) for it in range(0, 100): # if (it&1023)==0: print(it) h2 = hexblk pos = random.randint(0, len(hexblk)) val = random.randint(0, 15) h3 = h2[:pos] + ('%x' % val) + h2[pos + 1:] try: self.nodes[0].validateblocktemplate(h3) except JSONRPCException as e: if not (e.error["code"] == -1 or e.error["code"] == -22): print(str(e)) # its ok we expect garbage self.nodes[1].submitblock(hexblk) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = next_time + 600 prev_block = block txl = [] for tx in prev_block.vtx: for outp in range(0, len(tx.vout)): ov = tx.vout[outp].nValue txl.append(create_transaction(tx, outp, CScript([OP_CHECKSIG] * 100), [int(ov / 2)] * 2)) block = create_block(tip, coinbase, next_time, txl) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) for n in self.nodes: expectException(lambda: n.validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-blk-sigops")
class RPCSendRawTransactions(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.genesisactivationheight = 600 self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.wrong_key = CECKey() self.wrong_key.set_secretbytes(b"horseradish") self.locking_script = CScript([self.coinbase_pubkey, OP_CHECKSIG]) self.default_args = [ '-debug', '-maxgenesisgracefulperiod=0', '-genesisactivationheight=%d' % self.genesisactivationheight ] self.extra_args = [self.default_args] * self.num_nodes self.private_key = CECKey() self.private_key.set_secretbytes(b"fatstacks") self.public_key = self.private_key.get_pubkey() def run_test(self): self.test.run() # Sign a transaction, using the key we know about. # This signs input 0 in tx, which is assumed to be spending output n in spend_tx def sign_tx(self, tx, spend_tx, n, *, key): scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) sighash = SignatureHashForkId(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue) tx.vin[0].scriptSig = CScript([ key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) ]) def check_mempool(self, rpc, should_be_in_mempool, timeout=20): wait_until(lambda: set(rpc.getrawmempool()) == {t.hash for t in should_be_in_mempool}, timeout=timeout) # Generating transactions in order so first transaction's output will be an input for second transaction def get_chained_transactions(self, spend, num_of_transactions, *, money_to_spend=5000000000, bad_chain=False): ok = [] bad = [] orphan = [] bad_transaction = num_of_transactions // 2 if bad_chain else num_of_transactions for i in range(0, num_of_transactions): money_to_spend = money_to_spend - 1000 # one satoshi to fee tx = create_transaction(spend.tx, spend.n, b"", money_to_spend, self.locking_script) self.sign_tx(tx, spend.tx, spend.n, key=self.coinbase_key if i != bad_transaction else self.wrong_key) tx.rehash() txns = ok if i < bad_transaction else bad if i == bad_transaction else orphan txns.append(tx) spend = PreviousSpendableOutput(tx, 0) return ok, bad, orphan # Create a required number of chains with equal length. def get_txchains_n(self, num_of_chains, chain_length, spend, *, num_of_bad_chains): assert (0 <= num_of_bad_chains <= num_of_chains) if num_of_chains > len(spend): raise Exception('Insufficient number of spendable outputs.') txok = [] txbad = [] txorphan = [] bad_chain_marker = ([True] * num_of_bad_chains + [False] * (num_of_chains - num_of_bad_chains)) random.shuffle(bad_chain_marker) for x, bad_chain in enumerate(bad_chain_marker): ok, bad, orphan = self.get_chained_transactions( spend[x], chain_length, bad_chain=bad_chain) txok.extend(ok) txbad.extend(bad) txorphan.extend(orphan) return txok, txbad, txorphan # Test an expected valid results, depending on node's configuration. def run_scenario1(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, listunconfirmedancestors=False, useRpcWithDefaults=False, shuffle_txs=False, timeout=30): # Create and send tx chains. txchains, bad, orphan = self.get_txchains_n(num_of_chains, chain_length, spend, num_of_bad_chains=0) # Shuffle txs if it is required if shuffle_txs: random.shuffle(txchains) # Prepare inputs for sendrawtransactions rpc_txs_bulk_input = [] for tx in range(len(txchains)): # Collect txn input data for bulk submit through rpc interface. if useRpcWithDefaults: rpc_txs_bulk_input.append({'hex': ToHex(txchains[tx])}) else: rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee, 'listunconfirmedancestors': listunconfirmedancestors }) # Submit bulk tranactions. result = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) if listunconfirmedancestors: assert_equal(len(result), 1) assert_equal(len(result['unconfirmed']), num_of_chains * chain_length) first_in_chain = 0 expected_ancestors = [] # A map of transactions and their known parents to be checked in 'vin' parentsMap = {} parentTxId = "" # All transactions and their unconfirmed ancestors are part of sendrawtransactions inputs # First transaction in each chain should not have any unconfirmed ancestors # Next transactions in the chain have increasing number of unconfirmed ancestors for tx in result['unconfirmed']: if len(tx['ancestors']) == 0: first_in_chain += 1 # reset, since this is a new chain expected_ancestors = [] parentsMap = {} parentTxId = "" else: # we expect to have increasing number of unconfirmed ancestors by each transaction in this chain assert_equal(len(tx['ancestors']), len(expected_ancestors)) for ancestor in tx['ancestors']: assert (ancestor['txid'] in expected_ancestors) # each ancestor has 1 input assert_equal(len(ancestor['vin']), 1) # check input if ancestor['txid'] in parentsMap: assert_equal(ancestor['vin'][0]['txid'], parentsMap[ancestor['txid']]) expected_ancestors.append(tx['txid']) if parentTxId: parentsMap[tx['txid']] = parentTxId # Each chain should have one transaction (first in chain) without any unconfirmed ancestors assert_equal(first_in_chain, num_of_chains) else: # There should be no rejected transactions. assert_equal(len(result), 0) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, txchains, timeout) # Test an expected invalid results and invalid input data conditions. def run_scenario2(self, conn, timeout=30): # # sendrawtransactions with missing input # # inputs = [{ 'txid': "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout': 1 }] # won't exists outputs = {conn.rpc.getnewaddress(): 4.998} rawtx = conn.rpc.createrawtransaction(inputs, outputs) rawtx = conn.rpc.signrawtransaction(rawtx) rejected_txns = conn.rpc.sendrawtransactions([{'hex': rawtx['hex']}]) assert_equal(len(rejected_txns['invalid']), 1) # Reject invalid assert_equal(rejected_txns['invalid'][0]['reject_code'], 16) assert_equal(rejected_txns['invalid'][0]['reject_reason'], "missing-inputs") # No transactions should be in the mempool. assert_equal(conn.rpc.getmempoolinfo()['size'], 0) # # An empty json array of objects. # assert_raises_rpc_error( -8, "Invalid parameter: An empty json array of objects", conn.rpc.sendrawtransactions, []) # # An empty json object. # assert_raises_rpc_error(-8, "Invalid parameter: An empty json object", conn.rpc.sendrawtransactions, [{}]) # # Missing the hex string of the raw transaction. # assert_raises_rpc_error( -8, "Invalid parameter: Missing the hex string of the raw transaction", conn.rpc.sendrawtransactions, [{ 'dummy_str': 'dummy_value' }]) assert_raises_rpc_error( -8, "Invalid parameter: Missing the hex string of the raw transaction", conn.rpc.sendrawtransactions, [{ 'hex': -1 }]) # # TX decode failed. # assert_raises_rpc_error(-22, "TX decode failed", conn.rpc.sendrawtransactions, [{ 'hex': '050000000100000000a0ce6e35' }]) # # allowhighfees: Invalid value # assert_raises_rpc_error(-8, "allowhighfees: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'allowhighfees': -1 }]) assert_raises_rpc_error(-8, "allowhighfees: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'allowhighfees': 'dummy_value' }]) # # dontcheckfee: Invalid value # assert_raises_rpc_error(-8, "dontcheckfee: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'dontcheckfee': -1 }]) assert_raises_rpc_error(-8, "dontcheckfee: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'dontcheckfee': 'dummy_value' }]) # # listunconfirmedancestors: Invalid value # assert_raises_rpc_error(-8, "listunconfirmedancestors: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'listunconfirmedancestors': -1 }]) assert_raises_rpc_error(-8, "listunconfirmedancestors: Invalid value", conn.rpc.sendrawtransactions, [{ 'hex': rawtx['hex'], 'listunconfirmedancestors': 'dummy_value' }]) # Test an attempt to submit transactions (via rpc interface) which are already known # - received earlier through the p2p interface and not processed yet def run_scenario3(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, timeout=30): # Create and send tx chains. txchains, bad, orphan = self.get_txchains_n(num_of_chains, chain_length, spend, num_of_bad_chains=0) # Prepare inputs for sendrawtransactions rpc_txs_bulk_input = [] for tx in range(len(txchains)): # Collect txn input data for bulk submit through rpc interface. rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) # Send a txn, one by one, through p2p interface. conn.send_message(msg_tx(txchains[tx])) # Check if there is an expected number of transactions in the validation queues # - this scenario relies on ptv delayed processing wait_until(lambda: conn.rpc.getblockchainactivity()["transactions"] == num_of_chains * chain_length, timeout=timeout) # Submit a batch of txns through rpc interface. rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) # There should be num_of_chains * chain_length rejected transactions. # - there are num_of_chains*chain_length known transactions # - due to the fact that all were received through the p2p interface # - all are waiting in the ptv queues assert_equal(len(rejected_txns['known']), num_of_chains * chain_length) # No transactions should be in the mempool. assert_equal(conn.rpc.getmempoolinfo()['size'], 0) # Test duplicated input data set submitted through the rpc interface. # - input data are shuffled def run_scenario4(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, timeout=30): # Create and send tx chains. txchains, bad, orphan = self.get_txchains_n(num_of_chains, chain_length, spend, num_of_bad_chains=0) # Prepare duplicated inputs for sendrawtransactions rpc_txs_bulk_input = [] for tx in range(len(txchains)): rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) # Shuffle inputs. random.shuffle(rpc_txs_bulk_input) # Submit bulk input. rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) # There should be rejected known transactions. assert_equal(len(rejected_txns), 1) assert_equal(len(rejected_txns['known']), num_of_chains * chain_length) assert (set(rejected_txns['known']) == {t.hash for t in txchains}) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, txchains, timeout) # test an attempt to submit bad transactions in a chain through the rpc interface def run_scenario5(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, reverseOrder=False, timeout=30): # Create and send tx chains. num_of_bad_chains = num_of_chains // 3 ok, bad, orphan = self.get_txchains_n( num_of_chains, chain_length, spend, num_of_bad_chains=num_of_bad_chains) # Prepare inputs for sendrawtransactions rpc_txs_bulk_input = [] txs = ok + bad + orphan if reverseOrder: txs.reverse() for tx in txs: # Collect txn input data for bulk submit through rpc interface. rpc_txs_bulk_input.append({ 'hex': ToHex(tx), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) # Submit a batch of txns through rpc interface. rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) for k, v in rejected_txns.items(): self.log.info("====== rejected_txns[%s] = %s", k, v) assert_equal(len(rejected_txns), 1) assert_equal(len(rejected_txns['invalid']), len(bad) + len(orphan)) def reject_reason(x): return x['reject_reason'] invalid = { k: list(v) for k, v in itertools.groupby(sorted(rejected_txns['invalid'], key=reject_reason), key=reject_reason) } self.log.info("invalid: %s", invalid) missing_inputs = invalid.pop('missing-inputs') assert_equal({x["txid"] for x in missing_inputs}, {x.hash for x in orphan}) reason, rejected = invalid.popitem() assert_equal(reason.startswith('mandatory-script-verify-flag-failed '), True) assert_equal({x["txid"] for x in rejected}, {x.hash for x in bad}) # Valid transactions should be in the mempool. assert_equal(conn.rpc.getmempoolinfo()['size'], len(ok)) def make_block(self, txs, parent_hash, parent_height, parent_time): """ creates a block with given transactions""" block = create_block(int(parent_hash, 16), coinbase=create_coinbase(pubkey=self.public_key, height=parent_height + 1), nTime=parent_time + 1) block.vtx.extend(txs) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() block.solve() return block # Test an attempt to submit transactions (via rpc interface) which are already mined def run_scenario6(self, conn, num_of_chains, chain_length, spend, allowhighfees=False, dontcheckfee=False, timeout=30): # Create and send tx chains. txchains, bad, orphan = self.get_txchains_n(num_of_chains, chain_length, spend, num_of_bad_chains=0) to_mine = [] # Prepare inputs for sendrawtransactions rpc_txs_bulk_input = [] for tx in range(len(txchains)): # Collect txn input data for bulk submit through rpc interface. rpc_txs_bulk_input.append({ 'hex': ToHex(txchains[tx]), 'allowhighfees': allowhighfees, 'dontcheckfee': dontcheckfee }) if tx < len(txchains) // 2: # First half of txns will be mined in a block submitted through p2p interface. to_mine.append(txchains[tx]) root_block_info = conn.rpc.getblock(conn.rpc.getbestblockhash()) root_hash = root_block_info["hash"] root_height = root_block_info["height"] root_time = root_block_info["time"] # create the block block = self.make_block(to_mine, root_hash, root_height, root_time) conn.send_message(msg_block(block)) wait_until(lambda: conn.rpc.getbestblockhash() == block.hash, check_interval=0.3) # Check if there is an expected number of transactions in the mempool assert_equal(conn.rpc.getmempoolinfo()['size'], 0) # Submit a batch of txns through rpc interface. rejected_txns = conn.rpc.sendrawtransactions(rpc_txs_bulk_input) # There should be to_mine rejected transactions. assert_equal(len(rejected_txns['invalid']), len(to_mine)) # bitcoind knows about the outputs of the last already mined transaction assert_equal( len([ tx for tx in rejected_txns['invalid'] if tx['reject_reason'] == 'txn-already-known' ]), 1) # bitcoind knows nothing about the previous already mined transactions so it considers them orphans assert_equal( len([ tx for tx in rejected_txns['invalid'] if tx['reject_reason'] == 'missing-inputs' ]), len(to_mine) - 1) # No transactions that were already mined should be in the mempool. The rest should be assert_equal(conn.rpc.getmempoolinfo()['size'], len(txchains) - len(to_mine)) def get_tests(self): rejected_txs = [] def on_reject(conn, msg): rejected_txs.append(msg) # Shorthand for functions block = self.chain.next_block node = self.nodes[0] self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) # Create a new block block(0, coinbase_pubkey=self.coinbase_pubkey) self.chain.save_spendable_output() yield self.accepted() # Now we need that block to mature so we can spend the coinbase. # Also, move block height on beyond Genesis activation. test = TestInstance(sync_every_block=False) for i in range(600): block(5000 + i, coinbase_pubkey=self.coinbase_pubkey) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on. out = [] for i in range(200): out.append(self.chain.get_spendable_output()) self.stop_node(0) #==================================================================== # Valid test cases. # - a bulk submit of txns through sendrawtransactions rpc interface # - all transactions are valid and accepted by the mempool #==================================================================== # Scenario 1 (TS1). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, with default params. # - 1K txs used # - allowhighfees=False (default) # - dontcheckfee=False (default) # - listunconfirmedancestors=False (default) # - txn chains are in ordered sequence (no orphans should be detected during processing) # # Test case config num_of_chains = 10 chain_length = 100 # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS1: {} chains of length {}. Default params for rpc call.'. format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario1(conn, num_of_chains, chain_length, out, useRpcWithDefaults=True, timeout=20) # Scenario 2 (TS2). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, with default params. # - 1K txs used # - allowhighfees=False (default) # - dontcheckfee=False (default) # - listunconfirmedancestors=False (default) # - txn chains are shuffled (orphans should be detected during processing) # # Test case config num_of_chains = 10 chain_length = 100 # Node's config args = [ '-txnvalidationasynchrunfreq=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS2: {} chains of length {}. Shuffled txs. Default params for rpc call.' .format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario1(conn, num_of_chains, chain_length, out, useRpcWithDefaults=True, shuffle_txs=True, timeout=20) # Scenario 3 (TS3). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, with default params. # - 10K txs used # - allowhighfees=False (default) # - dontcheckfee=False (default) # - listunconfirmedancestors=False (default) # - txn chains are in ordered sequence (no orphans should be detected during processing) # # Test case config num_of_chains = 100 chain_length = 100 # Node's config args = [ '-txnvalidationasynchrunfreq=0', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS3: {} chains of length {}. Default params for rpc call.'. format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario1(conn, num_of_chains, chain_length, out, useRpcWithDefaults=True, timeout=30) # Scenario 4 (TS5). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, with explicitly declared default params. # - 1K txs used # - allowhighfees=False (an explicit default value) # - dontcheckfee=False (an explicit default value) # - listunconfirmedancestors=False (an explicit default value) # - txn chains are in ordered sequence (no orphans should be detected during processing) # # Test case config num_of_chains = 10 chain_length = 100 allowhighfees = False dontcheckfee = False listunconfirmedancestors = False # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS4: {} chains of length {}. allowhighfees={}, dontcheckfee={}, listunconfirmedancestors{}.' .format(num_of_chains, chain_length, str(allowhighfees), str(dontcheckfee), str(listunconfirmedancestors)), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario1(conn, num_of_chains, chain_length, out, allowhighfees, dontcheckfee, listunconfirmedancestors, timeout=20) # Scenario 5 (TS5). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, with non-default params. # - 1K txs used # - allowhighfees=True # - dontcheckfee=True # - listunconfirmedancestors=True # - txn chains are in ordered sequence (no orphans should be detected during processing) # # Test case config num_of_chains = 10 chain_length = 100 allowhighfees = True dontcheckfee = True listunconfirmedancestors = True # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS5: {} chains of length {}. allowhighfees={}, dontcheckfee={}, listunconfirmedancestors{}.' .format(num_of_chains, chain_length, str(allowhighfees), str(dontcheckfee), str(listunconfirmedancestors)), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario1(conn, num_of_chains, chain_length, out, allowhighfees, dontcheckfee, listunconfirmedancestors, timeout=20) #==================================================================== # Invalid test cases and non-empty rejects # - test invalid data # - test rejected transactions # - test duplicates #==================================================================== # Scenario 6 (TS6). # # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS6: Invalid conditions', 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario2(conn, timeout=20) # Scenario 7 (TS7). # # Test case config num_of_chains = 10 chain_length = 10 # Node's config args = [ '-txnvalidationasynchrunfreq=10000', '-maxstdtxnsperthreadratio=0', # Do not take any std txs for processing (from the ptv queues). '-maxnonstdtxnsperthreadratio=0', # Do not take any non-std txs for processing (from the ptv queues). '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS7: {} chains of length {}. Reject known transactions'. format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario3(conn, num_of_chains, chain_length, out, timeout=30) # Scenario 8 (TS8). # This test case checks a bulk submit of duplicated txs, through rpc sendrawtransactions interface. # - 2K txs used (1K are detected as duplicates - known transactions in the result set) # - rpc input data set is shuffled # # Test case config num_of_chains = 10 chain_length = 100 # Node's config args = [ '-txnvalidationasynchrunfreq=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS8: {} chains of length {}. Test duplicated inputs.'.format( num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario4(conn, num_of_chains, chain_length, out, timeout=20) # Scenario 9 (TS9). # # This test case checks bulk submit of chains with bad transactions in # the middle of the chain, through the rpc sendrawtransactions # interface. # # Test case config num_of_chains = 10 chain_length = 10 # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS9: {} chains of length {}. Reject known transactions'. format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario5(conn, num_of_chains, chain_length, out, reverseOrder=False, timeout=30) # Scenario 10 (TS10). # # This test case checks bulk submit of chains with bad transactions in # the middle of the chain, through the rpc sendrawtransactions # interface. # Essentially the same as Scenario 9 but txns are submitted in reversed order. # # Test case config num_of_chains = 10 chain_length = 10 # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-maxorphantxsize=0', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS10: {} chains of length {}. Reject known transactions'. format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario5(conn, num_of_chains, chain_length, out, reverseOrder=True, timeout=30) # Scenario 11 (TS11). # This test case checks a bulk submit of txs, through rpc sendrawtransactions interface, where some submitted transactions are already mined. # # Test case config num_of_chains = 1 chain_length = 10 # Node's config args = [ '-txnvalidationasynchrunfreq=100', '-limitancestorcount=100', '-checkmempool=0', '-persistmempool=0' ] with self.run_node_with_connections( 'TS11: {} chains of length {}. Pre-mined txs. Default params for rpc call.' .format(num_of_chains, chain_length), 0, args + self.default_args, number_of_connections=1) as (conn, ): # Run test case. self.run_scenario6(conn, num_of_chains, chain_length, out, timeout=20)
class TxnPropagationAfterBlock(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.tip = None self.block_time = None self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.extra_args = [['-broadcastdelay=50000', '-txnpropagationfreq=50000']] * self.num_nodes def run_test(self): self.test.run() # Sign a transaction, using the key we know about. # This signs input 0 in tx, which is assumed to be spending output n in spend_tx 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]))]) # Create a new block with some number of valid spending txns 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_tests(self): node = self.nodes[0] self.chain.set_genesis_hash( int(node.getbestblockhash(), 16) ) # Create a new block self.next_block(0) self.chain.save_spendable_output() yield self.accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(140): self.next_block(5000 + i) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(self.chain.get_spendable_output()) # Create blocks with multiple txns in block1 = self.next_block(1, spend=out[0:20]) block2 = self.next_block(2, spend=out[20:40]) # Check frequency propagator runs has been correctly set to very slow (we will poke as required) assert_equal(self.nodes[0].getnetworkinfo()['txnpropagationfreq'], 50000) # Get half of the txns from each block into the peers inventory queue for t in range(1, 11): self.nodes[0].sendrawtransaction(bytes_to_hex_str(block1.vtx[t].serialize()), True) self.nodes[0].sendrawtransaction(bytes_to_hex_str(block2.vtx[t].serialize()), True) self.nodes[0].settxnpropagationfreq(50000) wait_until(lambda: self.nodes[0].getpeerinfo()[0]['txninvsize'] == 20) # Feed in the other half of txns to just the txn propagator queue for t in range(11, 21): self.nodes[0].sendrawtransaction(bytes_to_hex_str(block1.vtx[t].serialize()), True) self.nodes[0].sendrawtransaction(bytes_to_hex_str(block2.vtx[t].serialize()), True) assert_equal(self.nodes[0].getnetworkinfo()['txnpropagationqlen'], 20) assert_equal(self.nodes[0].getmempoolinfo()['size'], 40) # Mine the first new block yield TestInstance([[block1, True]]) # Check the txns from the mined block have gone from the propagator queue and the nodes queue assert_equal(self.nodes[0].getnetworkinfo()['txnpropagationqlen'], 10) assert_equal(self.nodes[0].getpeerinfo()[0]['txninvsize'], 10) assert_equal(self.nodes[0].getmempoolinfo()['size'], 20)
def run_test(self): self.node = self.nodes[0] self.bootstrap_p2p() # returns a test case that asserts that the current tip was accepted # First generate some blocks so we have some spendable coins block_hashes = self.node.generate(25) for i in range(COINBASE_MATURITY): self.tip = create_block( int(self.node.getbestblockhash(), 16), create_coinbase(self.node.getblockcount() + 1), int(time.time())) self.tip.solve() self.sync_blocks([self.tip], success=True) for _ in range(10): self.node.sendtoaddress(self.node.getnewaddress(), 1000) block_hashes += self.node.generate(1) blocks = [] for block_hash in block_hashes: blocks.append(self.node.getblock(block_hash)) # These are our staking txs self.staking_prevouts = [] self.bad_vout_staking_prevouts = [] self.bad_txid_staking_prevouts = [] self.unconfirmed_staking_prevouts = [] for unspent in self.node.listunspent(): for block in blocks: if unspent['txid'] in block['tx']: tx_block_time = block['time'] break else: assert (False) if unspent['confirmations'] > COINBASE_MATURITY: self.staking_prevouts.append( (COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount']) * COIN, tx_block_time)) self.bad_vout_staking_prevouts.append( (COutPoint(int(unspent['txid'], 16), 0xff), int(unspent['amount']) * COIN, tx_block_time)) self.bad_txid_staking_prevouts.append( (COutPoint(int(unspent['txid'], 16) + 1, unspent['vout']), int(unspent['amount']) * COIN, tx_block_time)) if unspent['confirmations'] < COINBASE_MATURITY: self.unconfirmed_staking_prevouts.append( (COutPoint(int(unspent['txid'], 16), unspent['vout']), int(unspent['amount']) * COIN, tx_block_time)) # First let 25 seconds pass so that we do not submit blocks directly after the last one #time.sleep(100) block_count = self.node.getblockcount() # 1 A block that does not have the correct timestamp mask t = int(time.time()) | 1 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, request_block=False, reconnect=True) # 2 A block that with a too high reward (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, outNValue=30006) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 3 A block with an incorrect block sig bad_key = CECKey() bad_key.set_secretbytes(hash256(b'horse staple battery')) (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.sign_block(bad_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 4 A block that stakes with txs with too few confirmations (self.tip, block_sig_key) = self.create_unsigned_pos_block( self.unconfirmed_staking_prevouts) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True, request_block=False) # 5 A block that with a coinbase reward (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[0].vout[0].nValue = 1 self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 6 A block that with no vout in the coinbase (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[0].vout = [] self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 7 A block way into the future t = (int(time.time()) + 100) & 0xfffffff0 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, request_block=False, reconnect=True) # 8 No vout in the staking tx (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[1].vout = [] self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 9 Unsigned coinstake. (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, signStakeTx=False) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 10 A block without a coinstake tx. (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx.pop(-1) self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 11 A block without a coinbase. (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx.pop(0) self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 12 A block where the coinbase has no outputs (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[0].vout = [] self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 13 A block where the coinstake has no outputs (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[1].vout.pop(-1) self.tip.vtx[1].vout.pop(-1) stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet( bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex'] f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) self.tip.vtx[1] = CTransaction() self.tip.vtx[1].deserialize(f) self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 14 A block with an incorrect hashStateRoot (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.hashStateRoot = 0xe self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 15 A block with an incorrect hashUTXORoot (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.hashUTXORoot = 0xe self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 16 A block with an a signature on wrong header data (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.sign_block(block_sig_key) self.tip.nNonce = 0xfffe self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 17 A block with where the pubkey of the second output of the coinstake has been modified after block signing (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) scriptPubKey = self.tip.vtx[1].vout[1].scriptPubKey # Modify a byte of the pubkey self.tip.vtx[1].vout[ 1].scriptPubKey = scriptPubKey[0:20] + bytes.fromhex( hex(ord(scriptPubKey[20:21]) + 1)[2:4]) + scriptPubKey[21:] assert_equal(len(scriptPubKey), len(self.tip.vtx[1].vout[1].scriptPubKey)) stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet( bytes_to_hex_str(self.tip.vtx[1].serialize()))['hex'] f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) self.tip.vtx[1] = CTransaction() self.tip.vtx[1].deserialize(f) self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 18. A block in the past t = (int(time.time()) - 700) & 0xfffffff0 (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts, nTime=t) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, request_block=False, reconnect=True) # 19. A block with too many coinbase vouts (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.vtx[0].vout.append(CTxOut(0, CScript([OP_TRUE]))) self.tip.vtx[0].rehash() self.tip.hashMerkleRoot = self.tip.calc_merkle_root() self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 20. A block where the coinstake's vin is not the prevout specified in the block (self.tip, block_sig_key) = self.create_unsigned_pos_block( self.staking_prevouts, coinStakePrevout=self.staking_prevouts[-1][0]) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True) # 21. A block that stakes with valid txs but invalid vouts (self.tip, block_sig_key) = self.create_unsigned_pos_block( self.bad_vout_staking_prevouts) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True, request_block=False) # 22. A block that stakes with txs that do not exist (self.tip, block_sig_key) = self.create_unsigned_pos_block( self.bad_txid_staking_prevouts) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=False, reconnect=True, request_block=False) # Make sure for certain that no blocks were accepted. (This is also to make sure that no segfaults ocurred) assert_equal(self.node.getblockcount(), block_count) # And at last, make sure that a valid pos block is accepted (self.tip, block_sig_key) = self.create_unsigned_pos_block(self.staking_prevouts) self.tip.sign_block(block_sig_key) self.tip.rehash() self.sync_blocks([self.tip], success=True) assert_equal(self.node.getblockcount(), block_count + 1)
def run_test(self): node = self.nodes[0] # Generate 6 keys. rawkeys = [] pubkeys = [] for i in range(6): raw_key = CECKey() raw_key.set_secretbytes(('privkey%d' % i).encode('ascii')) rawkeys.append(raw_key) pubkeys = [CPubKey(key.get_pubkey()) for key in rawkeys] # Create a 4-of-6 multi-sig wallet with CLTV. height = 210 redeem_script = CScript( [CScriptNum(height), OP_CHECKLOCKTIMEVERIFY, OP_DROP] + # CLTV (lock_time >= 210) [OP_4] + pubkeys + [OP_6, OP_CHECKMULTISIG]) # multi-sig hex_redeem_script = bytes_to_hex_str(redeem_script) p2sh_address = script_to_p2sh(redeem_script, main=False) # Send 1 coin to the mult-sig wallet. txid = node.sendtoaddress(p2sh_address, 1.0) raw_tx = node.getrawtransaction(txid, True) try: node.importaddress(hex_redeem_script, 'cltv', True, True) except Exception as err: pass assert_equal(sig(node.getreceivedbyaddress(p2sh_address, 0) - Decimal(1.0)), 0) # Mine one block to confirm the transaction. node.generate(1) # block 201 assert_equal(sig(node.getreceivedbyaddress(p2sh_address, 1) - Decimal(1.0)), 0) # Try to spend the coin. addr_to = node.getnewaddress('') # (1) Find the UTXO for vout in raw_tx['vout']: if vout['scriptPubKey']['addresses'] == [p2sh_address]: vout_n = vout['n'] hex_script_pubkey = raw_tx['vout'][vout_n]['scriptPubKey']['hex'] value = raw_tx['vout'][vout_n]['value'] # (2) Create a tx inputs = [{ "txid": txid, "vout": vout_n, "scriptPubKey": hex_script_pubkey, "redeemScript": hex_redeem_script, "amount": value, }] outputs = {addr_to: 0.999} lock_time = height hex_spend_raw_tx = node.createrawtransaction(inputs, outputs, lock_time) hex_funding_raw_tx = node.getrawtransaction(txid, False) # (3) Try to sign the spending tx. tx0 = CTransaction() tx0.deserialize(io.BytesIO(hex_str_to_bytes(hex_funding_raw_tx))) tx1 = CTransaction() tx1.deserialize(io.BytesIO(hex_str_to_bytes(hex_spend_raw_tx))) self.sign_tx(tx1, tx0, vout_n, redeem_script, 0, rawkeys[:4]) # Sign with key[0:4] # Mine some blocks to pass the lock time. node.generate(10) # Spend the CLTV multi-sig coins. raw_tx1 = tx1.serialize() hex_raw_tx1 = bytes_to_hex_str(raw_tx1) node.sendrawtransaction(hex_raw_tx1) # Check the tx is accepted by mempool but not confirmed. assert_equal(sig(node.getreceivedbyaddress(addr_to, 0) - Decimal(0.999)), 0) assert_equal(sig(node.getreceivedbyaddress(addr_to, 1)), 0) # Mine a block to confirm the tx. node.generate(1) assert_equal(sig(node.getreceivedbyaddress(addr_to, 1) - Decimal(0.999)), 0)
class FullBlockTest(ComparisonTestFramework): ''' Can either run this test as 1 node with expected answers, or two and compare them. Change the "outcome" variable from each TestInstance object to only do the comparison. ''' def __init__(self): self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.block_time = int(time.time())+1 self.tip = None self.blocks = {} def run_test(self): test = TestManager(self, self.options.tmpdir) test.add_all_connections(self.nodes) NetworkThread().start() # Start up network handling in another thread sync_stormnodes(self.nodes) test.run() def add_transactions_to_block(self, block, tx_list): [ tx.rehash() for tx in tx_list ] block.vtx.extend(tx_list) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() return block # Create a block on top of self.tip, and advance self.tip to point to the new block # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, # and rest will go to fees. def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): if self.tip == None: base_block_hash = self.genesis_hash else: base_block_hash = self.tip.sha256 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, self.block_time) if (spend != None): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) # no signature yet # This copies the java comparison tool testing behavior: the first # txout has a garbage scriptPubKey, "to make sure we're not # pre-verifying too much" (?) tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) # Now sign it if necessary scriptSig = b"" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL) scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block block = self.add_transactions_to_block(block, [tx]) block.solve() self.tip = block self.block_heights[block.sha256] = height self.block_time += 1 assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previous marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject = None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # add transactions to a block produced by next_block def update_block(block_number, new_transactions): block = self.blocks[block_number] old_hash = block.sha256 self.add_transactions_to_block(block, new_transactions) block.solve() # Update the internal state just like in next_block self.tip = block self.block_heights[block.sha256] = self.block_heights[old_hash] del self.block_heights[old_hash] self.blocks[block_number] = block return block # creates a new block and advances the tip to that block block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(1000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Start by building a couple of blocks on top (which output is spent is # in parentheses): # genesis -> b1 (0) -> b2 (1) out0 = get_spendable_output() block(1, spend=out0) save_spendable_output() yield accepted() out1 = get_spendable_output() b2 = block(2, spend=out1) yield accepted() # so fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes priority. tip(1) b3 = block(3, spend=out1) txout_b3 = PreviousSpendableOutput(b3.vtx[1], 1) yield rejected() # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) out2 = get_spendable_output() block(4, spend=out2) yield accepted() # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) tip(2) block(5, spend=out2) save_spendable_output() yield rejected() out3 = get_spendable_output() block(6, spend=out3) yield accepted() # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) tip(5) block(7, spend=out2) yield rejected() out4 = get_spendable_output() block(8, spend=out4) yield rejected() # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) tip(6) block(9, spend=out4, additional_coinbase_value=1) yield rejected(RejectResult(16, b'bad-cb-amount')) # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) tip(5) block(10, spend=out3) yield rejected() block(11, spend=out4, additional_coinbase_value=1) yield rejected(RejectResult(16, b'bad-cb-amount')) # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) # (b12 added last) # \-> b3 (1) -> b4 (2) tip(5) b12 = block(12, spend=out3) save_spendable_output() #yield TestInstance([[b12, False]]) b13 = block(13, spend=out4) # Deliver the block header for b12, and the block b13. # b13 should be accepted but the tip won't advance until b12 is delivered. yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) save_spendable_output() out5 = get_spendable_output() # b14 is invalid, but the node won't know that until it tries to connect # Tip still can't advance because b12 is missing block(14, spend=out5, additional_coinbase_value=1) yield rejected() yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6) # \-> b3 (1) -> b4 (2) # Test that a block with a lot of checksigs is okay lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50 - 1)) tip(13) block(15, spend=out5, script=lots_of_checksigs) yield accepted() # Test that a block with too many checksigs is rejected out6 = get_spendable_output() too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50)) block(16, spend=out6, script=too_many_checksigs) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Attempt to spend a transaction created on a different fork # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) # \-> b3 (1) -> b4 (2) tip(15) block(17, spend=txout_b3) yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) # Attempt to spend a transaction created on a different fork (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b18 (b3.vtx[1]) -> b19 (6) # \-> b3 (1) -> b4 (2) tip(13) block(18, spend=txout_b3) yield rejected() block(19, spend=out6) yield rejected() # Attempt to spend a coinbase at depth too low # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) # \-> b3 (1) -> b4 (2) tip(15) out7 = get_spendable_output() block(20, spend=out7) yield rejected(RejectResult(16, b'bad-txns-premature-spend-of-coinbase')) # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b21 (6) -> b22 (5) # \-> b3 (1) -> b4 (2) tip(13) block(21, spend=out6) yield rejected() block(22, spend=out5) yield rejected() # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) # \-> b24 (6) -> b25 (7) # \-> b3 (1) -> b4 (2) tip(15) b23 = block(23, spend=out6) old_hash = b23.sha256 tx = CTransaction() script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) tx.vout.append(CTxOut(0, script_output)) tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 1))) b23 = update_block(23, [tx]) # Make sure the math above worked out to produce a max-sized block assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE) yield accepted() # Make the next block one byte bigger and check that it fails tip(15) b24 = block(24, spend=out6) script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69 script_output = CScript([b'\x00' * (script_length+1)]) tx.vout = [CTxOut(0, script_output)] b24 = update_block(24, [tx]) assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE+1) yield rejected(RejectResult(16, b'bad-blk-length')) b25 = block(25, spend=out7) yield rejected() # Create blocks with a coinbase input script size out of range # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) # \-> ... (6) -> ... (7) # \-> b3 (1) -> b4 (2) tip(15) b26 = block(26, spend=out6) b26.vtx[0].vin[0].scriptSig = b'\x00' b26.vtx[0].rehash() # update_block causes the merkle root to get updated, even with no new # transactions, and updates the required state. b26 = update_block(26, []) yield rejected(RejectResult(16, b'bad-cb-length')) # Extend the b26 chain to make sure darksilkd isn't accepting b26 b27 = block(27, spend=out7) yield rejected() # Now try a too-large-coinbase script tip(15) b28 = block(28, spend=out6) b28.vtx[0].vin[0].scriptSig = b'\x00' * 101 b28.vtx[0].rehash() b28 = update_block(28, []) yield rejected(RejectResult(16, b'bad-cb-length')) # Extend the b28 chain to make sure darksilkd isn't accepted b28 b29 = block(29, spend=out7) # TODO: Should get a reject message back with "bad-prevblk", except # there's a bug that prevents this from being detected. Just note # failure for now, and add the reject result later. yield rejected() # b30 has a max-sized coinbase scriptSig. tip(23) b30 = block(30) b30.vtx[0].vin[0].scriptSig = b'\x00' * 100 b30.vtx[0].rehash() b30 = update_block(30, []) yield accepted()
def get_tests(self): node = self.nodes[0] self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(15): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE // 2) yield accepted() # Start moving MTP forward bfork = block(5555, out[15], block_size=8 * ONE_MEGABYTE) bfork.nTime = MONOLITH_START_TIME - 1 update_block(5555, []) yield accepted() # Get to one block of the May 15, 2018 HF activation for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check that the MTP is just before the configured fork point. assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], MONOLITH_START_TIME - 1) # Before we acivate the May 15, 2018 HF, 8MB is the limit. block(4444, spend=out[16], block_size=8 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(5104) # Actiavte the May 15, 2018 HF block(5556) yield accepted() # Now MTP is exactly the fork time. Bigger blocks are now accepted. assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], MONOLITH_START_TIME) # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) yield accepted() # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript([OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block(19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) yield accepted() block(20, spend=out[18], script=lots_of_checksigs, block_size=ONE_MEGABYTE, extra_sigops=1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) yield accepted() # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) yield accepted() # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block(29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(26) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"fatstacks") public_key = private_key.get_pubkey() # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_tx(out[22], 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) yield accepted() # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) yield accepted() # Submit a very large block via RPC large_block = block(33, spend=out[24], block_size=self.excessive_block_size) node.submitblock(ToHex(large_block))
def run_test(self): # Connect to node0 node0 = BaseNode() connections = [] connections.append( NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) NetworkThread().start() # Start up network handling in another thread node0.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock( self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase( height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block( self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid # (null) signature tx = CTransaction() tx.vin.append( CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block( self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 2100 deep for i in range(2100): block = create_block( self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Start node1 and node2 with assumevalid so they accept a block with a # bad signature. self.nodes.append(start_node(1, self.options.tmpdir, ["-assumevalid=" + hex(block102.sha256)])) node1 = BaseNode() # connects to node1 connections.append( NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1)) node1.add_connection(connections[1]) node1.wait_for_verack() self.nodes.append(start_node(2, self.options.tmpdir, ["-assumevalid=" + hex(block102.sha256)])) node2 = BaseNode() # connects to node2 connections.append( NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2)) node2.add_connection(connections[2]) node2.wait_for_verack() # send header lists to all three nodes node0.send_header_for_blocks(self.blocks[0:2000]) node0.send_header_for_blocks(self.blocks[2000:]) node1.send_header_for_blocks(self.blocks[0:2000]) node1.send_header_for_blocks(self.blocks[2000:]) node2.send_header_for_blocks(self.blocks[0:200]) # Send 102 blocks to node0. Block 102 will be rejected. for i in range(101): node0.send_message(msg_block(self.blocks[i])) node0.sync_with_ping() # make sure the most recent block is synced node0.send_message(msg_block(self.blocks[101])) assert_equal(self.nodes[0].getblock( self.nodes[0].getbestblockhash())['height'], 101) # Send 3102 blocks to node1. All blocks will be accepted. for i in range(2202): node1.send_message(msg_block(self.blocks[i])) node1.sync_with_ping() # make sure the most recent block is synced assert_equal(self.nodes[1].getblock( self.nodes[1].getbestblockhash())['height'], 2202) # Send 102 blocks to node2. Block 102 will be rejected. for i in range(101): node2.send_message(msg_block(self.blocks[i])) node2.sync_with_ping() # make sure the most recent block is synced node2.send_message(msg_block(self.blocks[101])) assert_equal(self.nodes[2].getblock( self.nodes[2].getbestblockhash())['height'], 101)
def run_test(self): # Generate enough blocks to trigger certain block votes self.nodes[0].generate(1150) self.sync_all() logging.info("not on chain tip") badtip = int( self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16) height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) cur_time = int(time.time()) self.nodes[0].setmocktime(cur_time) self.nodes[1].setmocktime(cur_time) block = create_block(badtip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: does not build on chain tip") logging.info("time too far in the past") block = create_block(tip, coinbase, cur_time) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: time-too-old") logging.info("time too far in the future") block = create_block(tip, coinbase, cur_time + 10000000) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: time-too-new") logging.info("bad version 1") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 1 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad version 2") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 2 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad version 3") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 3 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-version") logging.info("bad coinbase height") tip = int(self.nodes[0].getblockhash(height), 16) block = create_block(tip, create_coinbase(height), cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-cb-height") logging.info("bad merkle root") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.hashMerkleRoot = 0x12345678 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txnmrklroot") logging.info("no tx") block = create_block(tip, None, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-blk-length") logging.info("good block") block = create_block(tip, coinbase, cur_time + 600) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) # ------ self.nodes[0].validateblocktemplate(hexblk) block.solve() hexblk = ToHex(block) self.nodes[0].submitblock(hexblk) self.sync_all() prev_block = block # out_value is less than 50SBT because regtest halvings happen every 150 blocks, and is in Satoshis out_value = block.vtx[0].vout[0].nValue tx1 = create_transaction( prev_block.vtx[0], 0, b'\x51', [int(out_value / 2), int(out_value / 2)]) height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = cur_time + 1200 logging.info("no coinbase") block = create_block(tip, None, next_time, [tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-cb-missing") logging.info("double coinbase") coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() coinbase2 = create_coinbase(height + 1, coinbase_pubkey) block = create_block(tip, coinbase, next_time, [coinbase2, tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-cb-multiple") logging.info("premature coinbase spend") block = create_block(tip, coinbase, next_time, [tx1]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-premature-spend-of-coinbase") self.nodes[0].generate(100) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = cur_time + 1200 logging.info("inputs below outputs") tx6 = create_transaction(prev_block.vtx[0], 0, b'\x51', [out_value + 1000]) block = create_block(tip, coinbase, next_time, [tx6]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-in-belowout") tx5 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(21000001 * COIN)]) logging.info("money range") block = create_block(tip, coinbase, next_time, [tx5]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-vout-toolarge") logging.info("bad tx offset") tx_bad = create_broken_transaction(prev_block.vtx[0], 1, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx_bad]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") logging.info("bad tx offset largest number") tx_bad = create_broken_transaction(prev_block.vtx[0], 0xffffffff, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx_bad]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") logging.info("double tx") tx2 = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 4)]) block = create_block(tip, coinbase, next_time, [tx2, tx2]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") tx3 = create_transaction( prev_block.vtx[0], 0, b'\x51', [int(out_value / 9), int(out_value / 10)]) tx4 = create_transaction( prev_block.vtx[0], 0, b'\x51', [int(out_value / 8), int(out_value / 7)]) logging.info("double spend") block = create_block(tip, coinbase, next_time, [tx3, tx4]) block.nVersion = 0x20000000 block.rehash() hexblk = ToHex(block) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-txns-inputs-missingorspent") tx_good = create_transaction(prev_block.vtx[0], 0, b'\x51', [int(out_value / 50)] * 50) logging.info("good tx") block = create_block(tip, coinbase, next_time, [tx_good]) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) self.nodes[0].validateblocktemplate(hexblk) self.nodes[0].submitblock(hexblk) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = next_time + 600 coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() coinbase3 = create_coinbase(height + 1, coinbase_pubkey) txl = [] for i in range(0, 50): ov = block.vtx[1].vout[i].nValue txl.append( create_transaction(block.vtx[1], i, b'\x51', [int(ov / 50)] * 50)) block = create_block(tip, coinbase, next_time, txl) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) for n in self.nodes: n.validateblocktemplate(hexblk) logging.info("excessive") self.nodes[0].setminingmaxblock(1000) self.nodes[0].setexcessiveblock(1000, 12) expectException(lambda: self.nodes[0].validateblocktemplate(hexblk), JSONRPCException, "invalid block: excessive") self.nodes[0].setexcessiveblock(16 * 1000 * 1000, 12) self.nodes[0].setminingmaxblock(1000 * 1000) for it in range(0, 100): # if (it&1023)==0: print(it) h2 = hexblk pos = random.randint(0, len(hexblk)) val = random.randint(0, 15) h3 = h2[:pos] + ('%x' % val) + h2[pos + 1:] try: self.nodes[0].validateblocktemplate(h3) except JSONRPCException as e: if not (e.error["code"] == -1 or e.error["code"] == -22): print(str(e)) # its ok we expect garbage self.nodes[1].submitblock(hexblk) self.sync_all() height = self.nodes[0].getblockcount() tip = int(self.nodes[0].getblockhash(height), 16) coinbase = create_coinbase(height + 1) next_time = next_time + 600 prev_block = block txl = [] for tx in prev_block.vtx: for outp in range(0, len(tx.vout)): ov = tx.vout[outp].nValue txl.append( create_transaction(tx, outp, CScript([OP_CHECKSIG] * 100), [int(ov / 2)] * 2)) block = create_block(tip, coinbase, next_time, txl) block.nVersion = 0x20000000 block.rehash() block.solve() hexblk = ToHex(block) for n in self.nodes: expectException(lambda: n.validateblocktemplate(hexblk), JSONRPCException, "invalid block: bad-blk-sigops")
class PTVTxnChains(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.genesisactivationheight = 600 self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.locking_script = CScript([self.coinbase_pubkey, OP_CHECKSIG]) self.extra_args = [['-debug', '-genesisactivationheight=%d' % self.genesisactivationheight]] * self.num_nodes def run_test(self): self.test.run() # Sign a transaction, using the key we know about. # This signs input 0 in tx, which is assumed to be spending output n in spend_tx def sign_tx(self, tx, spend_tx, n): scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) 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 check_mempool(self, rpc, should_be_in_mempool, timeout=20): wait_until(lambda: set(rpc.getrawmempool()) == {t.hash for t in should_be_in_mempool}, timeout=timeout) # Generating transactions in order so first transaction's output will be an 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 - 1000 # one satoshi to fee tx = create_transaction(spend.tx, spend.n, b"", money_to_spend, self.locking_script) self.sign_tx(tx, spend.tx, spend.n) tx.rehash() txns.append(tx) spend = PreviousSpendableOutput(tx, 0) return txns # Create a required number of chains with equal length. def get_txchains_n(self, num_of_chains, chain_length, spend): if num_of_chains > len(spend): raise Exception('Insufficient number of spendable outputs.') txchains = [] for x in range(0, num_of_chains): txchains += self.get_chained_transactions(spend[x], chain_length) return txchains def run_scenario1(self, conn, num_of_chains, chain_length, spend, timeout): # Create and send tx chains. txchains = self.get_txchains_n(num_of_chains, chain_length, spend) for tx in range(len(txchains)): conn.send_message(msg_tx(txchains[tx])) # Check if the validation queues are empty. wait_until(lambda: self.nodes[0].rpc.getblockchainactivity()["transactions"] == 0, timeout=timeout) # Check if required transactions are accepted by the mempool. self.check_mempool(conn.rpc, txchains, timeout) def get_tests(self): rejected_txs = [] def on_reject(conn, msg): rejected_txs.append(msg) # Shorthand for functions block = self.chain.next_block node = self.nodes[0] self.chain.set_genesis_hash(int(node.getbestblockhash(), 16)) # Create a new block block(0, coinbase_pubkey=self.coinbase_pubkey) self.chain.save_spendable_output() yield self.accepted() # Now we need that block to mature so we can spend the coinbase. # Also, move block height on beyond Genesis activation. test = TestInstance(sync_every_block=False) for i in range(600): block(5000 + i, coinbase_pubkey=self.coinbase_pubkey) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # Collect spendable outputs now to avoid cluttering the code later on. out = [] for i in range(200): out.append(self.chain.get_spendable_output()) self.stop_node(0) num_of_threads = multiprocessing.cpu_count() # Scenario 1. # This test case shows that false-positive orphans are not created while processing a set of chains, where chainlength=10. # Each thread from the validaiton thread pool should have an assigned chain of txns to process. args = ['-maxorphantxsize=0', '-txnvalidationasynchrunfreq=100', '-checkmempool=0', '-persistmempool=0'] with self.run_node_with_connections('Scenario 1: {} chains of length 10. Storing orphans is disabled.'.format(num_of_threads), 0, args, number_of_connections=1) as (conn,): # Run test case. self.run_scenario1(conn, num_of_threads, 10, out, timeout=20) # Scenario 2. # This test case shows that false-positive orphans are not created while processing a set of chains, where chainlength=20. # Each thread from the validaiton thread pool should have an assigned chain of txns to process. args = ['-maxorphantxsize=0', '-txnvalidationasynchrunfreq=0', '-limitancestorcount=20', '-limitdescendantcount=20', '-checkmempool=0', '-persistmempool=0' '-maxstdtxvalidationduration=100'] with self.run_node_with_connections('Scenario 2: {} chains of length 20. Storing orphans is disabled.'.format(num_of_threads), 0, args, number_of_connections=1) as (conn,): # Run test case. self.run_scenario1(conn, num_of_threads, 20, out, timeout=30) # Scenario 3. # This scenario will cause 'too-long-validation-time' reject reason to happen - during ptv processing. # If a given task has got a chain of 50 txns to process and 10th txn is rejected with 'too-long-validation-time' rejection reason, then # all remaining txns from the chain are detected as false-positive orphans. # Due to a runtime environment it is not possible to estimate the number of such rejects. args = ['-maxorphantxsize=10', '-txnvalidationasynchrunfreq=0', '-limitancestorcount=50', '-limitdescendantcount=50', '-checkmempool=0', '-persistmempool=0'] with self.run_node_with_connections("Scenario 3: 100 chains of length 50. Storing orphans is enabled.", 0, args, number_of_connections=1) as (conn,): # Run test case. self.run_scenario1(conn, 100, 50, out, timeout=60)
class ColoredCoinTest(BitcoinTestFramework): def set_test_params(self): self.pubkeys = [ "025700236c2890233592fcef262f4520d22af9160e3d9705855140eb2aa06c35d3", "03831a69b8009833ab5b0326012eaf489bfea35a7321b1ca15b11d88131423fafc" ] privkeystr = [ "67ae3f5bfb3464b9704d7bd3a134401cc80c3a172240ebfca9f1e40f51bb6d37", "dbb9d19637018267268dfc2cc7aec07e7217c1a2d6733e1184a0909273bf078b" ] self.privkeys = [] for key in privkeystr: ckey = CECKey() ckey.set_secretbytes(bytes.fromhex(key)) self.privkeys.append(ckey) self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes( bytes.fromhex( "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747" )) self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.schnorr_key = Schnorr() self.schnorr_key.set_secretbytes( bytes.fromhex( "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747" )) self.num_nodes = 1 self.setup_clean_chain = True def run_test(self): node = self.nodes[0] # convenience reference to the node self.address = node.getnewaddress() node.add_p2p_connection(P2PDataStore()) node.p2p.wait_for_getheaders(timeout=5) self.address = self.nodes[0].getnewaddress() self.log.info("Test starting...") #generate 10 blocks for coinbase outputs coinbase_txs = [] for i in range(1, 10): height = node.getblockcount() + 1 coinbase_tx = create_coinbase(height, self.coinbase_pubkey) coinbase_txs.append(coinbase_tx) tip = node.getbestblockhash() block_time = node.getblockheader(tip)["mediantime"] + 1 block = create_block(int(tip, 16), coinbase_tx, block_time) block.solve(self.signblockprivkey) tip = block.hash node.p2p.send_and_ping(msg_block(block)) assert_equal(node.getbestblockhash(), tip) change_script = CScript([self.coinbase_pubkey, OP_CHECKSIG]) burn_script = CScript([hex_str_to_bytes(self.pubkeys[1]), OP_CHECKSIG]) #TxSuccess1 - coinbaseTx1 - issue 100 REISSUABLE + 30 (UTXO-1,2) colorId_reissuable = colorIdReissuable( coinbase_txs[0].vout[0].scriptPubKey) script_reissuable = CP2PHK_script(colorId=colorId_reissuable, pubkey=self.pubkeys[0]) script_transfer_reissuable = CP2PHK_script(colorId=colorId_reissuable, pubkey=self.pubkeys[1]) txSuccess1 = CTransaction() txSuccess1.vin.append( CTxIn(COutPoint(coinbase_txs[0].malfixsha256, 0), b"")) txSuccess1.vout.append(CTxOut(100, script_reissuable)) txSuccess1.vout.append( CTxOut(30 * COIN, CScript([self.coinbase_pubkey, OP_CHECKSIG]))) sig_hash, err = SignatureHash(coinbase_txs[0].vout[0].scriptPubKey, txSuccess1, 0, SIGHASH_ALL) signature = self.coinbase_key.sign( sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL txSuccess1.vin[0].scriptSig = CScript([signature]) txSuccess1.rehash() test_transaction_acceptance(node, txSuccess1, accepted=True) #TxSuccess2 - (UTXO-2) - issue 100 NON-REISSUABLE (UTXO-3) colorId_nonreissuable = colorIdNonReissuable( COutPoint(txSuccess1.malfixsha256, 1).serialize()) script_nonreissuable = CP2PHK_script(colorId=colorId_nonreissuable, pubkey=self.pubkeys[0]) script_transfer_nonreissuable = CP2PHK_script( colorId=colorId_nonreissuable, pubkey=self.pubkeys[1]) txSuccess2 = CTransaction() txSuccess2.vin.append(CTxIn(COutPoint(txSuccess1.malfixsha256, 1), b"")) txSuccess2.vout.append(CTxOut(100, script_nonreissuable)) sig_hash, err = SignatureHash(txSuccess1.vout[1].scriptPubKey, txSuccess2, 0, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess2.vin[0].scriptSig = CScript([signature]) txSuccess2.rehash() test_transaction_acceptance(node, txSuccess2, accepted=True) #TxSuccess3 - coinbaseTx2 - issue 1 NFT (UTXO-4) colorId_nft = colorIdNFT( COutPoint(coinbase_txs[1].malfixsha256, 0).serialize()) script_nft = CP2PHK_script(colorId=colorId_nft, pubkey=self.pubkeys[0]) script_transfer_nft = CP2PHK_script(colorId=colorId_nft, pubkey=self.pubkeys[0]) txSuccess3 = CTransaction() txSuccess3.vin.append( CTxIn(COutPoint(coinbase_txs[1].malfixsha256, 0), b"")) txSuccess3.vout.append(CTxOut(1, script_nft)) sig_hash, err = SignatureHash(coinbase_txs[1].vout[0].scriptPubKey, txSuccess3, 0, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess3.vin[0].scriptSig = CScript([signature]) txSuccess3.rehash() test_transaction_acceptance(node, txSuccess3, accepted=True) #TxFailure4 - (UTXO-1) - split REISSUABLE - 25 + 75 (UTXO-5,6) # - (UTXO-3) - split NON-REISSUABLE - 40 + 60 (UTXO-7,8) # - coinbaseTx3 - issue 100 REISSUABLE (UTXO-9) TxFailure4 = CTransaction() TxFailure4.vin.append(CTxIn(COutPoint(txSuccess1.malfixsha256, 0), b"")) TxFailure4.vin.append(CTxIn(COutPoint(txSuccess2.malfixsha256, 0), b"")) TxFailure4.vin.append( CTxIn(COutPoint(coinbase_txs[2].malfixsha256, 0), b"")) TxFailure4.vout.append(CTxOut(25, script_reissuable)) TxFailure4.vout.append(CTxOut(75, script_reissuable)) TxFailure4.vout.append(CTxOut(40, script_nonreissuable)) TxFailure4.vout.append(CTxOut(60, script_nonreissuable)) TxFailure4.vout.append(CTxOut(100, script_reissuable)) sig_hash, err = SignatureHash(txSuccess1.vout[0].scriptPubKey, TxFailure4, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure4.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess2.vout[0].scriptPubKey, TxFailure4, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure4.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[2].vout[0].scriptPubKey, TxFailure4, 2, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' TxFailure4.vin[2].scriptSig = CScript([signature]) TxFailure4.rehash() test_transaction_acceptance(node, TxFailure4, accepted=False, reason=b"bad-txns-token-balance") #TxSuccess4 - (UTXO-1) - split REISSUABLE - 25 + 75 (UTXO-5,6) # - (UTXO-3) - split NON-REISSUABLE - 40 + 60 (UTXO-7,8) txSuccess4 = CTransaction() txSuccess4.vin.append(CTxIn(COutPoint(txSuccess1.malfixsha256, 0), b"")) txSuccess4.vin.append(CTxIn(COutPoint(txSuccess2.malfixsha256, 0), b"")) txSuccess4.vin.append( CTxIn(COutPoint(coinbase_txs[2].malfixsha256, 0), b"")) txSuccess4.vout.append(CTxOut(25, script_reissuable)) txSuccess4.vout.append(CTxOut(75, script_reissuable)) txSuccess4.vout.append(CTxOut(40, script_nonreissuable)) txSuccess4.vout.append(CTxOut(60, script_nonreissuable)) sig_hash, err = SignatureHash(txSuccess1.vout[0].scriptPubKey, txSuccess4, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess4.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess2.vout[0].scriptPubKey, txSuccess4, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess4.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[2].vout[0].scriptPubKey, txSuccess4, 2, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess4.vin[2].scriptSig = CScript([signature]) txSuccess4.rehash() test_transaction_acceptance(node, txSuccess4, accepted=True) #TxFailure5 - (UTXO-6) - split REISSUABLE(75) (UTXO-10,11) # - (UTXO-7) - split NON-REISSUABLE(40) (UTXO-12) # - (UTXO-4) - split NFT (UTXO-13) # - coinbaseTx4 TxFailure5 = CTransaction() TxFailure5.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 1), b"")) TxFailure5.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 2), b"")) TxFailure5.vin.append(CTxIn(COutPoint(txSuccess3.malfixsha256, 0), b"")) TxFailure5.vin.append( CTxIn(COutPoint(coinbase_txs[3].malfixsha256, 0), b"")) TxFailure5.vout.append(CTxOut(35, script_reissuable)) TxFailure5.vout.append(CTxOut(40, script_reissuable)) TxFailure5.vout.append(CTxOut(20, script_nonreissuable)) TxFailure5.vout.append(CTxOut(20, script_nonreissuable)) TxFailure5.vout.append(CTxOut(1, script_nft)) TxFailure5.vout.append(CTxOut(1, script_nft)) sig_hash, err = SignatureHash(txSuccess4.vout[1].scriptPubKey, TxFailure5, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure5.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess4.vout[2].scriptPubKey, TxFailure5, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure5.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess3.vout[0].scriptPubKey, TxFailure5, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure5.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[3].vout[0].scriptPubKey, TxFailure5, 3, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' TxFailure5.vin[3].scriptSig = CScript([signature]) TxFailure5.rehash() test_transaction_acceptance(node, TxFailure5, accepted=False, reason=b"bad-txns-token-balance") #txSuccess5 - (UTXO-6) - split REISSUABLE(75) (UTXO-10,11) # - (UTXO-7) - split NON-REISSUABLE(40) (UTXO-12) # - (UTXO-4) - transfer NFT (UTXO-13) # - coinbaseTx4 txSuccess5 = CTransaction() txSuccess5.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 1), b"")) txSuccess5.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 2), b"")) txSuccess5.vin.append(CTxIn(COutPoint(txSuccess3.malfixsha256, 0), b"")) txSuccess5.vin.append( CTxIn(COutPoint(coinbase_txs[3].malfixsha256, 0), b"")) txSuccess5.vout.append(CTxOut(35, script_reissuable)) txSuccess5.vout.append(CTxOut(40, script_reissuable)) txSuccess5.vout.append(CTxOut(20, script_nonreissuable)) txSuccess5.vout.append(CTxOut(20, script_nonreissuable)) txSuccess5.vout.append(CTxOut(1, script_nft)) sig_hash, err = SignatureHash(txSuccess4.vout[1].scriptPubKey, txSuccess5, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess5.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess4.vout[2].scriptPubKey, txSuccess5, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess5.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess3.vout[0].scriptPubKey, txSuccess5, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess5.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[3].vout[0].scriptPubKey, txSuccess5, 3, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess5.vin[3].scriptSig = CScript([signature]) txSuccess5.rehash() test_transaction_acceptance(node, txSuccess5, accepted=True) #TxFailure6 - (UTXO-11) - transfer REISSUABLE(40) (UTXO-14) # - (UTXO-8) - burn NON-REISSUABLE(60) (UTXO-15)* # - (UTXO-13) - transfer NFT (UTXO-16) # - coinbaseTx5 - issue 1000 REISSUABLE1, change (UTXO-17) colorId_reissuable1 = colorIdReissuable( coinbase_txs[6].vout[0].scriptPubKey) script_reissuable1 = CP2PHK_script(colorId=colorId_reissuable, pubkey=self.pubkeys[0]) TxFailure6 = CTransaction() TxFailure6.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 1), b"")) TxFailure6.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 3), b"")) TxFailure6.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 4), b"")) TxFailure6.vin.append( CTxIn(COutPoint(coinbase_txs[4].malfixsha256, 0), b"")) TxFailure6.vout.append(CTxOut(40, script_transfer_reissuable)) TxFailure6.vout.append(CTxOut(30, script_transfer_nonreissuable)) TxFailure6.vout.append(CTxOut(1, script_transfer_nft)) TxFailure6.vout.append(CTxOut(1000, script_reissuable1)) TxFailure6.vout.append(CTxOut(1 * COIN, change_script)) sig_hash, err = SignatureHash(txSuccess5.vout[1].scriptPubKey, TxFailure6, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure6.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess4.vout[3].scriptPubKey, TxFailure6, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure6.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess5.vout[4].scriptPubKey, TxFailure6, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure6.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[4].vout[0].scriptPubKey, TxFailure6, 3, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' TxFailure6.vin[3].scriptSig = CScript([signature]) TxFailure6.rehash() test_transaction_acceptance(node, TxFailure6, accepted=False, reason=b"bad-txns-token-balance") #TxSuccess6 - (UTXO-11) - transfer REISSUABLE(40) (UTXO-14) # - (UTXO-8) - burn NON-REISSUABLE(60) (UTXO-15)* # - (UTXO-13) - transfer NFT (UTXO-16) # - coinbaseTx5 - change txSuccess6 = CTransaction() txSuccess6.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 1), b"")) txSuccess6.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 3), b"")) txSuccess6.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 4), b"")) txSuccess6.vin.append( CTxIn(COutPoint(coinbase_txs[4].malfixsha256, 0), b"")) txSuccess6.vout.append(CTxOut(40, script_transfer_reissuable)) txSuccess6.vout.append(CTxOut(30, script_transfer_nonreissuable)) txSuccess6.vout.append(CTxOut(1, script_transfer_nft)) txSuccess6.vout.append(CTxOut(1 * COIN, change_script)) sig_hash, err = SignatureHash(txSuccess5.vout[1].scriptPubKey, txSuccess6, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess6.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess4.vout[3].scriptPubKey, txSuccess6, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess6.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess5.vout[4].scriptPubKey, txSuccess6, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess6.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[4].vout[0].scriptPubKey, txSuccess6, 3, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess6.vin[3].scriptSig = CScript([signature]) txSuccess6.rehash() test_transaction_acceptance(node, txSuccess6, accepted=True) #TxSuccess7 - coinbaseTx5 - issue 1000 REISSUABLE1, change (UTXO-17) txSuccess7 = CTransaction() txSuccess7.vin.append( CTxIn(COutPoint(coinbase_txs[5].malfixsha256, 0), b"")) txSuccess7.vout.append(CTxOut(1000, script_reissuable1)) sig_hash, err = SignatureHash(coinbase_txs[5].vout[0].scriptPubKey, txSuccess7, 0, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess7.vin[0].scriptSig = CScript([signature]) txSuccess7.rehash() test_transaction_acceptance(node, txSuccess7, accepted=True) #TxFailure7 - (UTXO-9,14) - aggregate REISSUABLE(25 + 40) x # - (UTXO-12) - burn NON-REISSUABLE(20) * TxFailure7 = CTransaction() TxFailure7.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 0), b"")) TxFailure7.vin.append(CTxIn(COutPoint(txSuccess6.malfixsha256, 0), b"")) TxFailure7.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 2), b"")) TxFailure7.vout.append(CTxOut(65, script_transfer_reissuable)) sig_hash, err = SignatureHash(txSuccess4.vout[0].scriptPubKey, TxFailure7, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure7.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess6.vout[0].scriptPubKey, TxFailure7, 1, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure7.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess5.vout[2].scriptPubKey, TxFailure7, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' TxFailure7.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) TxFailure7.rehash() test_transaction_acceptance(node, TxFailure7, accepted=False, reason=b'min relay fee not met') #txSuccess8 - (UTXO-9,14) - aggregate REISSUABLE(25 + 40) x # - (UTXO-12) - burn NON-REISSUABLE(20) * # - coinbase[6] txSuccess8 = CTransaction() txSuccess8.vin.append(CTxIn(COutPoint(txSuccess4.malfixsha256, 0), b"")) txSuccess8.vin.append(CTxIn(COutPoint(txSuccess6.malfixsha256, 0), b"")) txSuccess8.vin.append(CTxIn(COutPoint(txSuccess5.malfixsha256, 2), b"")) txSuccess8.vin.append( CTxIn(COutPoint(coinbase_txs[6].malfixsha256, 0), b"")) txSuccess8.vout.append(CTxOut(65, script_transfer_reissuable)) sig_hash, err = SignatureHash(txSuccess4.vout[0].scriptPubKey, txSuccess8, 0, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess8.vin[0].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(txSuccess6.vout[0].scriptPubKey, txSuccess8, 1, SIGHASH_ALL) signature = self.privkeys[1].sign(sig_hash) + b'\x01' txSuccess8.vin[1].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[1])]) sig_hash, err = SignatureHash(txSuccess5.vout[2].scriptPubKey, txSuccess8, 2, SIGHASH_ALL) signature = self.privkeys[0].sign(sig_hash) + b'\x01' txSuccess8.vin[2].scriptSig = CScript( [signature, hex_str_to_bytes(self.pubkeys[0])]) sig_hash, err = SignatureHash(coinbase_txs[6].vout[0].scriptPubKey, txSuccess8, 3, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' txSuccess8.vin[3].scriptSig = CScript([signature]) txSuccess8.rehash() test_transaction_acceptance(node, txSuccess8, accepted=True) #TxFailure8 - (UTXO-17) - convert REISSUABLE to NON-REISSUABLE TxFailure8 = CTransaction() TxFailure8.vin.append(CTxIn(COutPoint(txSuccess7.malfixsha256, 0), b"")) TxFailure8.vout.append(CTxOut(60, script_transfer_nonreissuable)) sig_hash, err = SignatureHash(txSuccess7.vout[0].scriptPubKey, TxFailure8, 0, SIGHASH_ALL) signature = self.coinbase_key.sign(sig_hash) + b'\x01' TxFailure8.vin[0].scriptSig = CScript([signature]) TxFailure8.rehash() test_transaction_acceptance(node, TxFailure8, accepted=False, reason=b'invalid-colorid')
def run_test(self): node, = self.nodes self.bootstrap_p2p() tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node, success=True) spendable_outputs = [block.vtx[0] for block in blocks] self.log.info("Mature the blocks and get out of IBD.") node.generate(100) tip = self.getbestblock(node) self.log.info("Setting up spends to test and mining the fundings.") fundings = [] # Generate a key pair privkeybytes = b"Schnorr!" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): spendfrom = spendable_outputs.pop() script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) value = spendfrom.vout[0].nValue # Fund transaction txfund = create_transaction(spendfrom, 0, b'', value, script) txfund.rehash() fundings.append(txfund) # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(value - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = SIGHASH_ALL | SIGHASH_FORKID hashbyte = bytes([sighashtype & 0xff]) sighash = SignatureHashForkId(script, txspend, 0, sighashtype, value) if sigtype == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sigtype == 'ecdsa': txsig = private_key.sign(sighash) + hashbyte txspend.vin[0].scriptSig = CScript([dummy, txsig]) txspend.rehash() return txspend # two of these transactions, which are valid both before and after upgrade. ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') ecdsa0tx_2 = create_fund_and_spend_tx(OP_0, 'ecdsa') # two of these, which are nonstandard before upgrade and invalid after. ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') ecdsa1tx_2 = create_fund_and_spend_tx(OP_1, 'ecdsa') # this one is always invalid. schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') # this one is only going to be valid after the upgrade. schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') tip = self.build_block(tip, fundings) node.p2p.send_blocks_and_test([tip], node) self.log.info("Start preupgrade tests") self.log.info("Sending rejected transactions via RPC") assert_raises_rpc_error(-26, rpc_error(**PREUPGRADE_ECDSA_NULLDUMMY_ERROR), node.sendrawtransaction, ToHex(ecdsa1tx)) assert_raises_rpc_error(-26, rpc_error(**SCHNORR_LEGACY_MULTISIG_ERROR), node.sendrawtransaction, ToHex(schnorr0tx)) assert_raises_rpc_error(-26, rpc_error(**PREUPGRADE_SCHNORR_MULTISIG_ERROR), node.sendrawtransaction, ToHex(schnorr1tx)) self.log.info( "Sending rejected transactions via net (banning depending on situation)" ) self.check_for_no_ban_on_rejected_tx( ecdsa1tx, **PREUPGRADE_ECDSA_NULLDUMMY_ERROR) self.check_for_ban_on_rejected_tx(schnorr0tx, **SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_no_ban_on_rejected_tx( schnorr1tx, **PREUPGRADE_SCHNORR_MULTISIG_ERROR) self.log.info( "Sending invalid transactions in blocks (and get banned!)") self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), **BADINPUTS_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr1tx]), **BADINPUTS_ERROR) self.log.info("Sending valid transaction via net, then mining it") node.p2p.send_txs_and_test([ecdsa0tx], node) assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) tip = self.build_block(tip, [ecdsa0tx]) node.p2p.send_blocks_and_test([tip], node) assert_equal(node.getrawmempool(), []) # Activation tests self.log.info("Approach to just before upgrade activation") # Move our clock to the uprade time so we will accept such future-timestamped blocks. node.setmocktime(GRAVITON_START_TIME) # Mine six blocks with timestamp starting at GRAVITON_START_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=GRAVITON_START_TIME + i) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node) assert_equal(node.getblockchaininfo()['mediantime'], GRAVITON_START_TIME - 1) self.log.info( "The next block will activate, but the activation block itself must follow old rules" ) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), **BADINPUTS_ERROR) self.log.info( "Send a lecacy ECDSA multisig into mempool, we will check after upgrade to make sure it didn't get cleaned out unnecessarily." ) node.p2p.send_txs_and_test([ecdsa0tx_2], node) assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) # save this tip for later preupgrade_block = tip self.log.info( "Mine the activation block itself, including a legacy nulldummy violation at the last possible moment" ) tip = self.build_block(tip, [ecdsa1tx]) node.p2p.send_blocks_and_test([tip], node) self.log.info("We have activated!") assert_equal(node.getblockchaininfo()['mediantime'], GRAVITON_START_TIME) assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) # save this tip for later upgrade_block = tip self.log.info( "Trying to mine a legacy nulldummy violation, but we are just barely too late" ) self.check_for_ban_on_rejected_block( self.build_block(tip, [ecdsa1tx_2]), **BADINPUTS_ERROR) self.log.info( "If we try to submit it by mempool or RPC, the error code has changed but we still aren't banned" ) assert_raises_rpc_error(-26, rpc_error(**POSTUPGRADE_ECDSA_NULLDUMMY_ERROR), node.sendrawtransaction, ToHex(ecdsa1tx_2)) self.check_for_no_ban_on_rejected_tx( ecdsa1tx_2, **POSTUPGRADE_ECDSA_NULLDUMMY_ERROR) self.log.info( "Submitting a new Schnorr-multisig via net, and mining it in a block" ) node.p2p.send_txs_and_test([schnorr1tx], node) assert_equal(set(node.getrawmempool()), {ecdsa0tx_2.hash, schnorr1tx.hash}) tip = self.build_block(tip, [schnorr1tx]) node.p2p.send_blocks_and_test([tip], node) # save this tip for later postupgrade_block = tip self.log.info( "That legacy ECDSA multisig is still in mempool, let's mine it") assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) tip = self.build_block(tip, [ecdsa0tx_2]) node.p2p.send_blocks_and_test([tip], node) assert_equal(node.getrawmempool(), []) self.log.info( "Trying Schnorr in legacy multisig remains invalid and banworthy as ever" ) self.check_for_ban_on_rejected_tx(schnorr0tx, **SCHNORR_LEGACY_MULTISIG_ERROR) self.check_for_ban_on_rejected_block( self.build_block(tip, [schnorr0tx]), **BADINPUTS_ERROR) # Deactivation tests self.log.info( "Invalidating the post-upgrade blocks returns the transactions to mempool" ) node.invalidateblock(postupgrade_block.hash) assert_equal(set(node.getrawmempool()), {ecdsa0tx_2.hash, schnorr1tx.hash}) self.log.info( "Invalidating the upgrade block evicts the transactions valid only after upgrade" ) node.invalidateblock(upgrade_block.hash) assert_equal(set(node.getrawmempool()), {ecdsa0tx_2.hash}) self.log.info("Return to our tip") node.reconsiderblock(upgrade_block.hash) node.reconsiderblock(postupgrade_block.hash) assert_equal(node.getbestblockhash(), tip.hash) assert_equal(node.getrawmempool(), []) self.log.info( "Create an empty-block reorg that forks from pre-upgrade") tip = preupgrade_block blocks = [] for _ in range(10): tip = self.build_block(tip) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node) self.log.info( "Transactions from orphaned blocks are sent into mempool ready to be mined again, including upgrade-dependent ones even though the fork deactivated and reactivated the upgrade." ) assert_equal(set(node.getrawmempool()), {ecdsa0tx_2.hash, schnorr1tx.hash}) node.generate(1) tip = self.getbestblock(node) assert set(tx.rehash() for tx in tip.vtx).issuperset( {ecdsa0tx_2.hash, schnorr1tx.hash})
def make_key(bytes=b"randombytes"): key = CECKey() key.set_secretbytes(bytes) return key
def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=0): api = self.nodes[node_n] # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(prev_hash, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(staking_prevouts): raise Exception("Not able to solve for any prev_outpoint") # Create coinstake TX amount, prev_time, prevScript = staking_prevouts[block.prevoutStake] outNValue = int(amount + 250 * COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append( CTxOut(outNValue, hex_str_to_bytes(prevScript))) if fInvalid == 1: # Create a new private key and get the corresponding public key block_sig_key = CECKey() block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = block_sig_key.get_pubkey() stake_tx_unsigned.vout[1].scriptPubKey = CScript( [pubkey, OP_CHECKSIG]) else: # Export the staking private key to sign the block with it privKey, compressed = wif_to_privkey(api.dumpprivkey(s_address)) block_sig_key = CECKey() block_sig_key.set_compressed(compressed) block_sig_key.set_secretbytes(bytes.fromhex(privKey)) # check the address addy = key_to_p2pkh(bytes_to_hex_str(block_sig_key.get_pubkey()), False, True) assert (addy == s_address) if fInvalid == 2: # add a new output with 100 coins from the pot new_key = CECKey() new_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = new_key.get_pubkey() stake_tx_unsigned.vout.append( CTxOut(100 * COIN, CScript([pubkey, OP_CHECKSIG]))) stake_tx_unsigned.vout[1].nValue = outNValue - 100 * COIN # Sign coinstake TX and add it to the block stake_tx_signed_raw_hex = api.signrawtransaction( bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] stake_tx_signed = CTransaction() stake_tx_signed.deserialize( BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) block.vtx.append(stake_tx_signed) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # sign block with block signing key and return it block.sign_block(block_sig_key) return block
def run_test(self): node = self.nodes[0] node.add_p2p_connection(P2PDataStore()) # Set the blocksize to 2MB as initial condition node.setexcessiveblock(self.excessive_block_size) self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() node.p2p.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(99): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() node.p2p.send_blocks_and_test(maturity_blocks, node) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(16): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node) # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) node.p2p.send_blocks_and_test([self.tip], node) # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-length') # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript([OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block(19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node) block(20, spend=out[18], script=lots_of_checksigs, block_size=ONE_MEGABYTE, extra_sigops=1) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-sigops') # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) node.p2p.send_blocks_and_test([self.tip], node) # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-sigops') # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-sigops') # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) node.p2p.send_blocks_and_test([self.tip], node) # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-sigops') # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-blk-sigops') # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block(29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-txn-sigops') # Rewind bad block tip(26) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"fatstacks") public_key = private_key.get_pubkey() # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_tx(out[22], 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) node.p2p.send_blocks_and_test([self.tip], node) # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) node.p2p.send_blocks_and_test([self.tip], node, success=False, reject_reason='bad-txn-sigops') # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) node.p2p.send_blocks_and_test([self.tip], node) # Submit a very large block via RPC large_block = block(33, spend=out[24], block_size=self.excessive_block_size) node.submitblock(ToHex(large_block))
def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] block.vtx.extend(new_transactions) old_sha256 = block.sha256 make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand block = self.next_block node = self.nodes[0] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"replayprotection") public_key = private_key.get_pubkey() # This is a little handier to use than the version in blocktools.py def create_fund_and_spend_tx(spend, forkvalue=0): # Fund transaction script = CScript([public_key, OP_CHECKSIG]) txfund = create_transaction(spend.tx, spend.n, b'', 50 * COIN, script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID sighash = SignatureHashForkId(script, txspend, 0, sighashtype, 50 * COIN) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) txspend.vin[0].scriptSig = CScript([sig]) txspend.rehash() return [txfund, txspend] def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert (tx_id in set(node.getrawmempool())) return tx_id # Before the fork, no replay protection required to get in the mempool. txns = create_fund_and_spend_tx(out[0]) send_transaction_to_mempool(txns[0]) send_transaction_to_mempool(txns[1]) # And txns get mined in a block properly. block(1) update_block(1, txns) yield accepted() # Replay protected transactions are rejected. replay_txns = create_fund_and_spend_tx(out[1], 0xffdead) send_transaction_to_mempool(replay_txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) # And block containing them are rejected as well. block(2) update_block(2, replay_txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) # Create a block that would activate the replay protection. bfork = block(5555) bfork.nTime = REPLAY_PROTECTION_START_TIME - 1 update_block(5555, []) yield accepted() for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check we are just before the activation time assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], REPLAY_PROTECTION_START_TIME - 1) # We are just before the fork, replay protected txns still are rejected assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) block(3) update_block(3, replay_txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5104) # Send some non replay protected txns in the mempool to check # they get cleaned at activation. txns = create_fund_and_spend_tx(out[2]) send_transaction_to_mempool(txns[0]) tx_id = send_transaction_to_mempool(txns[1]) # Activate the replay protection block(5556) yield accepted() # Check we just activated the replay protection assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], REPLAY_PROTECTION_START_TIME) # Non replay protected transactions are not valid anymore, # so they should be removed from the mempool. assert (tx_id not in set(node.getrawmempool())) # Good old transactions are now invalid. send_transaction_to_mempool(txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(txns[1])) # They also cannot be mined block(4) update_block(4, txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # The replay protected transaction is now valid replay_tx0_id = send_transaction_to_mempool(replay_txns[0]) replay_tx1_id = send_transaction_to_mempool(replay_txns[1]) # Make sure the transaction are ready to be mined. tmpl = node.getblocktemplate() found_id0 = False found_id1 = False for txn in tmpl['transactions']: txid = txn['txid'] if txid == replay_tx0_id: found_id0 = True elif txid == replay_tx1_id: found_id1 = True assert (found_id0 and found_id1) # And the mempool is still in good shape. assert (replay_tx0_id in set(node.getrawmempool())) assert (replay_tx1_id in set(node.getrawmempool())) # They also can also be mined b5 = block(5) update_block(5, replay_txns) yield accepted() # Ok, now we check if a reorg work properly across the activation. postforkblockid = node.getbestblockhash() node.invalidateblock(postforkblockid) assert (replay_tx0_id in set(node.getrawmempool())) assert (replay_tx1_id in set(node.getrawmempool())) # Deactivating replay protection. forkblockid = node.getbestblockhash() node.invalidateblock(forkblockid) # The funding tx is not evicted from the mempool, since it's valid in # both sides of the fork assert (replay_tx0_id in set(node.getrawmempool())) assert (replay_tx1_id not in set(node.getrawmempool())) # Check that we also do it properly on deeper reorg. node.reconsiderblock(forkblockid) node.reconsiderblock(postforkblockid) node.invalidateblock(forkblockid) assert (replay_tx0_id in set(node.getrawmempool())) assert (replay_tx1_id not in set(node.getrawmempool()))
class P2SH(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 3 self.setup_clean_chain = True self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.genesisactivationheight = 150 self.extra_args = [[ '-norelaypriority', '-acceptnonstdtxn=0', '-banscore=1000000', f'-genesisactivationheight={self.genesisactivationheight}' ]] * 3 def run_test(self): self.test.run() def get_tests(self): # shorthand for functions block = self.chain.next_block node0 = self.nodes[0] node1 = self.nodes[1] node2 = self.nodes[2] self.chain.set_genesis_hash(int(node1.getbestblockhash(), 16)) # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(0, 100): block(i, coinbase_pubkey=self.coinbase_pubkey) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test # create two addresses on the node0 address1 = node0.getnewaddress() scriptPubKey1 = node0.validateaddress(address1)["scriptPubKey"] address2 = node0.getnewaddress() scriptPubKey2 = node0.validateaddress(address2)["scriptPubKey"] # import P2SH(P2PKH) on node1 and node2 # have to do in this way because it seems that we can't create P2PKH address and later add P2SH(P2PKH) to the same private key node1.importaddress(scriptPubKey1, "x", True, True) # importing script, not key node1.importprivkey(node0.dumpprivkey(address1)) node2.importaddress(scriptPubKey2, "x", True, True) # importing script, not key node2.importprivkey(node0.dumpprivkey(address2)) out = [self.chain.get_spendable_output() for _ in range(50)] # Create a p2sh transactions def new_P2SH_tx(scriptPubKey): output = out.pop(0) redeem_script = CScript(hex_str_to_bytes(scriptPubKey)) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) return create_and_sign_transaction(spend_tx=output.tx, n=output.n, value=output.tx.vout[0].nValue - 100, private_key=self.coinbase_key, script=p2sh_script) # Add the transactions to the block assert node0.getblockcount( ) < self.genesisactivationheight, "We must be before genesis" block(100) new_tx1 = new_P2SH_tx(scriptPubKey1) self.chain.update_block( 100, [new_tx1]) # sending funds to P2SH address BEFORE genesis yield self.accepted() current_height = node1.getblockcount() for i in range(self.genesisactivationheight - current_height): block(101 + i, coinbase_pubkey=self.coinbase_pubkey) test.blocks_and_transactions.append([self.chain.tip, True]) self.chain.save_spendable_output() yield test assert node0.getblockcount( ) >= self.genesisactivationheight, "We must be after genesis" block(150) new_tx2 = new_P2SH_tx(scriptPubKey2) self.chain.update_block( 150, [new_tx2]) # sending funds to P2SH address AFTER genesis yield self.rejected(RejectResult(16, b'bad-txns-vout-p2sh')) self.chain.set_tip(149) balance1 = node1.getbalance("*", 1, False) assert balance1 * COIN == new_tx1.vout[ 0].nValue, "Wallet has registered pre genesis transaction." balance2 = node2.getbalance("*", 1, False) assert balance2 * COIN == 0, "No funds in wallet as transaction is not accepted." # Pre genesis P2SH transaction can be spent through wallet node1.sendtoaddress(node0.getnewaddress(), balance1 - 1) balance1_new = node1.getbalance("*", 1, False) assert balance1 > balance1_new, "Pre genesis P2SH is spent."
class FIVEG_FakeStakeTest(BitcoinTestFramework): def set_test_params(self): ''' Setup test environment :param: :return: ''' self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [['-staking=1', '-debug=net']] * self.num_nodes def setup_network(self): ''' Can't rely on syncing all the nodes when staking=1 :param: :return: ''' self.setup_nodes() for i in range(self.num_nodes - 1): for j in range(i + 1, self.num_nodes): connect_nodes_bi(self.nodes, i, j) def init_test(self): ''' Initializes test parameters :param: :return: ''' title = "*** Starting %s ***" % self.__class__.__name__ underline = "-" * len(title) self.log.info("\n\n%s\n%s\n%s\n", title, underline, self.description) # Global Test parameters (override in run_test) self.DEFAULT_FEE = 0.1 # Spam blocks to send in current test self.NUM_BLOCKS = 30 # Setup the p2p connections and start up the network thread. self.test_nodes = [] for i in range(self.num_nodes): self.test_nodes.append(TestNode()) self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i)) network_thread_start() # Start up network handling in another thread self.node = self.nodes[0] # Let the test nodes get in sync for i in range(self.num_nodes): self.test_nodes[i].wait_for_verack() def run_test(self): ''' Performs the attack of this test - run init_test first. :param: :return: ''' self.description = "" self.init_test() return def create_spam_block(self, hashPrevBlock, stakingPrevOuts, height, fStakeDoubleSpent=False, fZPoS=False, spendingPrevOuts={}): ''' creates a block to spam the network with :param hashPrevBlock: (hex string) hash of previous block stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake height: (int) block height fStakeDoubleSpent: (bool) spend the coinstake input inside the block fZPoS: (bool) stake the block with zerocoin spendingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake :return block: (CBlock) generated block ''' # If not given inputs to create spam txes, use a copy of the staking inputs if len(spendingPrevOuts) == 0: spendingPrevOuts = dict(stakingPrevOuts) # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX # Even if PoS blocks have empty coinbase vout, the height is required for the vin script coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(hashPrevBlock, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(stakingPrevOuts): raise Exception("Not able to solve for any prev_outpoint") # Sign coinstake TX and add it to the block signed_stake_tx = self.sign_stake_tx( block, stakingPrevOuts[block.prevoutStake][0], fZPoS) block.vtx.append(signed_stake_tx) # Remove coinstake input prevout unless we want to try double spending in the same block. # Skip for zPoS as the spendingPrevouts are just regular UTXOs if not fZPoS and not fStakeDoubleSpent: del spendingPrevOuts[block.prevoutStake] # remove a random prevout from the list # (to randomize block creation if the same height is picked two times) if len(spendingPrevOuts) > 0: del spendingPrevOuts[choice(list(spendingPrevOuts))] # Create spam for the block. Sign the spendingPrevouts for outPoint in spendingPrevOuts: value_out = int(spendingPrevOuts[outPoint][0] - self.DEFAULT_FEE * COIN) tx = create_transaction(outPoint, b"", value_out, nTime, scriptPubKey=CScript([ self.block_sig_key.get_pubkey(), OP_CHECKSIG ])) # sign txes signed_tx_hex = self.node.signrawtransaction( bytes_to_hex_str(tx.serialize()))['hex'] signed_tx = CTransaction() signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex))) block.vtx.append(signed_tx) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # Sign block with coinstake key and return it block.sign_block(self.block_sig_key) return block def spend_utxo(self, utxo, address_list): ''' spend amount from previously unspent output to a provided address :param utxo: (JSON) returned from listunspent used as input addresslist: (string) destination address :return: txhash: (string) tx hash if successful, empty string otherwise ''' try: inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}] out_amount = (float(utxo["amount"]) - self.DEFAULT_FEE) / len(address_list) outputs = {} for address in address_list: outputs[address] = out_amount spendingTx = self.node.createrawtransaction(inputs, outputs) spendingTx_signed = self.node.signrawtransaction(spendingTx) if spendingTx_signed["complete"]: txhash = self.node.sendrawtransaction(spendingTx_signed["hex"]) return txhash else: self.log.warning("Error: %s" % str(spendingTx_signed["errors"])) return "" except JSONRPCException as e: self.log.error("JSONRPCException: %s" % str(e)) return "" def spend_utxos(self, utxo_list, address_list=[]): ''' spend utxos to provided list of addresses or 10 new generate ones. :param utxo_list: (JSON list) returned from listunspent used as input address_list: (string list) [optional] recipient FIVEG addresses. if not set, 10 new addresses will be generated from the wallet for each tx. :return: txHashes (string list) tx hashes ''' txHashes = [] # If not given, get 10 new addresses from self.node wallet if address_list == []: for i in range(10): address_list.append(self.node.getnewaddress()) for utxo in utxo_list: try: # spend current utxo to provided addresses txHash = self.spend_utxo(utxo, address_list) if txHash != "": txHashes.append(txHash) except JSONRPCException as e: self.log.error("JSONRPCException: %s" % str(e)) continue return txHashes def stake_amplification_step(self, utxo_list, address_list=[]): ''' spends a list of utxos providing the list of new outputs :param utxo_list: (JSON list) returned from listunspent used as input address_list: (string list) [optional] recipient FIVEG addresses. :return: new_utxos: (JSON list) list of new (valid) inputs after the spends ''' self.log.info("--> Stake Amplification step started with %d UTXOs", len(utxo_list)) txHashes = self.spend_utxos(utxo_list, address_list) num_of_txes = len(txHashes) new_utxos = [] if num_of_txes > 0: self.log.info( "Created %d transactions...Mining 2 blocks to include them..." % num_of_txes) self.node.generate(2) time.sleep(2) new_utxos = self.node.listunspent() self.log.info( "Amplification step produced %d new \"Fake Stake\" inputs:" % len(new_utxos)) return new_utxos def stake_amplification(self, utxo_list, iterations, address_list=[]): ''' performs the "stake amplification" which gives higher chances at finding fake stakes :param utxo_list: (JSON list) returned from listunspent used as input iterations: (int) amount of stake amplification steps to perform address_list: (string list) [optional] recipient FIVEG addresses. :return: all_inputs: (JSON list) list of all spent inputs ''' self.log.info("** Stake Amplification started with %d UTXOs", len(utxo_list)) valid_inputs = utxo_list all_inputs = [] for i in range(iterations): all_inputs = all_inputs + valid_inputs old_inputs = valid_inputs valid_inputs = self.stake_amplification_step( old_inputs, address_list) self.log.info("** Stake Amplification ended with %d \"fake\" UTXOs", len(all_inputs)) return all_inputs def sign_stake_tx(self, block, stake_in_value, fZPoS=False): ''' signs a coinstake transaction :param block: (CBlock) block with stake to sign stake_in_value: (int) staked amount fZPoS: (bool) zerocoin stake :return: stake_tx_signed: (CTransaction) signed tx ''' self.block_sig_key = CECKey() if fZPoS: self.log.info("Signing zPoS stake...") # Create raw zerocoin stake TX (signed) raw_stake = self.node.createrawzerocoinstake(block.prevoutStake) stake_tx_signed_raw_hex = raw_stake["hex"] # Get stake TX private key to sign the block with stake_pkey = raw_stake["private-key"] self.block_sig_key.set_compressed(True) self.block_sig_key.set_secretbytes(bytes.fromhex(stake_pkey)) else: # Create a new private key and get the corresponding public key self.block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = self.block_sig_key.get_pubkey() # Create the raw stake TX (unsigned) scriptPubKey = CScript([pubkey, OP_CHECKSIG]) outNValue = int(stake_in_value + 2 * COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey)) # Sign the stake TX stake_tx_signed_raw_hex = self.node.signrawtransaction( bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] # Deserialize the signed raw tx into a CTransaction object and return it stake_tx_signed = CTransaction() stake_tx_signed.deserialize( BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) return stake_tx_signed def get_prevouts(self, utxo_list, blockHeight, zpos=False): ''' get prevouts (map) for each utxo in a list :param utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input <if zpos=True> (JSON list) mints returned from listmintedzerocoins used as input blockHeight: (int) height of the previous block zpos: (bool) type of utxo_list :return: stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints to amount, block_time, nStakeModifier, hashStake ''' zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000] stakingPrevOuts = {} for utxo in utxo_list: if zpos: # get mint checkpoint checkpointHeight = blockHeight - 200 checkpointBlock = self.node.getblock( self.node.getblockhash(checkpointHeight), True) checkpoint = int(checkpointBlock['acc_checkpoint'], 16) # parse checksum and get checksumblock pos = zerocoinDenomList.index(utxo['denomination']) checksum = (checkpoint >> (32 * (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF checksumBlock = self.node.getchecksumblock( hex(checksum), utxo['denomination'], True) # get block hash and block time txBlockhash = checksumBlock['hash'] txBlocktime = checksumBlock['time'] else: # get raw transaction for current input utxo_tx = self.node.getrawtransaction(utxo['txid'], 1) # get block hash and block time txBlocktime = utxo_tx['blocktime'] txBlockhash = utxo_tx['blockhash'] # get Stake Modifier stakeModifier = int( self.node.getblock(txBlockhash)['modifier'], 16) # assemble prevout object utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime, stakeModifier, zpos) return stakingPrevOuts def log_data_dir_size(self): ''' Prints the size of the '/regtest/blocks' directory. :param: :return: ''' init_size = dir_size(self.node.datadir + "/regtest/blocks") self.log.info("Size of data dir: %s kilobytes" % str(init_size)) def test_spam(self, name, staking_utxo_list, fRandomHeight=False, randomRange=0, randomRange2=0, fDoubleSpend=False, fMustPass=False, fZPoS=False, spending_utxo_list=[]): ''' General method to create, send and test the spam blocks :param name: (string) chain branch (usually either "Main" or "Forked") staking_utxo_list: (string list) utxos to use for staking fRandomHeight: (bool) send blocks at random height randomRange: (int) if fRandomHeight=True, height is >= current-randomRange randomRange2: (int) if fRandomHeight=True, height is < current-randomRange2 fDoubleSpend: (bool) if true, stake input is double spent in block.vtx fMustPass: (bool) if true, the blocks must be stored on disk fZPoS: (bool) stake the block with zerocoin spending_utxo_list: (string list) utxos to use for spending :return: err_msgs: (string list) reports error messages from the test or an empty list if test is successful ''' # Create empty error messages list err_msgs = [] # Log initial datadir size self.log_data_dir_size() # Get latest block number and hash block_count = self.node.getblockcount() pastBlockHash = self.node.getblockhash(block_count) randomCount = block_count self.log.info("Current height: %d" % block_count) for i in range(0, self.NUM_BLOCKS): if i != 0: self.log.info("Sent %d blocks out of %d" % (i, self.NUM_BLOCKS)) # if fRandomHeight=True get a random block number (in range) and corresponding hash if fRandomHeight: randomCount = randint(block_count - randomRange, block_count - randomRange2) pastBlockHash = self.node.getblockhash(randomCount) # Get spending prevouts and staking prevouts for the height of current block current_block_n = randomCount + 1 stakingPrevOuts = self.get_prevouts(staking_utxo_list, randomCount, zpos=fZPoS) spendingPrevOuts = self.get_prevouts(spending_utxo_list, randomCount) # Create the spam block block = self.create_spam_block(pastBlockHash, stakingPrevOuts, current_block_n, fStakeDoubleSpent=fDoubleSpend, fZPoS=fZPoS, spendingPrevOuts=spendingPrevOuts) # Log time and size of the block block_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(block.nTime)) block_size = len(block.serialize()) / 1000 self.log.info( "Sending block %d [%s...] - nTime: %s - Size (kb): %.2f", current_block_n, block.hash[:7], block_time, block_size) # Try submitblock var = self.node.submitblock(bytes_to_hex_str(block.serialize())) time.sleep(1) if (not fMustPass and var not in [None, "bad-txns-invalid-zvg"] ) or (fMustPass and var != "inconclusive"): self.log.error("submitblock [fMustPass=%s] result: %s" % (str(fMustPass), str(var))) err_msgs.append("submitblock %d: %s" % (current_block_n, str(var))) # Try sending the message block msg = msg_block(block) try: self.test_nodes[0].handle_connect() self.test_nodes[0].send_message(msg) time.sleep(2) block_ret = self.node.getblock(block.hash) if not fMustPass and block_ret is not None: self.log.error("Error, block stored in %s chain" % name) err_msgs.append("getblock %d: result not None" % current_block_n) if fMustPass: if block_ret is None: self.log.error("Error, block NOT stored in %s chain" % name) err_msgs.append("getblock %d: result is None" % current_block_n) else: self.log.info("Good. Block IS stored on disk.") except JSONRPCException as e: exc_msg = str(e) if exc_msg == "Can't read block from disk (-32603)": if fMustPass: self.log.warning("Bad! Block was NOT stored to disk.") err_msgs.append(exc_msg) else: self.log.info("Good. Block was not stored on disk.") else: self.log.warning(exc_msg) err_msgs.append(exc_msg) except Exception as e: exc_msg = str(e) self.log.error(exc_msg) err_msgs.append(exc_msg) self.log.info("Sent all %s blocks." % str(self.NUM_BLOCKS)) # Log final datadir size self.log_data_dir_size() # Return errors list return err_msgs
def run_test(self): # Connect to node0 p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) network_thread_start() self.nodes[0].p2p.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock( self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append( CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 8400 deep (Pigeon needs 4x as much blocks to allow -assumevalid to work) for i in range(8400): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # We're adding new connections so terminate the network thread self.nodes[0].disconnect_p2ps() network_thread_join() # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)]) self.start_node(2, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)]) p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) p2p1 = self.nodes[1].add_p2p_connection(BaseNode()) p2p2 = self.nodes[2].add_p2p_connection(BaseNode()) network_thread_start() p2p0.wait_for_verack() p2p1.wait_for_verack() p2p2.wait_for_verack() # Make sure nodes actually accept the many headers self.mocktime = self.block_time set_node_times(self.nodes, self.mocktime) # send header lists to all three nodes. # node0 does not need to receive all headers # node1 must receive all headers as otherwise assumevalid is ignored in ConnectBlock # node2 should NOT receive all headers to force skipping of the assumevalid check in ConnectBlock p2p0.send_header_for_blocks(self.blocks[0:2000]) p2p1.send_header_for_blocks(self.blocks[0:2000]) p2p1.send_header_for_blocks(self.blocks[2000:4000]) p2p1.send_header_for_blocks(self.blocks[4000:6000]) p2p1.send_header_for_blocks(self.blocks[6000:8000]) p2p1.send_header_for_blocks(self.blocks[8000:]) p2p2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p0) self.assert_blockchain_height(self.nodes[0], 101) # Send 200 blocks to node1. All blocks, including block 102, will be accepted. for i in range(200): p2p1.send_message(msg_block(self.blocks[i])) # Syncing so many blocks can take a while on slow systems. Give it plenty of time to sync. p2p1.sync_with_ping(300) assert_equal( self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 200) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p2) self.assert_blockchain_height(self.nodes[2], 101)
def create_unsigned_pos_block(self, staking_prevouts, nTime=None, outNValue=10002, signStakeTx=True, bestBlockHash=None, coinStakePrevout=None): if not nTime: current_time = int(time.time()) + 15 nTime = current_time & 0xfffffff0 if not bestBlockHash: bestBlockHash = self.node.getbestblockhash() block_height = self.node.getblockcount() else: block_height = self.node.getblock(bestBlockHash)['height'] parent_block_stake_modifier = int( self.node.getblock(bestBlockHash)['modifier'], 16) parent_block_raw_hex = self.node.getblock(bestBlockHash, False) f = io.BytesIO(hex_str_to_bytes(parent_block_raw_hex)) parent_block = CBlock() parent_block.deserialize(f) coinbase = create_coinbase(block_height + 1) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.rehash() block = create_block(int(bestBlockHash, 16), coinbase, nTime) block.hashPrevBlock = int(bestBlockHash, 16) if not block.solve_stake(parent_block_stake_modifier, staking_prevouts): return None # create a new private key used for block signing. block_sig_key = CECKey() block_sig_key.set_secretbytes(hash256(struct.pack('<I', 0xffff))) pubkey = block_sig_key.get_pubkey() scriptPubKey = CScript([pubkey, OP_CHECKSIG]) stake_tx_unsigned = CTransaction() if not coinStakePrevout: coinStakePrevout = block.prevoutStake stake_tx_unsigned.vin.append(CTxIn(coinStakePrevout)) stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append( CTxOut(int(outNValue * COIN), scriptPubKey)) stake_tx_unsigned.vout.append( CTxOut(int(outNValue * COIN), scriptPubKey)) if signStakeTx: stake_tx_signed_raw_hex = self.node.signrawtransactionwithwallet( bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] f = io.BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex)) stake_tx_signed = CTransaction() stake_tx_signed.deserialize(f) block.vtx.append(stake_tx_signed) else: block.vtx.append(stake_tx_unsigned) block.hashMerkleRoot = block.calc_merkle_root() return (block, block_sig_key)
def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): [tx.rehash() for tx in new_transactions] block = self.blocks[block_number] block.vtx.extend(new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block node = self.nodes[0] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"replayprotection") public_key = private_key.get_pubkey() # This is a little handier to use than the version in blocktools.py def create_fund_and_spend_tx(spend, forkvalue=0): # Fund transaction script = CScript([public_key, OP_CHECKSIG]) txfund = create_transaction( spend.tx, spend.n, b'', 50 * COIN, script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID sighash = SignatureHashForkId( script, txspend, 0, sighashtype, 50 * COIN) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) txspend.vin[0].scriptSig = CScript([sig]) txspend.rehash() return [txfund, txspend] def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert(tx_id in set(node.getrawmempool())) return tx_id # Before the fork, no replay protection required to get in the mempool. txns = create_fund_and_spend_tx(out[0]) send_transaction_to_mempool(txns[0]) send_transaction_to_mempool(txns[1]) # And txns get mined in a block properly. block(1) update_block(1, txns) yield accepted() # Replay protected transactions are rejected. replay_txns = create_fund_and_spend_tx(out[1], 0xffdead) send_transaction_to_mempool(replay_txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) # And block containing them are rejected as well. block(2) update_block(2, replay_txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) # Create a block that would activate the replay protection. bfork = block(5555) bfork.nTime = REPLAY_PROTECTION_START_TIME - 1 update_block(5555, []) yield accepted() for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check we are just before the activation time assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], REPLAY_PROTECTION_START_TIME - 1) # We are just before the fork, replay protected txns still are rejected assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(replay_txns[1])) block(3) update_block(3, replay_txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5104) # Send some non replay protected txns in the mempool to check # they get cleaned at activation. txns = create_fund_and_spend_tx(out[2]) send_transaction_to_mempool(txns[0]) tx_id = send_transaction_to_mempool(txns[1]) # Activate the replay protection block(5556) yield accepted() # Non replay protected transactions are not valid anymore, # so they should be removed from the mempool. assert(tx_id not in set(node.getrawmempool())) # Good old transactions are now invalid. send_transaction_to_mempool(txns[0]) assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, node.sendrawtransaction, ToHex(txns[1])) # They also cannot be mined block(4) update_block(4, txns) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # The replay protected transaction is now valid send_transaction_to_mempool(replay_txns[0]) replay_tx_id = send_transaction_to_mempool(replay_txns[1]) # They also can also be mined b5 = block(5) update_block(5, replay_txns) yield accepted() # Ok, now we check if a reorg work properly accross the activation. postforkblockid = node.getbestblockhash() node.invalidateblock(postforkblockid) assert(replay_tx_id in set(node.getrawmempool())) # Deactivating replay protection. forkblockid = node.getbestblockhash() node.invalidateblock(forkblockid) assert(replay_tx_id not in set(node.getrawmempool())) # Check that we also do it properly on deeper reorg. node.reconsiderblock(forkblockid) node.reconsiderblock(postforkblockid) node.invalidateblock(forkblockid) assert(replay_tx_id not in set(node.getrawmempool()))
def run_test(self): # Connect to node0 node0 = BaseNode() connections = [] connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0)) node0.add_connection(connections[0]) NetworkThread().start() # Start up network handling in another thread node0.wait_for_verack() # Build the blockchain self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 self.blocks = [] # Get a pubkey for the coinbase TXO coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() # Create the first block with a coinbase output to our key height = 1 block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time) self.blocks.append(block) self.block_time += 1 block.solve() # Save the coinbase for later self.block1 = block self.tip = block.sha256 height += 1 # Bury the block 100 deep so the coinbase output is spendable for i in range(100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Create a transaction spending the coinbase output with an invalid (null) signature tx = CTransaction() tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b"")) tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() block102 = create_block(self.tip, create_coinbase(height), self.block_time) self.block_time += 1 block102.vtx.extend([tx]) block102.hashMerkleRoot = block102.calc_merkle_root() block102.rehash() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 2100 deep for i in range(2100): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 self.block_time += 1 height += 1 # Start node1 and node2 with assumevalid so they accept a block with a bad signature. self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) node1 = BaseNode() # connects to node1 connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1)) node1.add_connection(connections[1]) node1.wait_for_verack() self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)]) node2 = BaseNode() # connects to node2 connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2)) node2.add_connection(connections[2]) node2.wait_for_verack() # send header lists to all three nodes node0.send_header_for_blocks(self.blocks[0:2000]) node0.send_header_for_blocks(self.blocks[2000:]) node1.send_header_for_blocks(self.blocks[0:2000]) node1.send_header_for_blocks(self.blocks[2000:]) node2.send_header_for_blocks(self.blocks[0:200]) # Send blocks to node0. Block 102 will be rejected. self.send_blocks_until_disconnected(node0) self.assert_blockchain_height(self.nodes[0], 101) # Send all blocks to node1. All blocks will be accepted. for i in range(2202): node1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. node1.sync_with_ping(120) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(node2) self.assert_blockchain_height(self.nodes[2], 101)
def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # shorthand block = self.next_block node = self.nodes[0] node_ban = self.nodes[1] # save the current tip so its coinbase can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get a coinbase that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(199): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Generate a key pair to test P2SH sigops count privkeybytes = b"Schnorr!" * 4 private_key = CECKey() private_key.set_secretbytes(privkeybytes) # get uncompressed public key serialization public_key = private_key.get_pubkey() def create_fund_and_spend_tx(spend, multi=False, sig='schnorr'): if multi: script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) else: script = CScript([public_key, OP_CHECKSIG]) # Fund transaction txfund = create_transaction(spend.tx, spend.n, b'', 50 * COIN, script) txfund.rehash() # Spend transaction txspend = CTransaction() txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction sighashtype = SIGHASH_ALL | SIGHASH_FORKID hashbyte = bytes([sighashtype & 0xff]) sighash = SignatureHashForkId(script, txspend, 0, sighashtype, 50 * COIN) if sig == 'schnorr': txsig = schnorr.sign(privkeybytes, sighash) + hashbyte elif sig == 'ecdsa': txsig = private_key.sign(sighash) + hashbyte elif isinstance(sig, bytes): txsig = sig + hashbyte if multi: txspend.vin[0].scriptSig = CScript([b'', txsig]) else: txspend.vin[0].scriptSig = CScript([txsig]) txspend.rehash() return txfund, txspend def send_transaction_to_mempool(tx): tx_id = node.sendrawtransaction(ToHex(tx)) assert (tx_id in set(node.getrawmempool())) return tx_id # Check we are not banned when sending a txn that node_ban rejects. def check_for_no_ban_on_rejected_tx(tx, reject_code, reject_reason): # Grab the first connection p2p = node_ban.p2p assert (p2p.state == 'connected') # The P2PConnection stores a public counter for each message type # and the last receive message of each type. We use this counter to # identify that we received a new reject message. with mininode_lock: rejects_count = p2p.message_count['reject'] # Send the transaction directly. We use a ping for synchronization: # if we have been banned, the pong message won't be received, a # timeout occurs and the test fails. p2p.send_message(msg_tx(tx)) p2p.sync_with_ping() # Check we haven't been disconnected assert (p2p.state == 'connected') # Check the reject message matches what we expected with mininode_lock: assert (p2p.message_count['reject'] == rejects_count + 1) reject_msg = p2p.last_message['reject'] assert (reject_msg.code == reject_code and reject_msg.reason == reject_reason and reject_msg.data == tx.sha256) # Check we are disconnected when sending a txn that node_ban rejects. # (Can't actually get banned, since bitcoind won't ban local peers.) def check_for_ban_on_rejected_tx(tx): # Take a connection p2p = node_ban.p2ps.pop() assert (p2p.state == 'connected') # make sure we can ping p2p.sync_with_ping() # send the naughty transaction p2p.send_message(msg_tx(tx)) # if not "banned", this will timeout and raise exception. p2p.wait_for_disconnect() # Setup fundings fundings = [] fund, schnorrchecksigtx = create_fund_and_spend_tx(out[0]) fundings.append(fund) fund, schnorrmultisigtx = create_fund_and_spend_tx(out[1], multi=True) fundings.append(fund) fund, ecdsachecksigtx = create_fund_and_spend_tx(out[2], sig='ecdsa') fundings.append(fund) if fakeDER64: fund, DER64checksigtx = create_fund_and_spend_tx(out[5], sig=fakeDER64) fundings.append(fund) fund, DER64multisigtx = create_fund_and_spend_tx(out[6], multi=True, sig=fakeDER64) fundings.append(fund) for fund in fundings: send_transaction_to_mempool(fund) block(1, transactions=fundings) yield accepted() # we're now set up for the various spends; make sure the other node # is set up, too. sync_blocks(self.nodes) # We are before the upgrade, no Schnorrs get in the mempool. assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, node.sendrawtransaction, ToHex(schnorrchecksigtx)) assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # And blocks containing them are rejected as well. block(2, transactions=[schnorrchecksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) block(3, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(1) # So far we were creating blocks well in advance of activation. # Now, start creating blocks that will move mediantime up to near # activation. bfork = block(5555, nTime=GREAT_WALL_START_TIME - 1) yield accepted() sync_blocks(self.nodes) # Create 5 more blocks with timestamps from GREAT_WALL_START_TIME+0 to +4 for i in range(5): block(5200 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check we are just before the activation time. assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) # We are just before the upgrade, still no Schnorrs get in the mempool, assert_raises_rpc_error(-26, RPC_EARLY_SCHNORR_ERROR, node.sendrawtransaction, ToHex(schnorrchecksigtx)) assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # ... nor in blocks. block(10, transactions=[schnorrchecksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5204) block(11, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5204) # Ensure that sending future-valid schnorr txns is *non-bannable*. check_for_no_ban_on_rejected_tx(schnorrchecksigtx, 16, EARLY_SCHNORR_ERROR) # Ensure that sending schnorrs in multisig *is* bannable. check_for_ban_on_rejected_tx(schnorrmultisigtx) if fakeDER64: # Throw a couple of "valid" 65-byte ECDSA signatures into the # mempool just prior to the activation. faked_checksig_tx_id = send_transaction_to_mempool(DER64checksigtx) faked_multisig_tx_id = send_transaction_to_mempool(DER64multisigtx) # Put a proper ECDSA transaction into the mempool but it won't # be mined... ecdsa_tx_id = send_transaction_to_mempool(ecdsachecksigtx) # Activate the Schnorr! forkblock = block(5556) yield accepted() # We have exactly hit the activation time. assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) # Make sure ECDSA is still in -- we don't want to lose uninvolved txns # when the upgrade happens. assert ecdsa_tx_id in set(node.getrawmempool()) if fakeDER64: # The 64-byte DER sigs must be ejected. assert faked_checksig_tx_id not in set(node.getrawmempool()) assert faked_multisig_tx_id not in set(node.getrawmempool()) # If we try to re-add them, they fail with non-banning errors. # In CHECKSIG it's invalid Schnorr and hence NULLFAIL. assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKSIG_ERROR, node.sendrawtransaction, ToHex(DER64checksigtx)) # In CHECKMULTISIG it's invalid length and hence BAD_LENGTH. assert_raises_rpc_error(-26, RPC_LATE_DER64_CHECKMULTISIG_ERROR, node.sendrawtransaction, ToHex(DER64multisigtx)) # And they can't be mined either... block(14, transactions=[DER64checksigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) block(15, transactions=[DER64multisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # Ensure that sending past-valid DER64 txns is *non-bannable*. check_for_no_ban_on_rejected_tx(DER64checksigtx, 16, LATE_DER64_CHECKSIG_ERROR) check_for_no_ban_on_rejected_tx(DER64multisigtx, 16, LATE_DER64_CHECKMULTISIG_ERROR) # The multisig throws a different error now assert_raises_rpc_error(-26, RPC_SCHNORR_MULTISIG_ERROR, node.sendrawtransaction, ToHex(schnorrmultisigtx)) # And it still can't be mined block(16, transactions=[schnorrmultisigtx]) yield rejected(RejectResult(16, b'blk-bad-inputs')) # Rewind bad block tip(5556) # Sending schnorrs in multisig is STILL bannable. check_for_ban_on_rejected_tx(schnorrmultisigtx) # The Schnorr CHECKSIG is now valid schnorr_tx_id = send_transaction_to_mempool(schnorrchecksigtx) # It can also be mined postforkblock = block( 21, transactions=[schnorrchecksigtx, ecdsachecksigtx]) yield accepted() # (we mined the ecdsa tx too) assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id not in set(node.getrawmempool()) # Ok, now we check if a rewind works properly accross the activation. # First, rewind the normal post-fork block. node.invalidateblock(postforkblock.hash) # txes popped back into mempool assert schnorr_tx_id in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Deactivating upgrade. node.invalidateblock(forkblock.hash) # This should kick out the Schnorr sig, but not the valid ECDSA sig. assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Check that we also do it properly on deeper rewind. node.reconsiderblock(forkblock.hash) node.reconsiderblock(postforkblock.hash) node.invalidateblock(forkblock.hash) assert schnorr_tx_id not in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool()) # Try an actual reorg (deactivates then activates upgrade in one step) node.reconsiderblock(forkblock.hash) node.reconsiderblock(postforkblock.hash) tip(5204) test = TestInstance(sync_every_block=False) for i in range(3): block(5900 + i) test.blocks_and_transactions.append([self.tip, True]) # Perform the reorg yield test # reorg finishes after the fork assert_equal( node.getblockheader(node.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME + 2) # Schnorr didn't get lost! assert schnorr_tx_id in set(node.getrawmempool()) assert ecdsa_tx_id in set(node.getrawmempool())
def get_tests(self): node = self.nodes[0] self.genesis_hash = int(node.getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(15): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE // 2) yield accepted() # Start moving MTP forward bfork = block(5555, out[15], block_size=8 * ONE_MEGABYTE) bfork.nTime = MONOLITH_START_TIME - 1 update_block(5555, []) yield accepted() # Get to one block of the May 15, 2018 HF activation for i in range(5): block(5100 + i) test.blocks_and_transactions.append([self.tip, True]) yield test # Check that the MTP is just before the configured fork point. assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], MONOLITH_START_TIME - 1) # Before we acivate the May 15, 2018 HF, 8MB is the limit. block(4444, spend=out[16], block_size=8 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(5104) # Actiavte the May 15, 2018 HF block(5556) yield accepted() # Now MTP is exactly the fork time. Bigger blocks are now accepted. assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], MONOLITH_START_TIME) # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) yield accepted() # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript( [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block(19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) yield accepted() block(20, spend=out[18], script=lots_of_checksigs, block_size=ONE_MEGABYTE, extra_sigops=1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) yield accepted() # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) yield accepted() # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript( [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block( 29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(26) # Generate a key pair to test P2SH sigops count private_key = CECKey() private_key.set_secretbytes(b"fatstacks") public_key = private_key.get_pubkey() # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript( [public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_tx(out[22], 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) yield accepted() # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId( redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = private_key.sign(sighash) + \ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) yield accepted() # Submit a very large block via RPC large_block = block( 33, spend=out[24], block_size=self.excessive_block_size) node.submitblock(ToHex(large_block))
def makePubKeys(numOfKeys): key = CECKey() key.set_secretbytes(b"randombytes2") return [key.get_pubkey()] * numOfKeys
class PIVX_FakeStakeTest(BitcoinTestFramework): def set_test_params(self): ''' Setup test environment :param: :return: ''' self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [['-staking=1', '-debug=net']]*self.num_nodes def setup_network(self): ''' Can't rely on syncing all the nodes when staking=1 :param: :return: ''' self.setup_nodes() for i in range(self.num_nodes - 1): for j in range(i+1, self.num_nodes): connect_nodes_bi(self.nodes, i, j) def init_test(self): ''' Initializes test parameters :param: :return: ''' self.log.info("\n\n*** Starting %s ***\n------------------------\n%s\n", self.__class__.__name__, self.description) # Global Test parameters (override in run_test) self.DEFAULT_FEE = 0.1 # Spam blocks to send in current test self.NUM_BLOCKS = 30 # Setup the p2p connections and start up the network thread. self.test_nodes = [] for i in range(self.num_nodes): self.test_nodes.append(TestNode()) self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i)) network_thread_start() # Start up network handling in another thread self.node = self.nodes[0] # Let the test nodes get in sync for i in range(self.num_nodes): self.test_nodes[i].wait_for_verack() def run_test(self): ''' Performs the attack of this test - run init_test first. :param: :return: ''' self.description = "" self.init_test() return def create_spam_block(self, hashPrevBlock, stakingPrevOuts, height, fStakeDoubleSpent=False, fZPoS=False, spendingPrevOuts={}): ''' creates a block to spam the network with :param hashPrevBlock: (hex string) hash of previous block stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake height: (int) block height fStakeDoubleSpent: (bool) spend the coinstake input inside the block fZPoS: (bool) stake the block with zerocoin spendingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake :return block: (CBlock) generated block ''' self.log.info("Creating Spam Block") # If not given inputs to create spam txes, use a copy of the staking inputs if len(spendingPrevOuts) == 0: spendingPrevOuts = dict(stakingPrevOuts) # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX # Even if PoS blocks have empty coinbase vout, the height is required for the vin script coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(hashPrevBlock, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(stakingPrevOuts): raise Exception("Not able to solve for any prev_outpoint") self.log.info("Stake found. Signing block...") # Sign coinstake TX and add it to the block signed_stake_tx = self.sign_stake_tx(block, stakingPrevOuts[block.prevoutStake][0], fZPoS) block.vtx.append(signed_stake_tx) # Remove coinstake input prevout unless we want to try double spending in the same block. # Skip for zPoS as the spendingPrevouts are just regular UTXOs if not fZPoS and not fStakeDoubleSpent: del spendingPrevOuts[block.prevoutStake] # remove a random prevout from the list # (to randomize block creation if the same height is picked two times) del spendingPrevOuts[choice(list(spendingPrevOuts))] # Create spam for the block. Sign the spendingPrevouts self.log.info("Creating spam TXes...") for outPoint in spendingPrevOuts: value_out = int(spendingPrevOuts[outPoint][0] - self.DEFAULT_FEE * COIN) tx = create_transaction(outPoint, b"", value_out, nTime, scriptPubKey=CScript([self.block_sig_key.get_pubkey(), OP_CHECKSIG])) # sign txes signed_tx_hex = self.node.signrawtransaction(bytes_to_hex_str(tx.serialize()))['hex'] signed_tx = CTransaction() signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex))) block.vtx.append(signed_tx) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # Sign block with coinstake key and return it block.sign_block(self.block_sig_key) return block def spend_utxo(self, utxo, address_list): ''' spend amount from previously unspent output to a provided address :param utxo: (JSON) returned from listunspent used as input addresslist: (string) destination address :return: txhash: (string) tx hash if successful, empty string otherwise ''' try: inputs = [{"txid":utxo["txid"], "vout":utxo["vout"]}] out_amount = (float(utxo["amount"]) - self.DEFAULT_FEE)/len(address_list) outputs = {} for address in address_list: outputs[address] = out_amount spendingTx = self.node.createrawtransaction(inputs, outputs) spendingTx_signed = self.node.signrawtransaction(spendingTx) if spendingTx_signed["complete"]: txhash = self.node.sendrawtransaction(spendingTx_signed["hex"]) return txhash else: self.log.warning("Error: %s" % str(spendingTx_signed["errors"])) return "" except JSONRPCException as e: self.log.error("JSONRPCException: %s" % str(e)) return "" def spend_utxos(self, utxo_list, address_list = []): ''' spend utxos to provided list of addresses or 10 new generate ones. :param utxo_list: (JSON list) returned from listunspent used as input address_list: (string list) [optional] recipient PIVX addresses. if not set, 10 new addresses will be generated from the wallet for each tx. :return: txHashes (string list) tx hashes ''' txHashes = [] # If not given, get 10 new addresses from self.node wallet if address_list == []: for i in range(10): address_list.append(self.node.getnewaddress()) for utxo in utxo_list: try: # spend current utxo to provided addresses txHash = self.spend_utxo(utxo, address_list) if txHash != "": txHashes.append(txHash) except JSONRPCException as e: self.log.error("JSONRPCException: %s" % str(e)) continue return txHashes def stake_amplification_step(self, utxo_list, address_list = []): ''' spends a list of utxos providing the list of new outputs :param utxo_list: (JSON list) returned from listunspent used as input address_list: (string list) [optional] recipient PIVX addresses. :return: new_utxos: (JSON list) list of new (valid) inputs after the spends ''' self.log.info("--> Stake Amplification step started with %d UTXOs", len(utxo_list)) txHashes = self.spend_utxos(utxo_list, address_list) num_of_txes = len(txHashes) new_utxos = [] if num_of_txes> 0: self.log.info("Created %d transactions...Mining 2 blocks to include them..." % num_of_txes) self.node.generate(2) time.sleep(2) new_utxos = self.node.listunspent() self.log.info("Amplification step produced %d new \"Fake Stake\" inputs:" % len(new_utxos)) return new_utxos def stake_amplification(self, utxo_list, iterations, address_list = []): ''' performs the "stake amplification" which gives higher chances at finding fake stakes :param utxo_list: (JSON list) returned from listunspent used as input iterations: (int) amount of stake amplification steps to perform address_list: (string list) [optional] recipient PIVX addresses. :return: all_inputs: (JSON list) list of all spent inputs ''' self.log.info("** Stake Amplification started with %d UTXOs", len(utxo_list)) valid_inputs = utxo_list all_inputs = [] for i in range(iterations): all_inputs = all_inputs + valid_inputs old_inputs = valid_inputs valid_inputs = self.stake_amplification_step(old_inputs, address_list) self.log.info("** Stake Amplification ended with %d \"fake\" UTXOs", len(all_inputs)) return all_inputs def sign_stake_tx(self, block, stake_in_value, fZPoS=False): ''' signs a coinstake transaction :param block: (CBlock) block with stake to sign stake_in_value: (int) staked amount fZPoS: (bool) zerocoin stake :return: stake_tx_signed: (CTransaction) signed tx ''' self.block_sig_key = CECKey() if fZPoS: self.log.info("Signing zPoS stake...") # Create raw zerocoin stake TX (signed) raw_stake = self.node.createrawzerocoinstake(block.prevoutStake) stake_tx_signed_raw_hex = raw_stake["hex"] # Get stake TX private key to sign the block with stake_pkey = raw_stake["private-key"] self.block_sig_key.set_compressed(True) self.block_sig_key.set_secretbytes(bytes.fromhex(stake_pkey)) else: # Create a new private key and get the corresponding public key self.block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = self.block_sig_key.get_pubkey() # Create the raw stake TX (unsigned) scriptPubKey = CScript([pubkey, OP_CHECKSIG]) outNValue = int(stake_in_value + 2*COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey)) # Sign the stake TX stake_tx_signed_raw_hex = self.node.signrawtransaction(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] # Deserialize the signed raw tx into a CTransaction object and return it stake_tx_signed = CTransaction() stake_tx_signed.deserialize(BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) return stake_tx_signed def get_prevouts(self, utxo_list, blockHeight, zpos=False): ''' get prevouts (map) for each utxo in a list :param utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input <if zpos=True> (JSON list) mints returned from listmintedzerocoins used as input blockHeight: (int) height of the previous block zpos: (bool) type of utxo_list :return: stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints to amount, block_time, nStakeModifier, hashStake ''' zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000] stakingPrevOuts = {} for utxo in utxo_list: if zpos: # get mint checkpoint checkpointHeight = blockHeight - 200 checkpointBlock = self.node.getblock(self.node.getblockhash(checkpointHeight), True) checkpoint = int(checkpointBlock['acc_checkpoint'], 16) # parse checksum and get checksumblock pos = zerocoinDenomList.index(utxo['denomination']) checksum = (checkpoint >> (32 * (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF checksumBlock = self.node.getchecksumblock(hex(checksum), utxo['denomination'], True) # get block hash and block time txBlockhash = checksumBlock['hash'] txBlocktime = checksumBlock['time'] else: # get raw transaction for current input utxo_tx = self.node.getrawtransaction(utxo['txid'], 1) # get block hash and block time txBlocktime = utxo_tx['blocktime'] txBlockhash = utxo_tx['blockhash'] # get Stake Modifier stakeModifier = int(self.node.getblock(txBlockhash)['modifier'], 16) # assemble prevout object utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime, stakeModifier, zpos) return stakingPrevOuts def log_data_dir_size(self): ''' Prints the size of the '/regtest/blocks' directory. :param: :return: ''' init_size = dir_size(self.node.datadir + "/regtest/blocks") self.log.info("Size of data dir: %s kilobytes" % str(init_size)) def test_spam(self, name, staking_utxo_list, fRandomHeight=False, randomRange=0, randomRange2=0, fDoubleSpend=False, fMustPass=False, fZPoS=False, spending_utxo_list=[]): ''' General method to create, send and test the spam blocks :param name: (string) chain branch (usually either "Main" or "Forked") staking_utxo_list: (string list) utxos to use for staking fRandomHeight: (bool) send blocks at random height randomRange: (int) if fRandomHeight=True, height is >= current-randomRange randomRange2: (int) if fRandomHeight=True, height is < current-randomRange2 fDoubleSpend: (bool) if true, stake input is double spent in block.vtx fMustPass: (bool) if true, the blocks must be stored on disk fZPoS: (bool) stake the block with zerocoin spending_utxo_list: (string list) utxos to use for spending :return: err_msgs: (string list) reports error messages from the test or an empty list if test is successful ''' # Create empty error messages list err_msgs = [] # Log initial datadir size self.log_data_dir_size() # Get latest block number and hash block_count = self.node.getblockcount() pastBlockHash = self.node.getblockhash(block_count) randomCount = block_count self.log.info("Current height: %d" % block_count) for i in range(0, self.NUM_BLOCKS): if i !=0: self.log.info("Sent %d blocks out of %d" % (i, self.NUM_BLOCKS)) # if fRandomHeight=True get a random block number (in range) and corresponding hash if fRandomHeight: randomCount = randint(block_count - randomRange, block_count - randomRange2) pastBlockHash = self.node.getblockhash(randomCount) # Get spending prevouts and staking prevouts for the height of current block current_block_n = randomCount + 1 stakingPrevOuts = self.get_prevouts(staking_utxo_list, randomCount, zpos=fZPoS) spendingPrevOuts = self.get_prevouts(spending_utxo_list, randomCount) # Create the spam block block = self.create_spam_block(pastBlockHash, stakingPrevOuts, current_block_n, fStakeDoubleSpent=fDoubleSpend, fZPoS=fZPoS, spendingPrevOuts=spendingPrevOuts) # Log time and size of the block block_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(block.nTime)) block_size = len(block.serialize())/1000 self.log.info("Sending block %d [%s...] - nTime: %s - Size (kb): %.2f", current_block_n, block.hash[:7], block_time, block_size) # Try submitblock var = self.node.submitblock(bytes_to_hex_str(block.serialize())) time.sleep(1) if (not fMustPass and var not in [None, "bad-txns-invalid-zpiv"]) or (fMustPass and var != "inconclusive"): self.log.error("submitblock [fMustPass=%s] result: %s" % (str(fMustPass), str(var))) err_msgs.append("submitblock %d: %s" % (current_block_n, str(var))) # Try sending the message block msg = msg_block(block) try: self.test_nodes[0].handle_connect() self.test_nodes[0].send_message(msg) time.sleep(2) block_ret = self.node.getblock(block.hash) if not fMustPass and block_ret is not None: self.log.error("Error, block stored in %s chain" % name) err_msgs.append("getblock %d: result not None" % current_block_n) if fMustPass: if block_ret is None: self.log.error("Error, block NOT stored in %s chain" % name) err_msgs.append("getblock %d: result is None" % current_block_n) else: self.log.info("Good. Block IS stored on disk.") except JSONRPCException as e: exc_msg = str(e) if exc_msg == "Can't read block from disk (-32603)": if fMustPass: self.log.warning("Bad! Block was NOT stored to disk.") err_msgs.append(exc_msg) else: self.log.info("Good. Block was not stored on disk.") else: self.log.warning(exc_msg) err_msgs.append(exc_msg) except Exception as e: exc_msg = str(e) self.log.error(exc_msg) err_msgs.append(exc_msg) self.log.info("Sent all %s blocks." % str(self.NUM_BLOCKS)) # Log final datadir size self.log_data_dir_size() # Return errors list return err_msgs
def run_test(self): node = self.nodes[0] # Generate 6 keys. rawkeys = [] pubkeys = [] for i in range(6): raw_key = CECKey() raw_key.set_secretbytes(('privkey%d' % i).encode('ascii')) rawkeys.append(raw_key) pubkeys = [CPubKey(key.get_pubkey()) for key in rawkeys] # Create a 4-of-6 multi-sig wallet with CLTV. height = 210 redeem_script = CScript( [CScriptNum(height), OP_CHECKLOCKTIMEVERIFY, OP_DROP ] + # CLTV (lock_time >= 210) [OP_4] + pubkeys + [OP_6, OP_CHECKMULTISIG]) # multi-sig hex_redeem_script = bytes_to_hex_str(redeem_script) p2sh_address = script_to_p2sh(redeem_script, main=False) # Send 1 coin to the mult-sig wallet. txid = node.sendtoaddress(p2sh_address, 1.0) raw_tx = node.getrawtransaction(txid, True) try: node.importaddress(hex_redeem_script, 'cltv', True, True) except Exception as err: pass assert_equal( sig(node.getreceivedbyaddress(p2sh_address, 0) - Decimal(1.0)), 0) # Mine one block to confirm the transaction. node.generate(1) # block 201 assert_equal( sig(node.getreceivedbyaddress(p2sh_address, 1) - Decimal(1.0)), 0) # Try to spend the coin. addr_to = node.getnewaddress('') # (1) Find the UTXO for vout in raw_tx['vout']: if vout['scriptPubKey']['addresses'] == [p2sh_address]: vout_n = vout['n'] hex_script_pubkey = raw_tx['vout'][vout_n]['scriptPubKey']['hex'] value = raw_tx['vout'][vout_n]['value'] # (2) Create a tx inputs = [{ "txid": txid, "vout": vout_n, "scriptPubKey": hex_script_pubkey, "redeemScript": hex_redeem_script, "amount": value, }] outputs = {addr_to: 0.999} lock_time = height hex_spend_raw_tx = node.createrawtransaction(inputs, outputs, lock_time) hex_funding_raw_tx = node.getrawtransaction(txid, False) # (3) Try to sign the spending tx. tx0 = CTransaction() tx0.deserialize(io.BytesIO(hex_str_to_bytes(hex_funding_raw_tx))) tx1 = CTransaction() tx1.deserialize(io.BytesIO(hex_str_to_bytes(hex_spend_raw_tx))) self.sign_tx(tx1, tx0, vout_n, redeem_script, 0, rawkeys[:4]) # Sign with key[0:4] # Mine some blocks to pass the lock time. node.generate(10) # Spend the CLTV multi-sig coins. raw_tx1 = tx1.serialize() hex_raw_tx1 = bytes_to_hex_str(raw_tx1) node.sendrawtransaction(hex_raw_tx1) # Check the tx is accepted by mempool but not confirmed. assert_equal( sig(node.getreceivedbyaddress(addr_to, 0) - Decimal(0.999)), 0) assert_equal(sig(node.getreceivedbyaddress(addr_to, 1)), 0) # Mine a block to confirm the tx. node.generate(1) assert_equal( sig(node.getreceivedbyaddress(addr_to, 1) - Decimal(0.999)), 0)
class FullBlockTest(ComparisonTestFramework): ''' Can either run this test as 1 node with expected answers, or two and compare them. Change the "outcome" variable from each TestInstance object to only do the comparison. ''' def __init__(self): self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.block_time = int(time.time())+1 self.tip = None self.blocks = {} def run_test(self): test = TestManager(self, self.options.tmpdir) test.add_all_connections(self.nodes) NetworkThread().start() # Start up network handling in another thread test.run() def add_transactions_to_block(self, block, tx_list): [ tx.rehash() for tx in tx_list ] block.vtx.extend(tx_list) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() return block # Create a block on top of self.tip, and advance self.tip to point to the new block # if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, # and rest will go to fees. def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): if self.tip == None: base_block_hash = self.genesis_hash else: base_block_hash = self.tip.sha256 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, self.block_time) if (spend != None): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) # no signature yet # This copies the java comparison tool testing behavior: the first # txout has a garbage scriptPubKey, "to make sure we're not # pre-verifying too much" (?) tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) # Now sign it if necessary scriptSig = b"" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it (sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL) scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block block = self.add_transactions_to_block(block, [tx]) block.solve() self.tip = block self.block_heights[block.sha256] = height self.block_time += 1 assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previous marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject = None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # add transactions to a block produced by next_block def update_block(block_number, new_transactions): block = self.blocks[block_number] old_hash = block.sha256 self.add_transactions_to_block(block, new_transactions) block.solve() # Update the internal state just like in next_block self.tip = block self.block_heights[block.sha256] = self.block_heights[old_hash] del self.block_heights[old_hash] self.blocks[block_number] = block return block # creates a new block and advances the tip to that block block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(1000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # Start by building a couple of blocks on top (which output is spent is # in parentheses): # genesis -> b1 (0) -> b2 (1) out0 = get_spendable_output() block(1, spend=out0) save_spendable_output() yield accepted() out1 = get_spendable_output() b2 = block(2, spend=out1) yield accepted() # so fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes priority. tip(1) b3 = block(3, spend=out1) txout_b3 = PreviousSpendableOutput(b3.vtx[1], 1) yield rejected() # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) out2 = get_spendable_output() block(4, spend=out2) yield accepted() # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) tip(2) block(5, spend=out2) save_spendable_output() yield rejected() out3 = get_spendable_output() block(6, spend=out3) yield accepted() # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) tip(5) block(7, spend=out2) yield rejected() out4 = get_spendable_output() block(8, spend=out4) yield rejected() # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) tip(6) block(9, spend=out4, additional_coinbase_value=1) yield rejected(RejectResult(16, b'bad-cb-amount')) # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) tip(5) block(10, spend=out3) yield rejected() block(11, spend=out4, additional_coinbase_value=1) yield rejected(RejectResult(16, b'bad-cb-amount')) # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) # (b12 added last) # \-> b3 (1) -> b4 (2) tip(5) b12 = block(12, spend=out3) save_spendable_output() #yield TestInstance([[b12, False]]) b13 = block(13, spend=out4) # Deliver the block header for b12, and the block b13. # b13 should be accepted but the tip won't advance until b12 is delivered. yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) save_spendable_output() out5 = get_spendable_output() # b14 is invalid, but the node won't know that until it tries to connect # Tip still can't advance because b12 is missing block(14, spend=out5, additional_coinbase_value=1) yield rejected() yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6) # \-> b3 (1) -> b4 (2) # Test that a block with a lot of checksigs is okay lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50 - 1)) tip(13) block(15, spend=out5, script=lots_of_checksigs) yield accepted() # Test that a block with too many checksigs is rejected out6 = get_spendable_output() too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 // 50)) block(16, spend=out6, script=too_many_checksigs) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Attempt to spend a transaction created on a different fork # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) # \-> b3 (1) -> b4 (2) tip(15) block(17, spend=txout_b3) yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) # Attempt to spend a transaction created on a different fork (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b18 (b3.vtx[1]) -> b19 (6) # \-> b3 (1) -> b4 (2) tip(13) block(18, spend=txout_b3) yield rejected() block(19, spend=out6) yield rejected() # Attempt to spend a coinbase at depth too low # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) # \-> b3 (1) -> b4 (2) tip(15) out7 = get_spendable_output() block(20, spend=out7) yield rejected(RejectResult(16, b'bad-txns-premature-spend-of-coinbase')) # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b21 (6) -> b22 (5) # \-> b3 (1) -> b4 (2) tip(13) block(21, spend=out6) yield rejected() block(22, spend=out5) yield rejected() # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) # \-> b24 (6) -> b25 (7) # \-> b3 (1) -> b4 (2) tip(15) b23 = block(23, spend=out6) old_hash = b23.sha256 tx = CTransaction() script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) tx.vout.append(CTxOut(0, script_output)) tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 1))) b23 = update_block(23, [tx]) # Make sure the math above worked out to produce a max-sized block assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE) yield accepted() # Make the next block one byte bigger and check that it fails tip(15) b24 = block(24, spend=out6) script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69 script_output = CScript([b'\x00' * (script_length+1)]) tx.vout = [CTxOut(0, script_output)] b24 = update_block(24, [tx]) assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE+1) yield rejected(RejectResult(16, b'bad-blk-length')) b25 = block(25, spend=out7) yield rejected() # Create blocks with a coinbase input script size out of range # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) # \-> ... (6) -> ... (7) # \-> b3 (1) -> b4 (2) tip(15) b26 = block(26, spend=out6) b26.vtx[0].vin[0].scriptSig = b'\x00' b26.vtx[0].rehash() # update_block causes the merkle root to get updated, even with no new # transactions, and updates the required state. b26 = update_block(26, []) yield rejected(RejectResult(16, b'bad-cb-length')) # Extend the b26 chain to make sure bitcreditd isn't accepting b26 b27 = block(27, spend=out7) yield rejected() # Now try a too-large-coinbase script tip(15) b28 = block(28, spend=out6) b28.vtx[0].vin[0].scriptSig = b'\x00' * 101 b28.vtx[0].rehash() b28 = update_block(28, []) yield rejected(RejectResult(16, b'bad-cb-length')) # Extend the b28 chain to make sure bitcreditd isn't accepted b28 b29 = block(29, spend=out7) # TODO: Should get a reject message back with "bad-prevblk", except # there's a bug that prevents this from being detected. Just note # failure for now, and add the reject result later. yield rejected() # b30 has a max-sized coinbase scriptSig. tip(23) b30 = block(30) b30.vtx[0].vin[0].scriptSig = b'\x00' * 100 b30.vtx[0].rehash() b30 = update_block(30, []) yield accepted()
class FullBlockTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"fatstacks") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} self.excessive_block_size = 16 * ONE_MEGABYTE self.extra_args = [[ '-norelaypriority', '-whitelist=127.0.0.1', '-limitancestorcount=9999', '-limitancestorsize=9999', '-limitdescendantcount=9999', '-limitdescendantsize=9999', '-maxmempool=999', "-excessiveblocksize=%d" % self.excessive_block_size ]] def add_options(self, parser): super().add_options(parser) parser.add_option("--runbarelyexpensive", dest="runbarelyexpensive", default=True) def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) # Start up network handling in another thread NetworkThread().start() # Set the blocksize to 2MB as initial condition self.nodes[0].setexcessiveblock(self.excessive_block_size) self.test.run() def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = create_transaction(spend_tx, n, b"", value, script) return tx # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output n in # spend_tx 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 create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, n, value, script) self.sign_tx(tx, spend_tx, n) tx.rehash() return tx def next_block(self, number, spend=None, additional_coinbase_value=0, script=None, extra_sigops=0, block_size=0, solve=True): """ Create a block on top of self.tip, and advance self.tip to point to the new block if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, and rest will go to fees. """ if self.tip == None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[ spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) spendable_output = None if (spend != None): tx = CTransaction() # no signature yet tx.vin.append( CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) # We put some random data into the first transaction of the chain # to randomize ids tx.vout.append( CTxOut(0, CScript([random.randint(0, 255), OP_DROP, OP_TRUE]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) spendable_output = PreviousSpendableOutput(tx, 0) # Now sign it if necessary scriptSig = b"" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it sighash = SignatureHashForkId( spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend.tx.vout[spend.n].nValue) scriptSig = CScript([ self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) ]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() if spendable_output != None and block_size > 0: while len(block.serialize()) < block_size: tx = CTransaction() script_length = block_size - len(block.serialize()) - 79 if script_length > 510000: script_length = 500000 tx_sigops = min(extra_sigops, script_length, MAX_TX_SIGOPS_COUNT) extra_sigops -= tx_sigops script_pad_len = script_length - tx_sigops script_output = CScript([b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops) tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx.vout.append(CTxOut(0, script_output)) tx.vin.append( CTxIn( COutPoint(spendable_output.tx.sha256, spendable_output.n))) spendable_output = PreviousSpendableOutput(tx, 0) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() # Make sure the math above worked out to produce the correct block size # (the math will fail if there are too many transactions in the block) assert_equal(len(block.serialize()), block_size) # Make sure all the requested sigops have been included assert_equal(extra_sigops, 0) if solve: block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(16): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE) yield accepted() # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) yield accepted() # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB - 1)) block(19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) yield accepted() too_many_blk_checksigs = CScript([OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block(20, spend=out[18], script=too_many_blk_checksigs, block_size=ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) yield accepted() # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) yield accepted() # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block(29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(26) # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_and_sign_transaction(out[22].tx, out[22].n, 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) yield accepted() # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = self.coinbase_key.sign(sighash) + bytes( bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) yield accepted() # Check that compact block also work for big blocks node = self.nodes[0] peer = TestNode() peer.add_connection(NodeConn('127.0.0.1', p2p_port(0), node, peer)) # Start up network handling in another thread and wait for connection # to be etablished NetworkThread().start() peer.wait_for_verack() # Wait for SENDCMPCT def received_sendcmpct(): return (peer.last_sendcmpct != None) wait_until(received_sendcmpct, timeout=30) sendcmpct = msg_sendcmpct() sendcmpct.version = 1 sendcmpct.announce = True peer.send_and_ping(sendcmpct) # Exchange headers def received_getheaders(): return (peer.last_getheaders != None) wait_until(received_getheaders, timeout=30) # Return the favor peer.send_message(peer.last_getheaders) # Wait for the header list def received_headers(): return (peer.last_headers != None) wait_until(received_headers, timeout=30) # It's like we know about the same headers ! peer.send_message(peer.last_headers) # Send a block b33 = block(33, spend=out[24], block_size=ONE_MEGABYTE + 1) yield accepted() # Checks the node to forward it via compact block def received_block(): return (peer.last_cmpctblock != None) wait_until(received_block, timeout=30) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert (cmpctblk_header.sha256 == b33.sha256) # Send a bigger block peer.clear_block_data() b34 = block(34, spend=out[25], block_size=8 * ONE_MEGABYTE) yield accepted() # Checks the node to forward it via compact block wait_until(received_block, timeout=30) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert (cmpctblk_header.sha256 == b34.sha256) # Let's send a compact block and see if the node accepts it. # First, we generate the block and send all transaction to the mempool b35 = block(35, spend=out[26], block_size=8 * ONE_MEGABYTE) for i in range(1, len(b35.vtx)): node.sendrawtransaction(ToHex(b35.vtx[i]), True) # Now we create the compact block and send it comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(b35) peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) # Check that compact block is received properly assert (int(node.getbestblockhash(), 16) == b35.sha256)
class FullBlockTest(ComparisonTestFramework): # Can either run this test as 1 node with expected answers, or two and compare them. # Change the "outcome" variable from each TestInstance object to only do # the comparison. def __init__(self): super().__init__() self.num_nodes = 1 self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"fatstacks") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} self.excessive_block_size = 16 * ONE_MEGABYTE self.extra_args = [['-norelaypriority', '-whitelist=127.0.0.1', '-limitancestorcount=9999', '-limitancestorsize=9999', '-limitdescendantcount=9999', '-limitdescendantsize=9999', '-maxmempool=999', "-excessiveblocksize=%d" % self.excessive_block_size]] def add_options(self, parser): super().add_options(parser) parser.add_option( "--runbarelyexpensive", dest="runbarelyexpensive", default=True) def run_test(self): self.test = TestManager(self, self.options.tmpdir) self.test.add_all_connections(self.nodes) # Start up network handling in another thread NetworkThread().start() # Set the blocksize to 2MB as initial condition self.nodes[0].setexcessiveblock(self.excessive_block_size) self.test.run() def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = create_transaction(spend_tx, n, b"", value, script) return tx # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output n in # spend_tx 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 create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, n, value, script) self.sign_tx(tx, spend_tx, n) tx.rehash() return tx def next_block(self, number, spend=None, additional_coinbase_value=0, script=None, extra_sigops=0, block_size=0, solve=True): """ Create a block on top of self.tip, and advance self.tip to point to the new block if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, and rest will go to fees. """ if self.tip == None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.block_heights[base_block_hash] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) coinbase.vout[0].nValue += additional_coinbase_value if (spend != None): coinbase.vout[0].nValue += spend.tx.vout[ spend.n].nValue - 1 # all but one satoshi to fees coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) spendable_output = None if (spend != None): tx = CTransaction() # no signature yet tx.vin.append( CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) # We put some random data into the first transaction of the chain # to randomize ids tx.vout.append( CTxOut(0, CScript([random.randint(0, 255), OP_DROP, OP_TRUE]))) if script == None: tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) else: tx.vout.append(CTxOut(1, script)) spendable_output = PreviousSpendableOutput(tx, 0) # Now sign it if necessary scriptSig = b"" scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend scriptSig = CScript([OP_TRUE]) else: # We have to actually sign it sighash = SignatureHashForkId( spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend.tx.vout[spend.n].nValue) scriptSig = CScript( [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) tx.vin[0].scriptSig = scriptSig # Now add the transaction to the block self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() if spendable_output != None and block_size > 0: while len(block.serialize()) < block_size: tx = CTransaction() script_length = block_size - len(block.serialize()) - 79 if script_length > 510000: script_length = 500000 tx_sigops = min( extra_sigops, script_length, MAX_TX_SIGOPS_COUNT) extra_sigops -= tx_sigops script_pad_len = script_length - tx_sigops script_output = CScript( [b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops) tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx.vout.append(CTxOut(0, script_output)) tx.vin.append( CTxIn(COutPoint(spendable_output.tx.sha256, spendable_output.n))) spendable_output = PreviousSpendableOutput(tx, 0) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() # Make sure the math above worked out to produce the correct block size # (the math will fail if there are too many transactions in the block) assert_equal(len(block.serialize()), block_size) # Make sure all the requested sigops have been included assert_equal(extra_sigops, 0) if solve: block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def get_tests(self): self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # returns a test case that asserts that the current tip was accepted def accepted(): return TestInstance([[self.tip, True]]) # returns a test case that asserts that the current tip was rejected def rejected(reject=None): if reject is None: return TestInstance([[self.tip, False]]) else: return TestInstance([[self.tip, reject]]) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() yield accepted() # Now we need that block to mature so we can spend the coinbase. test = TestInstance(sync_every_block=False) for i in range(99): block(5000 + i) test.blocks_and_transactions.append([self.tip, True]) save_spendable_output() yield test # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(100): out.append(get_spendable_output()) # Let's build some blocks and test them. for i in range(16): n = i + 1 block(n, spend=out[i], block_size=n * ONE_MEGABYTE) yield accepted() # block of maximal size block(17, spend=out[16], block_size=self.excessive_block_size) yield accepted() # Reject oversized blocks with bad-blk-length error block(18, spend=out[17], block_size=self.excessive_block_size + 1) yield rejected(RejectResult(16, b'bad-blk-length')) # Rewind bad block. tip(17) # Accept many sigops lots_of_checksigs = CScript( [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB - 1)) block( 19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) yield accepted() too_many_blk_checksigs = CScript( [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) block( 20, spend=out[18], script=too_many_blk_checksigs, block_size=ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(19) # Accept 40k sigops per block > 1MB and <= 2MB block(21, spend=out[18], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) yield accepted() # Accept 40k sigops per block > 1MB and <= 2MB block(22, spend=out[19], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(23, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(24, spend=out[20], script=lots_of_checksigs, extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(22) # Accept 60k sigops per block > 2MB and <= 3MB block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) yield accepted() # Accept 60k sigops per block > 2MB and <= 3MB block(26, spend=out[21], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) yield accepted() # Reject more than 40k sigops per block > 1MB and <= 2MB. block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Reject more than 40k sigops per block > 1MB and <= 2MB. block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) yield rejected(RejectResult(16, b'bad-blk-sigops')) # Rewind bad block tip(26) # Too many sigops in one txn too_many_tx_checksigs = CScript( [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) block( 29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(26) # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [ OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a p2sh transaction p2sh_tx = self.create_and_sign_transaction( out[22].tx, out[22].n, 1, p2sh_script) # Add the transaction to the block block(30) update_block(30, [p2sh_tx]) yield accepted() # Creates a new transaction using the p2sh transaction included in the # last block def spend_p2sh_tx(output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId( redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) sig = self.coinbase_key.sign(sighash) + bytes( bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # Sigops p2sh limit p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh txn too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) yield rejected(RejectResult(16, b'bad-txn-sigops')) # Rewind bad block tip(30) # Max sigops in one p2sh txn max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) yield accepted() # Check that compact block also work for big blocks node = self.nodes[0] peer = TestNode() peer.add_connection(NodeConn('127.0.0.1', p2p_port(0), node, peer)) # Start up network handling in another thread and wait for connection # to be etablished NetworkThread().start() peer.wait_for_verack() # Wait for SENDCMPCT def received_sendcmpct(): return (peer.last_sendcmpct != None) got_sendcmpt = wait_until(received_sendcmpct, timeout=30) assert(got_sendcmpt) sendcmpct = msg_sendcmpct() sendcmpct.version = 1 sendcmpct.announce = True peer.send_and_ping(sendcmpct) # Exchange headers def received_getheaders(): return (peer.last_getheaders != None) got_getheaders = wait_until(received_getheaders, timeout=30) assert(got_getheaders) # Return the favor peer.send_message(peer.last_getheaders) # Wait for the header list def received_headers(): return (peer.last_headers != None) got_headers = wait_until(received_headers, timeout=30) assert(got_headers) # It's like we know about the same headers ! peer.send_message(peer.last_headers) # Send a block b33 = block(33, spend=out[24], block_size=ONE_MEGABYTE + 1) yield accepted() # Checks the node to forward it via compact block def received_block(): return (peer.last_cmpctblock != None) got_cmpctblock = wait_until(received_block, timeout=30) assert(got_cmpctblock) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert(cmpctblk_header.sha256 == b33.sha256) # Send a bigger block peer.clear_block_data() b34 = block(34, spend=out[25], block_size=8 * ONE_MEGABYTE) yield accepted() # Checks the node to forward it via compact block got_cmpctblock = wait_until(received_block, timeout=30) assert(got_cmpctblock) # Was it our block ? cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header cmpctblk_header.calc_sha256() assert(cmpctblk_header.sha256 == b34.sha256) # Let's send a compact block and see if the node accepts it. # First, we generate the block and send all transaction to the mempool b35 = block(35, spend=out[26], block_size=8 * ONE_MEGABYTE) for i in range(1, len(b35.vtx)): node.sendrawtransaction(ToHex(b35.vtx[i]), True) # Now we create the compact block and send it comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(b35) peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) # Check that compact block is received properly assert(int(node.getbestblockhash(), 16) == b35.sha256)
class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} def add_options(self, parser): super().add_options(parser) parser.add_argument("--runbarelyexpensive", dest="runbarelyexpensive", default=True) def add_transactions_to_block(self, block, tx_list): [tx.rehash() for tx in tx_list] block.vtx.extend(tx_list) block.vtx = [block.vtx[0]] + \ sorted(block.vtx[1:], key=lambda tx: tx.get_id()) # this is a little handier to use than the version in blocktools.py def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = create_tx_with_script(spend_tx, n, b"", value, script) return tx # sign a transaction, using the key we know about # this signs input 0 in tx, which is assumed to be spending output n in # spend_tx 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 create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, n, value, script) self.sign_tx(tx, spend_tx, n) tx.rehash() return tx def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE])): if self.tip == None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 # First create the coinbase height = self.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 to fees coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 coinbase.rehash() block = create_block(base_block_hash, coinbase, block_time) # spend 1 satoshi tx = create_tx_with_script(spend.tx, spend.n, b"", 1, script) self.sign_tx(tx, spend.tx, spend.n) self.add_transactions_to_block(block, [tx]) block.hashMerkleRoot = block.calc_merkle_root() # Do PoW, which is very inexpensive on regnet block.solve() self.tip = block self.block_heights[block.sha256] = height assert number not in self.blocks self.blocks[number] = block return block def run_test(self): node = self.nodes[0] node.add_p2p_connection(P2PDataStore()) self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 spendable_outputs = [] # save the current tip so it can be spent by a later block def save_spendable_output(): spendable_outputs.append(self.tip) # get an output that we previously marked as spendable def get_spendable_output(): return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) # move the tip back to a previous block def tip(number): self.tip = self.blocks[number] # adds transactions to the block and updates state def update_block(block_number, new_transactions): block = self.blocks[block_number] self.add_transactions_to_block(block, new_transactions) old_sha256 = block.sha256 block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Update the internal state just like in next_block self.tip = block if block.sha256 != old_sha256: self.block_heights[ block.sha256] = self.block_heights[old_sha256] del self.block_heights[old_sha256] self.blocks[block_number] = block return block # shorthand for functions block = self.next_block # Create a new block block(0) save_spendable_output() node.p2p.send_blocks_and_test([self.tip], node) # Now we need that block to mature so we can spend the coinbase. maturity_blocks = [] for i in range(99): block(5000 + i) maturity_blocks.append(self.tip) save_spendable_output() node.p2p.send_blocks_and_test(maturity_blocks, node) # Collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(33): out.append(get_spendable_output()) # P2SH # Build the redeem script, hash it, use hash to create the p2sh script redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Creates a new transaction using a p2sh transaction as input def spend_p2sh_tx(p2sh_tx_to_spend, output_script=CScript([OP_TRUE])): # Create the transaction spent_p2sh_tx = CTransaction() spent_p2sh_tx.vin.append( CTxIn(COutPoint(p2sh_tx_to_spend.sha256, 0), b'')) spent_p2sh_tx.vout.append(CTxOut(1000, output_script)) # Sign the transaction using the redeem script sighash = SignatureHashForkId(redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx_to_spend.vout[0].nValue) sig = self.coinbase_key.sign(sighash) + bytes( bytearray([SIGHASH_ALL | SIGHASH_FORKID])) spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) spent_p2sh_tx.rehash() return spent_p2sh_tx # P2SH tests # Create a p2sh transaction p2sh_tx = self.create_and_sign_transaction(out[0].tx, out[0].n, 10000, p2sh_script) # Add the transaction to the block block(1) update_block(1, [p2sh_tx]) node.p2p.send_blocks_and_test([self.tip], node) # Sigops p2sh limit for the mempool test p2sh_sigops_limit_mempool = MAX_STANDARD_TX_SIGOPS - \ redeem_script.GetSigOpCount(True) # Too many sigops in one p2sh script too_many_p2sh_sigops_mempool = CScript([OP_CHECKSIG] * (p2sh_sigops_limit_mempool + 1)) # A transaction with this output script can't get into the mempool assert_raises_rpc_error( -26, RPC_TXNS_TOO_MANY_SIGOPS_ERROR, node.sendrawtransaction, ToHex(spend_p2sh_tx(p2sh_tx, too_many_p2sh_sigops_mempool))) # The transaction is rejected, so the mempool should still be empty assert_equal(set(node.getrawmempool()), set()) # Max sigops in one p2sh txn max_p2sh_sigops_mempool = CScript([OP_CHECKSIG] * (p2sh_sigops_limit_mempool)) # A transaction with this output script can get into the mempool max_p2sh_sigops_txn = spend_p2sh_tx(p2sh_tx, max_p2sh_sigops_mempool) max_p2sh_sigops_txn_id = node.sendrawtransaction( ToHex(max_p2sh_sigops_txn)) assert_equal(set(node.getrawmempool()), {max_p2sh_sigops_txn_id}) # Mine the transaction block(2, spend=out[1]) update_block(2, [max_p2sh_sigops_txn]) node.p2p.send_blocks_and_test([self.tip], node) # The transaction has been mined, it's not in the mempool anymore assert_equal(set(node.getrawmempool()), set())