def mine_and_test_listunspent(self, script_list, ismine): utxo = find_spendable_utxo(self.nodes[0], 50) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int('0x'+utxo['txid'],0), utxo['vout']))) for i in script_list: tx.vout.append(CTxOut(10000000, i)) tx.rehash() signresults = self.nodes[0].signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] txid = self.nodes[0].sendrawtransaction(signresults, True) self.nodes[0].generate(1) sync_blocks(self.nodes) watchcount = 0 spendcount = 0 for i in self.nodes[0].listunspent(): if (i['txid'] == txid): watchcount += 1 if (i['spendable'] == True): spendcount += 1 if (ismine == 2): assert_equal(spendcount, len(script_list)) elif (ismine == 1): assert_equal(watchcount, len(script_list)) assert_equal(spendcount, 0) else: assert_equal(watchcount, 0) return txid
def test_prioritised_transactions(self): # Ensure that fee deltas used via prioritisetransaction are # correctly used by replacement logic # 1. Check that feeperkb uses modified fees tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) # Higher fee, but the actual fee per KB is much lower. tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))] tx1b_hex = txToHex(tx1b) # Verify tx1b cannot replace tx1a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, True) # Use prioritisetransaction to set tx1a's fee to 0. self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1*COIN)) # Now tx1b should be able to replace tx1a tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True) assert(tx1b_txid in self.nodes[0].getrawmempool()) # 2. Check that absolute fee checks use modified fee. tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN)) tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx2a_hex = txToHex(tx2a) self.nodes[0].sendrawtransaction(tx2a_hex, True) # Lower fee, but we'll prioritise it tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2b.vout = [CTxOut(int(1.01 * COIN), CScript([b'a' * 35]))] tx2b.rehash() tx2b_hex = txToHex(tx2b) # Verify tx2b cannot replace tx2a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, True) # Now prioritise tx2b to have a higher modified fee self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1*COIN)) # tx2b should now be accepted tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True) assert(tx2b_txid in self.nodes[0].getrawmempool())
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
def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): """Create a txout with a given amount and scriptPubKey Mines coins as needed. confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ fee = 1*COIN while node.getbalance()['bitcoin'] < satoshi_round((amount + fee)/COIN): node.generate(100) new_addr = node.getnewaddress() unblinded_addr = node.validateaddress(new_addr)["unconfidential"] txidstr = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) tx1 = node.getrawtransaction(txidstr, 1) txid = int(txidstr, 16) i = None for i, txout in enumerate(tx1['vout']): if txout['scriptPubKey']['type'] == "fee": continue # skip fee outputs if txout['scriptPubKey']['addresses'] == [unblinded_addr]: break assert i is not None tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] tx1raw = CTransaction() tx1raw.deserialize(BytesIO(hex_str_to_bytes(node.getrawtransaction(txidstr)))) feeout = CTxOut(CTxOutValue(tx1raw.vout[i].nValue.getAmount() - amount)) tx2.vout = [CTxOut(amount, scriptPubKey), feeout] tx2.rehash() signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: node.generate(1) new_size = len(node.getrawmempool()) # Error out if we have something stuck in the mempool, as this # would likely be a bug. assert(new_size < mempool_size) mempool_size = new_size return COutPoint(int(txid, 16), 0)
def create_and_mine_tx_from_txids(self, txids, success = True): tx = CTransaction() for i in txids: txtmp = CTransaction() txraw = self.nodes[0].getrawtransaction(i) f = BytesIO(hex_str_to_bytes(txraw)) txtmp.deserialize(f) for j in range(len(txtmp.vout)): tx.vin.append(CTxIn(COutPoint(int('0x'+i,0), j))) tx.vout.append(CTxOut(0, CScript())) tx.rehash() signresults = self.nodes[0].signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] self.nodes[0].sendrawtransaction(signresults, True) self.nodes[0].generate(1) sync_blocks(self.nodes)
def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): """Create a txout with a given amount and scriptPubKey Mines coins as needed. confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ fee = 1*COIN while node.getbalance() < satoshi_round((amount + fee)/COIN): node.generate(100) new_addr = node.getnewaddress() txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) tx1 = node.getrawtransaction(txid, 1) txid = int(txid, 16) i = None for i, txout in enumerate(tx1['vout']): if txout['scriptPubKey']['addresses'] == [new_addr]: break assert i is not None tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] tx2.vout = [CTxOut(amount, scriptPubKey)] tx2.rehash() signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: node.generate(1) new_size = len(node.getrawmempool()) # Error out if we have something stuck in the mempool, as this # would likely be a bug. assert(new_size < mempool_size) mempool_size = new_size return COutPoint(int(txid, 16), 0)
def test_bip68_not_consensus(self): assert(get_bip9_status(self.nodes[0], 'csv')['status'] != 'active') txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Make an anyone-can-spend transaction tx2 = CTransaction() tx2.nVersion = 1 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))] # sign tx2 tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(ToHex(tx2)) # Now make an invalid spend of tx2 according to BIP68 sequence_value = 100 # 100 block relative locktime tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)] tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), CScript([b'a' * 35]))] tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3)) # make a block that violates bip68; ensure that the tip updates tip = int(self.nodes[0].getbestblockhash(), 16) block = create_block(tip, create_coinbase(self.nodes[0].getblockcount()+1)) block.nVersion = 3 block.vtx.extend([tx1, tx2, tx3]) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() add_witness_commitment(block) block.solve() self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True))) assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), CScript([b'a' * 35]))] tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(ToHex(tx)) return tx
def make_utxos(self): block = self.build_block_on_tip(self.nodes[0]) self.segwit_node.send_and_ping(msg_block(block)) assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 self.nodes[0].generatetoaddress(100, self.nodes[0].getnewaddress(address_type="bech32")) 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.segwit_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)])
def test_disable_flag(self): # Create some unconfirmed inputs new_addr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(new_addr, 2) # send 2 BTC utxos = self.nodes[0].listunspent(0, 0) assert len(utxos) > 0 utxo = utxos[0] tx1 = CTransaction() value = int(satoshi_round(utxo["amount"] - self.relayfee)*COIN) # Check that the disable flag disables relative locktime. # If sequence locks were used, this would require 1 block for the # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] tx1.vout = [CTxOut(value, CScript([b'a']))] tx1_signed = self.nodes[0].signrawtransactionwithwallet(ToHex(tx1))["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) tx1_id = int(tx1_id, 16) # This transaction will enable sequence-locks, so this transaction should # fail tx2 = CTransaction() tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] tx2.vout = [CTxOut(int(value - self.relayfee * COIN), CScript([b'a' * 35]))] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx2)) # Setting the version back down to 1 should disable the sequence lock, # so this should be accepted. tx2.nVersion = 1 self.nodes[0].sendrawtransaction(ToHex(tx2))
def assert_tx_format_also_signed(self, utxo, segwit): raw = self.nodes[0].createrawtransaction( [{"txid": utxo["txid"], "vout": utxo["vout"]}], [{self.unknown_addr: "49.9"}, {"fee": "0.1"}] ) unsigned_decoded = self.nodes[0].decoderawtransaction(raw) assert_equal(len(unsigned_decoded["vin"]), 1) assert('txinwitness' not in unsigned_decoded["vin"][0]) # Cross-check python serialization tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw))) assert_equal(tx.vin[0].prevout.hash, int("0x"+utxo["txid"], 0)) assert_equal(len(tx.vin), len(unsigned_decoded["vin"])) assert_equal(len(tx.vout), len(unsigned_decoded["vout"])) # assert re-encoding serialized = bytes_to_hex_str(tx.serialize()) assert_equal(serialized, raw) # Now sign and repeat tests signed_raw = self.nodes[0].signrawtransactionwithwallet(raw)["hex"] signed_decoded = self.nodes[0].decoderawtransaction(signed_raw) assert_equal(len(signed_decoded["vin"]), 1) assert(("txinwitness" in signed_decoded["vin"][0]) == segwit) # Cross-check python serialization tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(signed_raw))) assert_equal(tx.vin[0].prevout.hash, int("0x"+utxo["txid"], 0)) assert_equal(bytes_to_hex_str(tx.vin[0].scriptSig), signed_decoded["vin"][0]["scriptSig"]["hex"]) # test witness if segwit: wit_decoded = signed_decoded["vin"][0]["txinwitness"] for i in range(len(wit_decoded)): assert_equal(bytes_to_hex_str(tx.wit.vtxinwit[0].scriptWitness.stack[i]), wit_decoded[i]) # assert re-encoding serialized = bytes_to_hex_str(tx.serialize()) assert_equal(serialized, signed_raw) txid = self.nodes[0].sendrawtransaction(serialized) nodetx = self.nodes[0].getrawtransaction(txid, 1) assert_equal(nodetx["txid"], tx.rehash()) # cross-check wtxid report from node wtxid = bytes_to_hex_str(ser_uint256(tx.calc_sha256(True))[::-1]) assert_equal(nodetx["wtxid"], wtxid) assert_equal(nodetx["hash"], wtxid) # witness hash stuff assert_equal(nodetx["withash"], tx.calc_witness_hash()) return (txid, wtxid)
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
def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), CScript([b'a']))] tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), CScript([b'a' * 35]))] tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(ToHex(tx)) return tx test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee*COIN)) cur_time = int(time.time()) for i in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert tx2.hash in self.nodes[0].getrawmempool() test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee*COIN)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time+600) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) assert tx3.hash in self.nodes[0].getrawmempool() self.nodes[0].generate(1) assert tx3.hash not in self.nodes[0].getrawmempool() # One more test, this time using height locks tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True) assert tx4.hash in self.nodes[0].getrawmempool() # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True) assert tx5.hash not in self.nodes[0].getrawmempool() utxos = self.nodes[0].listunspent() tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"]*COIN) raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert tx4.hash not in self.nodes[0].getrawmempool() assert tx3.hash in self.nodes[0].getrawmempool() # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. tip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount()-1), 16) height = self.nodes[0].getblockcount() for i in range(2): block = create_block(tip, create_coinbase(height), cur_time) block.set_base_version(3) block.rehash() block.solve() tip = block.sha256 height += 1 self.nodes[0].submitblock(ToHex(block)) cur_time += 1 mempool = self.nodes[0].getrawmempool() assert tx3.hash not in mempool assert tx2.hash in mempool # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1)) self.nodes[0].generate(10)
def test_rbf(self): node = self.nodes[0] coin = self.coins.pop() inputs = [{ "txid": coin["txid"], "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER }] fee = Decimal('0.00125000') output = {node.get_deterministic_priv_key().address: 50 - fee} raw_replaceable_tx = node.createrawtransaction(inputs, output) signed_replaceable_tx = node.signrawtransactionwithkey( hexstring=raw_replaceable_tx, privkeys=self.privkeys) testres_replaceable = node.testmempoolaccept( [signed_replaceable_tx["hex"]]) replaceable_tx = CTransaction() replaceable_tx.deserialize( BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) assert_equal(testres_replaceable, [{ "txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), "allowed": True, "vsize": replaceable_tx.get_vsize(), "fees": { "base": fee } }]) # Replacement transaction is identical except has double the fee replacement_tx = CTransaction() replacement_tx.deserialize( BytesIO(hex_str_to_bytes(signed_replaceable_tx["hex"]))) replacement_tx.vout[0].nValue -= int(fee * COIN) # Doubled fee signed_replacement_tx = node.signrawtransactionwithkey( replacement_tx.serialize().hex(), self.privkeys) replacement_tx.deserialize( BytesIO(hex_str_to_bytes(signed_replacement_tx["hex"]))) self.log.info( "Test that transactions within a package cannot replace each other" ) testres_rbf_conflicting = node.testmempoolaccept( [signed_replaceable_tx["hex"], signed_replacement_tx["hex"]]) assert_equal(testres_rbf_conflicting, [{ "txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), "package-error": "conflict-in-package" }, { "txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "package-error": "conflict-in-package" }]) self.log.info( "Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF" ) node.sendrawtransaction(signed_replaceable_tx["hex"]) testres_rbf_single = node.testmempoolaccept( [signed_replacement_tx["hex"]]) # This transaction is a valid BIP125 replace-by-fee assert testres_rbf_single[0]["allowed"] testres_rbf_package = self.independent_txns_testres_blank + [ { "txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "allowed": False, "reject-reason": "bip125-replacement-disallowed" } ] self.assert_testres_equal( self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package)
def test_multiple_children(self): node = self.nodes[0] self.log.info( "Testmempoolaccept a package in which a transaction has two children within the package" ) first_coin = self.coins.pop() value = (first_coin["amount"] - Decimal("0.0002") ) / 2 # Deduct reasonable fee and make 2 outputs inputs = [{"txid": first_coin["txid"], "vout": 0}] outputs = [{self.address: value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE: value}] rawtx = node.createrawtransaction(inputs, outputs) parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) parent_tx = CTransaction() assert parent_signed["complete"] parent_tx.deserialize(BytesIO(hex_str_to_bytes(parent_signed["hex"]))) parent_txid = parent_tx.rehash() assert node.testmempoolaccept([parent_signed["hex"]])[0]["allowed"] parent_locking_script_a = parent_tx.vout[0].scriptPubKey.hex() child_value = value - Decimal("0.0001") # Child A (_, tx_child_a_hex, _, _) = self.chain_transaction(parent_txid, child_value, 0, parent_locking_script_a) assert not node.testmempoolaccept([tx_child_a_hex])[0]["allowed"] # Child B rawtx_b = node.createrawtransaction([{ "txid": parent_txid, "vout": 1 }], {self.address: child_value}) tx_child_b = CTransaction() tx_child_b.deserialize(BytesIO(hex_str_to_bytes(rawtx_b))) tx_child_b.wit.vtxinwit = [CTxInWitness()] tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] tx_child_b_hex = tx_child_b.serialize().hex() assert not node.testmempoolaccept([tx_child_b_hex])[0]["allowed"] self.log.info( "Testmempoolaccept with entire package, should work with children in either order" ) testres_multiple_ab = node.testmempoolaccept( rawtxs=[parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]) testres_multiple_ba = node.testmempoolaccept( rawtxs=[parent_signed["hex"], tx_child_b_hex, tx_child_a_hex]) assert all([ testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba ]) testres_single = [] # Test accept and then submit each one individually, which should be identical to package testaccept for rawtx in [parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]: testres = node.testmempoolaccept([rawtx]) testres_single.append(testres[0]) # Submit the transaction now so its child should have no problem validating node.sendrawtransaction(rawtx) assert_equal(testres_single, testres_multiple_ab)
def test_prioritised_transactions(self): # Ensure that fee deltas used via prioritisetransaction are # correctly used by replacement logic # 1. Check that feeperkb uses modified fees tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) tx1a = CTransaction() tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx1a_hex = txToHex(tx1a) tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, True) # Higher fee, but the actual fee per KB is much lower. tx1b = CTransaction() tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))] tx1b_hex = txToHex(tx1b) # Verify tx1b cannot replace tx1a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, True) # Use prioritisetransaction to set tx1a's fee to 0. self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN)) # Now tx1b should be able to replace tx1a tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, True) assert (tx1b_txid in self.nodes[0].getrawmempool()) # 2. Check that absolute fee checks use modified fee. tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN)) tx2a = CTransaction() tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2a.vout = [CTxOut(1 * COIN, CScript([b'a' * 35]))] tx2a_hex = txToHex(tx2a) self.nodes[0].sendrawtransaction(tx2a_hex, True) # Lower fee, but we'll prioritise it tx2b = CTransaction() tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] tx2b.vout = [CTxOut(int(1.01 * COIN), CScript([b'a' * 35]))] tx2b.rehash() tx2b_hex = txToHex(tx2b) # Verify tx2b cannot replace tx2a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, True) # Now prioritise tx2b to have a higher modified fee self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN)) # tx2b should now be accepted tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, True) assert (tx2b_txid in self.nodes[0].getrawmempool())
def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [ CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT) ] tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [ CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value) ] tx.vout = [ CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), DUMMY_P2WPKH_SCRIPT) ] tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(ToHex(tx)) return tx test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee * COIN)) cur_time = int(time.time()) for _ in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert tx2.hash in self.nodes[0].getrawmempool() test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee * COIN)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time + 600) # Save block template now to use for the reorg later tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) assert tx3.hash in self.nodes[0].getrawmempool() self.nodes[0].generate(1) assert tx3.hash not in self.nodes[0].getrawmempool() # One more test, this time using height locks tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True) assert tx4.hash in self.nodes[0].getrawmempool() # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True) assert tx5.hash not in self.nodes[0].getrawmempool() utxos = self.nodes[0].listunspent() tx5.vin.append( CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"] * COIN) raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert tx4.hash not in self.nodes[0].getrawmempool() assert tx3.hash in self.nodes[0].getrawmempool() # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. for i in range(2): block = create_block(tmpl=tmpl, ntime=cur_time) block.rehash() block.solve() tip = block.sha256 assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(ToHex(block))) tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) tmpl['previousblockhash'] = '%x' % tip tmpl['transactions'] = [] cur_time += 1 mempool = self.nodes[0].getrawmempool() assert tx3.hash not in mempool assert tx2.hash in mempool # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height + 1)) self.nodes[0].generate(10)
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0, target_weight=0): """Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed.""" utxo_to_spend = utxo_to_spend or self.get_utxo() assert fee_rate >= 0 assert fee >= 0 if self._mode in (MiniWalletMode.RAW_OP_TRUE, MiniWalletMode.ADDRESS_OP_TRUE): vsize = Decimal(104) # anyone-can-spend elif self._mode == MiniWalletMode.RAW_P2PK: vsize = Decimal( 168 ) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) else: assert False send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000)) assert send_value > 0 tx = CTransaction() tx.vin = [ CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence) ] tx.vout = [ CTxOut(int(COIN * send_value), bytearray(self._scriptPubKey)) ] tx.nLockTime = locktime if self._mode == MiniWalletMode.RAW_P2PK: self.sign_tx(tx) elif self._mode == MiniWalletMode.RAW_OP_TRUE: tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: tx.wit.vtxinwit = [CTxInWitness()] tx.wit.vtxinwit[0].scriptWitness.stack = [ CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key ] else: assert False assert_equal(tx.get_vsize(), vsize) if target_weight: self._bulk_tx(tx, target_weight) tx_hex = tx.serialize().hex() new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0) return { "txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo }
def run_test(self): self.description = "Covers the reorg with a zc public spend in vtx" self.init_test() DENOM_TO_USE = 10 # zc denomination INITAL_MINED_BLOCKS = 321 # First mined blocks (rewards collected to mint) MORE_MINED_BLOCKS = 105 # More blocks mined before spending zerocoins # 1) Starting mining blocks self.log.info("Mining %d blocks.." % INITAL_MINED_BLOCKS) self.node.generate(INITAL_MINED_BLOCKS) # 2) Mint 2 zerocoins self.node.mintzerocoin(DENOM_TO_USE) self.node.generate(1) self.node.mintzerocoin(DENOM_TO_USE) self.node.generate(1) # 3) Mine additional blocks and collect the mints self.log.info("Mining %d blocks.." % MORE_MINED_BLOCKS) self.node.generate(MORE_MINED_BLOCKS) self.log.info("Collecting mints...") mints = self.node.listmintedzerocoins(True, False) assert len(mints) == 2, "mints list has len %d (!= 2)" % len(mints) # 4) Get unspent coins and chain tip self.unspent = self.node.listunspent() block_count = self.node.getblockcount() pastBlockHash = self.node.getblockhash(block_count) self.log.info( "Block count: %d - Current best: %s..." % (self.node.getblockcount(), self.node.getbestblockhash()[:5])) pastBlock = CBlock() pastBlock.deserialize( BytesIO(hex_str_to_bytes(self.node.getblock(pastBlockHash, False)))) checkpoint = pastBlock.nAccumulatorCheckpoint # 5) get the raw zerocoin spend txes self.log.info("Getting the raw zerocoin public spends...") public_spend_A = self.node.createrawzerocoinpublicspend( mints[0].get("serial hash")) tx_A = CTransaction() tx_A.deserialize(BytesIO(hex_str_to_bytes(public_spend_A))) tx_A.rehash() public_spend_B = self.node.createrawzerocoinpublicspend( mints[1].get("serial hash")) tx_B = CTransaction() tx_B.deserialize(BytesIO(hex_str_to_bytes(public_spend_B))) tx_B.rehash() # Spending same coins to different recipients to get different txids my_addy = "yAVWM5urwaTyhiuFQHP2aP47rdZsLUG5PH" public_spend_A2 = self.node.createrawzerocoinpublicspend( mints[0].get("serial hash"), my_addy) tx_A2 = CTransaction() tx_A2.deserialize(BytesIO(hex_str_to_bytes(public_spend_A2))) tx_A2.rehash() public_spend_B2 = self.node.createrawzerocoinpublicspend( mints[1].get("serial hash"), my_addy) tx_B2 = CTransaction() tx_B2.deserialize(BytesIO(hex_str_to_bytes(public_spend_B2))) tx_B2.rehash() self.log.info("tx_A id: %s" % str(tx_A.hash)) self.log.info("tx_B id: %s" % str(tx_B.hash)) self.log.info("tx_A2 id: %s" % str(tx_A2.hash)) self.log.info("tx_B2 id: %s" % str(tx_B2.hash)) self.test_nodes[0].handle_connect() # 6) create block_A --> main chain self.log.info("") self.log.info("*** block_A ***") self.log.info("Creating block_A [%d] with public spend tx_A in it." % (block_count + 1)) block_A = self.new_block(block_count, pastBlock, checkpoint, tx_A) self.log.info("Hash of block_A: %s..." % block_A.hash[:5]) self.log.info("sending block_A...") var = self.node.submitblock(bytes_to_hex_str(block_A.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_A not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count + 1) assert_equal(self.node.getbestblockhash(), block_A.hash) self.log.info(" >> block_A connected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_A [%d]\n" % (block_count, block_count + 1)) # 7) create block_B --> forked chain self.log.info("*** block_B ***") self.log.info("Creating block_B [%d] with public spend tx_B in it." % (block_count + 1)) block_B = self.new_block(block_count, pastBlock, checkpoint, tx_B) self.log.info("Hash of block_B: %s..." % block_B.hash[:5]) self.log.info("sending block_B...") var = self.node.submitblock(bytes_to_hex_str(block_B.serialize())) self.log.info("result of block_B submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count + 1) assert_equal(self.node.getbestblockhash(), block_A.hash) # block_B is not added. Chain remains the same self.log.info(" >> block_B not connected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_A [%d]\n" % (block_count, block_count + 1)) # 8) Create new block block_C on the forked chain (block_B) block_count += 1 self.log.info("*** block_C ***") self.log.info( "Creating block_C [%d] on top of block_B triggering the reorg" % (block_count + 1)) block_C = self.new_block(block_count, block_B, checkpoint) self.log.info("Hash of block_C: %s..." % block_C.hash[:5]) self.log.info("sending block_C...") var = self.node.submitblock(bytes_to_hex_str(block_C.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_C not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count + 1) assert_equal(self.node.getbestblockhash(), block_C.hash) self.log.info( " >> block_A disconnected / block_B and block_C connected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d]\n" % (block_count - 1, block_count, block_count + 1)) # 7) Now create block_D which tries to spend same coin of tx_B again on the (new) main chain # (this block will be rejected) block_count += 1 self.log.info("*** block_D ***") self.log.info( "Creating block_D [%d] trying to double spend the coin of tx_B" % (block_count + 1)) block_D = self.new_block(block_count, block_C, checkpoint, tx_B2) self.log.info("Hash of block_D: %s..." % block_D.hash[:5]) self.log.info("sending block_D...") var = self.node.submitblock(bytes_to_hex_str(block_D.serialize())) self.log.info("result of block_D submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count) assert_equal(self.node.getbestblockhash(), block_C.hash) # block_D is not added. Chain remains the same self.log.info(" >> block_D rejected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d]\n" % (block_count - 2, block_count - 1, block_count)) # 8) Now create block_E which spends tx_A again on main chain # (this block will be accepted and connected since tx_A was spent on block_A now disconnected) self.log.info("*** block_E ***") self.log.info("Creating block_E [%d] trying spend tx_A on main chain" % (block_count + 1)) block_E = self.new_block(block_count, block_C, checkpoint, tx_A) self.log.info("Hash of block_E: %s..." % block_E.hash[:5]) self.log.info("sending block_E...") var = self.node.submitblock(bytes_to_hex_str(block_E.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_E not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count + 1) assert_equal(self.node.getbestblockhash(), block_E.hash) self.log.info(" >> block_E connected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d] --> block_E [%d]\n" % (block_count - 2, block_count - 1, block_count, block_count + 1)) # 9) Now create block_F which tries to double spend the coin in tx_A # # (this block will be rejected) block_count += 1 self.log.info("*** block_F ***") self.log.info( "Creating block_F [%d] trying to double spend the coin in tx_A" % (block_count + 1)) block_F = self.new_block(block_count, block_E, checkpoint, tx_A2) self.log.info("Hash of block_F: %s..." % block_F.hash[:5]) self.log.info("sending block_F...") var = self.node.submitblock(bytes_to_hex_str(block_F.serialize())) self.log.info("result of block_F submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count) assert_equal(self.node.getbestblockhash(), block_E.hash) self.log.info(" >> block_F rejected <<") self.log.info( "Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d] --> block_E [%d]\n" % (block_count - 3, block_count - 2, block_count - 1, block_count)) self.log.info("All good.")
def run_test(self): self.nodes[0].add_p2p_connection(P2PDataStore()) self.nodeaddress = self.nodes[0].getnewaddress() self.pubkey = self.nodes[0].getaddressinfo(self.nodeaddress)["pubkey"] self.log.info("Mining %d blocks", CHAIN_HEIGHT) self.coinbase_txids = [ self.nodes[0].getblock(b)['tx'][0] for b in self.nodes[0].generate( CHAIN_HEIGHT, self.signblockprivkeys) ] ## P2PKH transaction ######################## self.log.info("Test using a P2PKH transaction") spendtx = create_transaction(self.nodes[0], self.coinbase_txids[0], self.nodeaddress, amount=10) spendtx.rehash() copy_spendTx = CTransaction(spendtx) #cache hashes hash = spendtx.hash hashMalFix = spendtx.hashMalFix #malleate unDERify(spendtx) spendtx.rehash() # verify that hashMalFix remains the same even when signature is malleated and hash changes assert_not_equal(hash, spendtx.hash) assert_equal(hashMalFix, spendtx.hashMalFix) # verify that hash is spendtx.serialize() hash = encode(hash256(spendtx.serialize())[::-1], 'hex_codec').decode('ascii') assert_equal(hash, spendtx.hash) # verify that hashMalFix is spendtx.serialize(with_scriptsig=False) hashMalFix = encode( hash256(spendtx.serialize(with_scriptsig=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hashMalFix, spendtx.hashMalFix) assert_not_equal(hash, hashMalFix) #as this transaction does not have witness data the following is true assert_equal(spendtx.serialize(), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_not_equal( spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=False)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=True), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=False), spendtx.serialize_without_witness(with_scriptsig=False)) #Create block with only non-DER signature P2PKH transaction tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 1), block_time) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) # serialize with and without witness block remains the same assert_equal(block.serialize(with_witness=True), block.serialize()) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=False)) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=False, with_scriptsig=True)) self.log.info("Reject block with non-DER signature") self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), tip) wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock) with mininode_lock: assert_equal(self.nodes[0].p2p.last_message["reject"].code, REJECT_INVALID) assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256) assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'block-validation-failed') self.log.info("Accept block with DER signature") #recreate block with DER sig transaction block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 1), block_time) block.vtx.append(copy_spendTx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), block.hash) ## P2SH transaction ######################## self.log.info("Test using P2SH transaction ") REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP]) P2SH_1 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_1), OP_EQUAL]) tx = CTransaction() tx.vin.append( CTxIn(COutPoint(int(self.coinbase_txids[1], 16), 0), b"", 0xffffffff)) tx.vout.append(CTxOut(10, P2SH_1)) tx.rehash() spendtx_raw = self.nodes[0].signrawtransactionwithwallet( ToHex(tx), [], "ALL", self.options.scheme)["hex"] spendtx = FromHex(spendtx, spendtx_raw) spendtx.rehash() copy_spendTx = CTransaction(spendtx) #cache hashes hash = spendtx.hash hashMalFix = spendtx.hashMalFix #malleate spendtxcopy = spendtx unDERify(spendtxcopy) spendtxcopy.rehash() # verify that hashMalFix remains the same even when signature is malleated and hash changes assert_not_equal(hash, spendtxcopy.hash) assert_equal(hashMalFix, spendtxcopy.hashMalFix) # verify that hash is spendtx.serialize() hash = encode( hash256(spendtx.serialize(with_witness=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hash, spendtx.hash) # verify that hashMalFix is spendtx.serialize(with_scriptsig=False) hashMalFix = encode( hash256(spendtx.serialize(with_witness=False, with_scriptsig=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hashMalFix, spendtx.hashMalFix) assert_not_equal(hash, hashMalFix) #as this transaction does not have witness data the following is true assert_equal(spendtx.serialize(), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_not_equal( spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=False)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=True), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=False), spendtx.serialize_without_witness(with_scriptsig=False)) #Create block with only non-DER signature P2SH transaction tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 2), block_time) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) # serialize with and without witness block remains the same assert_equal(block.serialize(with_witness=True), block.serialize()) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=False)) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=True, with_scriptsig=True)) self.log.info("Reject block with non-DER signature") self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), tip) wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock) with mininode_lock: assert_equal(self.nodes[0].p2p.last_message["reject"].code, REJECT_INVALID) assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256) assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'block-validation-failed') self.log.info("Accept block with DER signature") #recreate block with DER sig transaction block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 2), block_time) block.vtx.append(copy_spendTx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), block.hash) ## redeem previous P2SH ######################### self.log.info("Test using P2SH redeem transaction ") tx = CTransaction() tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) tx.vin.append(CTxIn(COutPoint(block.vtx[1].malfixsha256, 0), b'')) (sighash, err) = SignatureHash(REDEEM_SCRIPT_1, tx, 1, SIGHASH_ALL) signKey = CECKey() signKey.set_secretbytes(b"horsebattery") sig = signKey.sign(sighash) + bytes(bytearray([SIGHASH_ALL])) scriptSig = CScript([sig, REDEEM_SCRIPT_1]) tx.vin[0].scriptSig = scriptSig tx.rehash() spendtx_raw = self.nodes[0].signrawtransactionwithwallet( ToHex(tx), [], "ALL", self.options.scheme)["hex"] spendtx = FromHex(spendtx, spendtx_raw) spendtx.rehash() #cache hashes hash = spendtx.hash hashMalFix = spendtx.hashMalFix #malleate spendtxcopy = spendtx unDERify(spendtxcopy) spendtxcopy.rehash() # verify that hashMalFix remains the same even when signature is malleated and hash changes assert_not_equal(hash, spendtxcopy.hash) assert_equal(hashMalFix, spendtxcopy.hashMalFix) # verify that hash is spendtx.serialize() hash = encode( hash256(spendtx.serialize(with_witness=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hash, spendtx.hash) # verify that hashMalFix is spendtx.serialize(with_scriptsig=False) hashMalFix = encode( hash256(spendtx.serialize(with_witness=False, with_scriptsig=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hashMalFix, spendtx.hashMalFix) assert_not_equal(hash, hashMalFix) #as this transaction does not have witness data the following is true assert_equal(spendtx.serialize(), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_not_equal( spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=False)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=True), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=False), spendtx.serialize_without_witness(with_scriptsig=False)) #Create block with only non-DER signature P2SH redeem transaction tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 3), block_time) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) # serialize with and without witness block remains the same assert_equal(block.serialize(with_witness=True), block.serialize()) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=False)) assert_equal(block.serialize(with_witness=True), block.serialize(with_witness=True, with_scriptsig=True)) self.log.info("Accept block with P2SH redeem transaction") self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), block.hash) ## p2sh_p2wpkh transaction ############################## self.log.info("Test using p2sh_p2wpkh transaction ") spendtxStr = create_witness_tx(self.nodes[0], True, getInput(self.coinbase_txids[4]), self.pubkey, amount=1.0) #get CTRansaction object from above hex spendtx = CTransaction() spendtx.deserialize(BytesIO(hex_str_to_bytes(spendtxStr))) spendtx.rehash() #cache hashes spendtx.rehash() hash = spendtx.hash hashMalFix = spendtx.hashMalFix withash = spendtx.calc_sha256(True) # malleate unDERify(spendtx) spendtx.rehash() withash2 = spendtx.calc_sha256(True) # verify that hashMalFix remains the same even when signature is malleated and hash changes assert_equal(withash, withash2) assert_equal(hash, spendtx.hash) assert_equal(hashMalFix, spendtx.hashMalFix) # verify that hash is spendtx.serialize() hash = encode(hash256(spendtx.serialize())[::-1], 'hex_codec').decode('ascii') assert_equal(hash, spendtx.hash) # verify that hashMalFix is spendtx.serialize(with_scriptsig=False) hashMalFix = encode( hash256(spendtx.serialize(with_scriptsig=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hashMalFix, spendtx.hashMalFix) assert_not_equal(hash, hashMalFix) #as this transaction does not have witness data the following is true assert_equal(spendtx.serialize(), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_not_equal( spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=False)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=True), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=False), spendtx.serialize_without_witness(with_scriptsig=False)) #Create block with only non-DER signature p2sh_p2wpkh transaction spendtxStr = self.nodes[0].signrawtransactionwithwallet( spendtxStr, [], "ALL", self.options.scheme) assert ("errors" not in spendtxStr or len(["errors"]) == 0) spendtxStr = spendtxStr["hex"] spendtx = CTransaction() spendtx.deserialize(BytesIO(hex_str_to_bytes(spendtxStr))) spendtx.rehash() tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 4), block_time) block.vtx.append(spendtx) add_witness_commitment(block) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) # serialize with and without witness assert_equal(block.serialize(with_witness=False), block.serialize()) assert_not_equal(block.serialize(with_witness=True), block.serialize(with_witness=False)) assert_not_equal( block.serialize(with_witness=True), block.serialize(with_witness=False, with_scriptsig=True)) self.log.info( "Reject block with p2sh_p2wpkh transaction and witness commitment") assert_raises_rpc_error( -22, "Block does not start with a coinbase", self.nodes[0].submitblock, bytes_to_hex_str(block.serialize(with_witness=True))) assert_equal(self.nodes[0].getbestblockhash(), tip) block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 4), block_time) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) self.log.info("Accept block with p2sh_p2wpkh transaction") self.nodes[0].submitblock( bytes_to_hex_str(block.serialize(with_witness=True))) assert_equal(self.nodes[0].getbestblockhash(), block.hash) ## p2sh_p2wsh transaction ############################## self.log.info("Test using p2sh_p2wsh transaction") spendtxStr = create_witness_tx(self.nodes[0], True, getInput(self.coinbase_txids[5]), self.pubkey, amount=1.0) #get CTRansaction object from above hex spendtx = CTransaction() spendtx.deserialize(BytesIO(hex_str_to_bytes(spendtxStr))) spendtx.rehash() #cache hashes spendtx.rehash() hash = spendtx.hash hashMalFix = spendtx.hashMalFix withash = spendtx.calc_sha256(True) # malleate unDERify(spendtx) spendtx.rehash() withash2 = spendtx.calc_sha256(True) # verify that hashMalFix remains the same even when signature is malleated and hash changes assert_equal(withash, withash2) assert_equal(hash, spendtx.hash) assert_equal(hashMalFix, spendtx.hashMalFix) # verify that hash is spendtx.serialize() hash = encode(hash256(spendtx.serialize())[::-1], 'hex_codec').decode('ascii') assert_equal(hash, spendtx.hash) # verify that hashMalFix is spendtx.serialize(with_scriptsig=False) hashMalFix = encode( hash256(spendtx.serialize(with_scriptsig=False))[::-1], 'hex_codec').decode('ascii') assert_equal(hashMalFix, spendtx.hashMalFix) assert_not_equal(hash, hashMalFix) #as this transaction does not have witness data the following is true assert_equal(spendtx.serialize(), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=True)) assert_not_equal( spendtx.serialize(with_witness=False), spendtx.serialize(with_witness=True, with_scriptsig=False)) assert_equal(spendtx.serialize(with_witness=False), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=True), spendtx.serialize_without_witness(with_scriptsig=True)) assert_equal(spendtx.serialize_with_witness(with_scriptsig=False), spendtx.serialize_without_witness(with_scriptsig=False)) #Create block with only non-DER signature p2sh_p2wsh transaction spendtxStr = self.nodes[0].signrawtransactionwithwallet( spendtxStr, [], "ALL", self.options.scheme) assert ("errors" not in spendtxStr or len(["errors"]) == 0) spendtxStr = spendtxStr["hex"] spendtx = CTransaction() spendtx.deserialize(BytesIO(hex_str_to_bytes(spendtxStr))) spendtx.rehash() tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 5), block_time) block.vtx.append(spendtx) add_witness_commitment(block) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) # serialize with and without witness assert_equal(block.serialize(with_witness=False), block.serialize()) assert_not_equal(block.serialize(with_witness=True), block.serialize(with_witness=False)) assert_not_equal( block.serialize(with_witness=True), block.serialize(with_witness=False, with_scriptsig=True)) self.log.info( "Reject block with p2sh_p2wsh transaction and witness commitment") assert_raises_rpc_error( -22, "Block does not start with a coinbase", self.nodes[0].submitblock, bytes_to_hex_str(block.serialize(with_witness=True))) assert_equal(self.nodes[0].getbestblockhash(), tip) block = create_block(int(tip, 16), create_coinbase(CHAIN_HEIGHT + 5), block_time) block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.hashImMerkleRoot = block.calc_immutable_merkle_root() block.rehash() block.solve(self.signblockprivkeys) self.log.info("Accept block with p2sh_p2wsh transaction") self.nodes[0].submitblock( bytes_to_hex_str(block.serialize(with_witness=True))) assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def run_test(self): node = self.nodes[0] self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], ))['hex'] txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, allowhighfees=True) node.generate(1) self.mempool_size = 0 self.check_mempool_result( result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known'}], rawtxs=[raw_tx_in_block], ) self.log.info('A transaction not in the mempool') fee = 0.00000700 raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later outputs=[{node.getnewaddress(): 0.3 - fee}], ))['hex'] tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True}], rawtxs=[raw_tx_0], ) self.log.info('A final transaction not in the mempool') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL outputs=[{node.getnewaddress(): 0.025}], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True}], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, ) node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True) self.mempool_size += 1 self.log.info('A transaction in the mempool') node.sendrawtransaction(hexstring=raw_tx_0) self.mempool_size += 1 self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool'}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that replaces a mempool transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF raw_tx_0 = node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that conflicts with an unconfirmed tx') # Send the transaction that replaces the mempool transaction and opts out of replaceability node.sendrawtransaction(hexstring=bytes_to_hex_str(tx.serialize()), allowhighfees=True) # take original raw_tx_0 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, ) self.log.info('A transaction with missing inputs, that never existed') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with missing inputs, that existed once in the past') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend raw_tx_1 = node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex'] txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, allowhighfees=True) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[ {'txid': txid_0, 'vout': 0}, {'txid': txid_1, 'vout': 0}, ], outputs=[{node.getnewaddress(): 0.1}] ))['hex'] txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, allowhighfees=True) node.generate(1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_0], ) self.check_mempool_result( result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_1], ) self.log.info('Create a signed "reference" tx for later use') raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': txid_spend_both, 'vout': 0}], outputs=[{node.getnewaddress(): 0.05}], ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with no outputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [] # Skip re-signing the transaction for context independent checks from now on # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex']))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-empty'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A really large transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with negative output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue *= -1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-negative'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with too large output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue = 21000000 * COIN + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-toolarge'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with too large sum of output values') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = 21000000 * COIN self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-txouttotal-toolarge'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with duplicate inputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-inputs-duplicate'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A coinbase transaction') # Pick the input of the first tx we signed, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: coinbase'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('Some nonstandard transactions') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.nVersion = 3 # A version currently non-standard self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: version'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0] = output_p2sh_burn tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: multi-op-return'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A timelocked transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-final'}], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, )
def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # As the fees are calculated prior to the transaction being signed, # there is some uncertainty that calculate fee provides the correct # minimal fee. Since regtest coins are free, let's go ahead and # increase the fee by an order of magnitude to ensure this test # passes. fee_multiplier = 10 # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [CTxOut(int(0), CScript([b'a']))] tx2.vout[0].nValue = tx1.vout[0].nValue - \ fee_multiplier * self.nodes[0].calculate_fee(tx2) tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [ CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value) ] tx.vout = [ CTxOut( int(orig_tx.vout[0].nValue - fee_multiplier * node.calculate_fee(tx)), CScript([b'a'])) ] pad_tx(tx) tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the # mempool node.sendrawtransaction(ToHex(tx)) return tx test_nonzero_locks(tx2, self.nodes[0], use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=-fee_multiplier * self.nodes[0].calculate_fee(tx2)) cur_time = int(time.time()) for i in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert tx2.hash in self.nodes[0].getrawmempool() test_nonzero_locks(tx2, self.nodes[0], use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=fee_multiplier * self.nodes[0].calculate_fee(tx2)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks(tx2, self.nodes[0], use_height_lock=False) assert tx3.hash in self.nodes[0].getrawmempool() self.nodes[0].generate(1) assert tx3.hash not in self.nodes[0].getrawmempool() # One more test, this time using height locks tx4 = test_nonzero_locks(tx3, self.nodes[0], use_height_lock=True) assert tx4.hash in self.nodes[0].getrawmempool() # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks(tx4, self.nodes[0], use_height_lock=True) assert tx5.hash not in self.nodes[0].getrawmempool() utxos = self.nodes[0].listunspent() tx5.vin.append( CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"] * COIN) raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert tx4.hash not in self.nodes[0].getrawmempool() assert tx3.hash in self.nodes[0].getrawmempool() # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. tip = int( self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 1), 16) height = self.nodes[0].getblockcount() for i in range(2): block = create_block(tip, create_coinbase(height), cur_time) block.nVersion = 3 block.rehash() block.solve() tip = block.sha256 height += 1 self.nodes[0].submitblock(ToHex(block)) cur_time += 1 mempool = self.nodes[0].getrawmempool() assert tx3.hash not in mempool assert tx2.hash in mempool # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height + 1)) self.nodes[0].generate(10)
def run_test(self): # Add p2p connection to node0 node0 = self.nodes[0] # convenience reference to the node node0.add_p2p_connection(P2PDataStore()) foo_addr = node0.getnewaddress("foo") n0_addr = node0.getnewaddress() n0_pubk = hex_str_to_bytes(node0.getaddressinfo(n0_addr)["pubkey"]) node1 = self.nodes[1] node1.add_p2p_connection(P2PDataStore()) n1_addr = node1.getnewaddress() # Generate the first block and let node0 get 50 BTC from the coinbase transaction as the initial funding best_block = node0.getblock(node0.getbestblockhash()) tip = int(node0.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 self.log.info("Create a new block using the coinbase of the node0.") block1 = create_block(tip, create_coinbase(height, n0_pubk), block_time) block1.solve() node0.p2p.send_blocks_and_test([block1], node0, success=True, timeout=60) self.log.info("Mature the block, make the mined BTC usable.") node0.generatetoaddress(100, node0.get_deterministic_priv_key().address ) # generate 100 more blocks. assert (node0.getbalance() == 50) # node0 get the reward as a miner # create a transaction tx1_raw = node0.createrawtransaction( inputs=[{ "txid": block1.vtx[0].hash, "vout": 0 }], outputs={foo_addr: 49.99} # 0.01 btc for miner fee ) tx1_sig = node0.signrawtransactionwithwallet(tx1_raw) assert_equal(tx1_sig["complete"], True) tx1_hex = tx1_sig["hex"] tct1 = CTransaction() tct1.deserialize(BytesIO(hex_str_to_bytes(tx1_hex))) tct1.rehash() node0.sendrawtransaction(tx1_hex) # craft a new transaction, which double spend the tx1 tx2_raw = node0.createrawtransaction(inputs=[{ "txid": tct1.hash, "vout": 0 }, { "txid": tct1.hash, "vout": 0 }], outputs={n1_addr: 49.99 * 2}) tx2_sig = node0.signrawtransactionwithwallet(tx2_raw) assert_equal(tx2_sig["complete"], True) tx2_hex = tx2_sig["hex"] tct2 = CTransaction() tct2.deserialize(BytesIO(hex_str_to_bytes(tx2_hex))) best_block = node0.getblock(node0.getbestblockhash()) tip = int(node0.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 block2 = create_block(tip, create_coinbase(height), block_time) block2.vtx.extend([tct1, tct2]) block2.hashMerkleRoot = block2.calc_merkle_root() add_witness_commitment(block2) block2.rehash() block2.solve() # node1.p2p.send_blocks_and_test([block2], node1, success=True, timeout=60) test_witness_block(node1, block2, True) # check the balances assert (node0.getbalance() == 0) assert (node1.getbalance() == 49.99 * 2) self.log.info("Successfully double spend the 50 BTCs.")
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): self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} self.coinbase_key = ECKey() self.coinbase_key.generate() self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() self.tip = None self.blocks = {} self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 self.spendable_outputs = [] # Create a new block b0 = self.next_block(0) self.save_spendable_output() self.sync_blocks([b0]) # Allow the block to mature blocks = [] for i in range(129): blocks.append(self.next_block(5000 + i)) self.save_spendable_output() self.sync_blocks(blocks) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(33): out.append(self.get_spendable_output()) # Start by building a block on top. # setup -> b13 (0) b13 = self.next_block(13, spend=out[0]) self.save_spendable_output() self.sync_blocks([b13]) # Add a block with MAX_BLOCK_SIGOPS_PER_MB and one with one more sigop # setup -> b13 (0) -> b15 (5) -> b16 (6) self.log.info("Accept a block with lots of checksigs") lots_of_checksigs = CScript( [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB - 1)) self.move_tip(13) b15 = self.next_block(15, spend=out[5], script=lots_of_checksigs) self.save_spendable_output() self.sync_blocks([b15], True) self.log.info("Reject a block with too many checksigs") too_many_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB)) b16 = self.next_block(16, spend=out[6], script=too_many_checksigs) self.sync_blocks([b16], success=False, reject_reason='bad-blk-sigops', reconnect=True) self.move_tip(15) # ... skipped feature_block tests ... # b31 - b35 - check sigops of OP_CHECKMULTISIG / OP_CHECKMULTISIGVERIFY / OP_CHECKSIGVERIFY # # setup -> ... b15 (5) -> b31 (8) -> b33 (9) -> b35 (10) # \-> b36 (11) # \-> b34 (10) # \-> b32 (9) # # MULTISIG: each op code counts as 20 sigops. To create the edge case, # pack another 19 sigops at the end. self.log.info( "Accept a block with the max number of OP_CHECKMULTISIG sigops") lots_of_multisigs = CScript( [OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS_PER_MB - 1) // 20) + [OP_CHECKSIG] * 19) b31 = self.next_block(31, spend=out[8], script=lots_of_multisigs) assert_equal(get_legacy_sigopcount_block(b31), MAX_BLOCK_SIGOPS_PER_MB) self.sync_blocks([b31], True) self.save_spendable_output() # this goes over the limit because the coinbase has one sigop self.log.info("Reject a block with too many OP_CHECKMULTISIG sigops") too_many_multisigs = CScript( [OP_CHECKMULTISIG] * (MAX_BLOCK_SIGOPS_PER_MB // 20)) b32 = self.next_block(32, spend=out[9], script=too_many_multisigs) assert_equal(get_legacy_sigopcount_block( b32), MAX_BLOCK_SIGOPS_PER_MB + 1) self.sync_blocks([b32], success=False, reject_reason='bad-blk-sigops', reconnect=True) # CHECKMULTISIGVERIFY self.log.info( "Accept a block with the max number of OP_CHECKMULTISIGVERIFY sigops") self.move_tip(31) lots_of_multisigs = CScript( [OP_CHECKMULTISIGVERIFY] * ((MAX_BLOCK_SIGOPS_PER_MB - 1) // 20) + [OP_CHECKSIG] * 19) b33 = self.next_block(33, spend=out[9], script=lots_of_multisigs) self.sync_blocks([b33], True) self.save_spendable_output() self.log.info( "Reject a block with too many OP_CHECKMULTISIGVERIFY sigops") too_many_multisigs = CScript( [OP_CHECKMULTISIGVERIFY] * (MAX_BLOCK_SIGOPS_PER_MB // 20)) b34 = self.next_block(34, spend=out[10], script=too_many_multisigs) self.sync_blocks([b34], success=False, reject_reason='bad-blk-sigops', reconnect=True) # CHECKSIGVERIFY self.log.info( "Accept a block with the max number of OP_CHECKSIGVERIFY sigops") self.move_tip(33) lots_of_checksigs = CScript( [OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS_PER_MB - 1)) b35 = self.next_block(35, spend=out[10], script=lots_of_checksigs) self.sync_blocks([b35], True) self.save_spendable_output() self.log.info("Reject a block with too many OP_CHECKSIGVERIFY sigops") too_many_checksigs = CScript( [OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS_PER_MB)) b36 = self.next_block(36, spend=out[11], script=too_many_checksigs) self.sync_blocks([b36], success=False, reject_reason='bad-blk-sigops', reconnect=True) # ... skipped feature_block tests ... # Check P2SH SigOp counting # # # ... -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b41 (12) # \-> b40 (12) # # b39 - create some P2SH outputs that will require 6 sigops to spend: # # redeem_script = COINBASE_PUBKEY, (OP_2DUP+OP_CHECKSIGVERIFY) * 5, OP_CHECKSIG # p2sh_script = OP_HASH160, ripemd160(sha256(script)), OP_EQUAL # self.log.info("Check P2SH SIGOPS are correctly counted") self.move_tip(35) b39 = self.next_block(39) b39_outputs = 0 b39_sigops_per_output = 6 # 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 transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE # This must be signed because it is spending a coinbase spend = out[11] tx = self.create_tx(spend, 0, 1, p2sh_script) tx.vout.append(CTxOut(spend.vout[0].nValue - 1, CScript([OP_TRUE]))) self.sign_tx(tx, spend) tx.rehash() b39 = self.update_block(39, [tx]) b39_outputs += 1 # Until block is full, add tx's with 1 satoshi to p2sh_script, the rest # to OP_TRUE tx_new = None tx_last = tx tx_last_n = len(tx.vout) - 1 total_size = len(b39.serialize()) while(total_size < LEGACY_MAX_BLOCK_SIZE): tx_new = self.create_tx(tx_last, tx_last_n, 1, p2sh_script) tx_new.vout.append( CTxOut(tx_last.vout[tx_last_n].nValue - 1, CScript([OP_TRUE]))) tx_new.rehash() total_size += len(tx_new.serialize()) if total_size >= LEGACY_MAX_BLOCK_SIZE: break b39.vtx.append(tx_new) # add tx to block tx_last = tx_new tx_last_n = len(tx_new.vout) - 1 b39_outputs += 1 b39 = self.update_block(39, []) self.sync_blocks([b39], True) self.save_spendable_output() # Test sigops in P2SH redeem scripts # # b40 creates 3333 tx's spending the 6-sigop P2SH outputs from b39 for a total of 19998 sigops. # The first tx has one sigop and then at the end we add 2 more to put us just over the max. # # b41 does the same, less one, so it has the maximum sigops permitted. # self.log.info("Reject a block with too many P2SH sigops") self.move_tip(39) b40 = self.next_block(40, spend=out[12]) sigops = get_legacy_sigopcount_block(b40) numTxs = (MAX_BLOCK_SIGOPS_PER_MB - sigops) // b39_sigops_per_output assert_equal(numTxs <= b39_outputs, True) lastOutpoint = COutPoint(b40.vtx[1].sha256, 0) lastAmount = b40.vtx[1].vout[0].nValue new_txs = [] for i in range(1, numTxs + 1): tx = CTransaction() tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) tx.vin.append(CTxIn(lastOutpoint, b'')) # second input is corresponding P2SH output from b39 tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b'')) # Note: must pass the redeem_script (not p2sh_script) to the # signature hash function sighash = SignatureHashForkId( redeem_script, tx, 1, SIGHASH_ALL | SIGHASH_FORKID, lastAmount) sig = self.coinbase_key.sign_ecdsa( sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) scriptSig = CScript([sig, redeem_script]) tx.vin[1].scriptSig = scriptSig pad_tx(tx) tx.rehash() new_txs.append(tx) lastOutpoint = COutPoint(tx.sha256, 0) lastAmount = tx.vout[0].nValue b40_sigops_to_fill = MAX_BLOCK_SIGOPS_PER_MB - \ (numTxs * b39_sigops_per_output + sigops) + 1 tx = CTransaction() tx.vin.append(CTxIn(lastOutpoint, b'')) tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b40_sigops_to_fill))) pad_tx(tx) tx.rehash() new_txs.append(tx) self.update_block(40, new_txs) self.sync_blocks([b40], success=False, reject_reason='bad-blk-sigops', reconnect=True) # same as b40, but one less sigop self.log.info("Accept a block with the max number of P2SH sigops") self.move_tip(39) b41 = self.next_block(41, spend=None) self.update_block(41, [b40tx for b40tx in b40.vtx[1:] if b40tx != tx]) b41_sigops_to_fill = b40_sigops_to_fill - 1 tx = CTransaction() tx.vin.append(CTxIn(lastOutpoint, b'')) tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b41_sigops_to_fill))) pad_tx(tx) self.update_block(41, [tx]) self.sync_blocks([b41], True) # ... skipped feature_block tests ... b72 = self.next_block(72) self.save_spendable_output() self.sync_blocks([b72]) # Test some invalid scripts and MAX_BLOCK_SIGOPS_PER_MB # # ..... -> b72 # \-> b** (22) # # b73 - tx with excessive sigops that are placed after an excessively large script element. # The purpose of the test is to make sure those sigops are counted. # # script is a bytearray of size 20,526 # # bytearray[0-19,998] : OP_CHECKSIG # bytearray[19,999] : OP_PUSHDATA4 # bytearray[20,000-20,003]: 521 (max_script_element_size+1, in little-endian format) # bytearray[20,004-20,525]: unread data (script_element) # bytearray[20,526] : OP_CHECKSIG (this puts us over the limit) self.log.info( "Reject a block containing too many sigops after a large script element") self.move_tip(72) b73 = self.next_block(73) size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1 a = bytearray([OP_CHECKSIG] * size) a[MAX_BLOCK_SIGOPS_PER_MB - 1] = int("4e", 16) # OP_PUSHDATA4 element_size = MAX_SCRIPT_ELEMENT_SIZE + 1 a[MAX_BLOCK_SIGOPS_PER_MB] = element_size % 256 a[MAX_BLOCK_SIGOPS_PER_MB + 1] = element_size // 256 a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0 a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0 tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) b73 = self.update_block(73, [tx]) assert_equal(get_legacy_sigopcount_block( b73), MAX_BLOCK_SIGOPS_PER_MB + 1) self.sync_blocks([b73], success=False, reject_reason='bad-blk-sigops', reconnect=True) # b74/75 - if we push an invalid script element, all prevous sigops are counted, # but sigops after the element are not counted. # # The invalid script element is that the push_data indicates that # there will be a large amount of data (0xffffff bytes), but we only # provide a much smaller number. These bytes are CHECKSIGS so they would # cause b75 to fail for excessive sigops, if those bytes were counted. # # b74 fails because we put MAX_BLOCK_SIGOPS_PER_MB+1 before the element # b75 succeeds because we put MAX_BLOCK_SIGOPS_PER_MB before the # element self.log.info( "Check sigops are counted correctly after an invalid script element") self.move_tip(72) b74 = self.next_block(74) size = MAX_BLOCK_SIGOPS_PER_MB - 1 + \ MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561 a = bytearray([OP_CHECKSIG] * size) a[MAX_BLOCK_SIGOPS_PER_MB] = 0x4e a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xfe a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff a[MAX_BLOCK_SIGOPS_PER_MB + 4] = 0xff tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) b74 = self.update_block(74, [tx]) self.sync_blocks([b74], success=False, reject_reason='bad-blk-sigops', reconnect=True) self.move_tip(72) b75 = self.next_block(75) size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 a = bytearray([OP_CHECKSIG] * size) a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e a[MAX_BLOCK_SIGOPS_PER_MB] = 0xff a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xff a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) b75 = self.update_block(75, [tx]) self.sync_blocks([b75], True) self.save_spendable_output() # Check that if we push an element filled with CHECKSIGs, they are not # counted self.move_tip(75) b76 = self.next_block(76) size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 a = bytearray([OP_CHECKSIG] * size) # PUSHDATA4, but leave the following bytes as just checksigs a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e tx = self.create_and_sign_transaction(out[23], 1, CScript(a)) b76 = self.update_block(76, [tx]) self.sync_blocks([b76], True) self.save_spendable_output()
def run_test(self): node = self.nodes[0] self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}, {"fee": coin["amount"] - Decimal('49.3')}], ))['hex'] txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0) node.generate(1) self.mempool_size = 0 self.check_mempool_result( result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}], rawtxs=[raw_tx_in_block], ) self.log.info('A transaction not in the mempool') fee = Decimal('0.000007') raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later outputs=[{node.getnewaddress(): Decimal('0.3') - fee}, {"fee": fee}], ))['hex'] tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], rawtxs=[raw_tx_0], ) self.log.info('A final transaction not in the mempool') coin = coins.pop() # Pick a random coin(base) to spend output_amount = Decimal('0.025') raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL outputs=[{node.getnewaddress(): output_amount}, {"fee": coin["amount"] - Decimal(str(output_amount))}], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) fee_expected = coin['amount'] - output_amount self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0) self.mempool_size += 1 self.log.info('A transaction in the mempool') node.sendrawtransaction(hexstring=raw_tx_0) self.mempool_size += 1 self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'txn-already-in-mempool'}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that replaces a mempool transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() - int(fee * COIN)) # Double the fee txid_0_out = tx.vout[0].nValue.getAmount() tx.vout[1].nValue.setToAmount(tx.vout[1].nValue.getAmount() + int(fee * COIN)) tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that conflicts with an unconfirmed tx') # Send the transaction that replaces the mempool transaction and opts out of replaceability node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) # take original raw_tx_0 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() - int(4 * fee * COIN)) # Set more fee tx.vout[1].nValue.setToAmount(tx.vout[1].nValue.getAmount() + int(4 * fee * COIN)) # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) self.log.info('A transaction with missing inputs, that never existed') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with missing inputs, that existed once in the past') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend tx.vout[1].nValue.setToAmount(49*COIN - tx.vout[0].nValue.getAmount()) # fee txid_1_out = tx.vout[0].nValue.getAmount() raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[ {'txid': txid_0, 'vout': 0}, {'txid': txid_1, 'vout': 0}, ], outputs=[{node.getnewaddress(): 0.1}, {"fee": Decimal(txid_0_out + txid_1_out)/Decimal(COIN) - Decimal('0.1')}] ))['hex'] txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0) node.generate(1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_0], ) self.check_mempool_result( result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_1], ) self.log.info('Create a signed "reference" tx for later use') raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': txid_spend_both, 'vout': 0}], outputs=[{node.getnewaddress(): 0.05}, {"fee": Decimal('0.1') - Decimal('0.05')}], ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) self.log.info('A transaction with no outputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [] # Skip re-signing the transaction for context independent checks from now on # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A really large transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-oversize'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with negative output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() * -1) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-negative'}], rawtxs=[tx.serialize().hex()], ) # The following two validations prevent overflow of the output amounts (see CVE-2010-5139). self.log.info('A transaction with too large output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue = CTxOutValue(MAX_MONEY + 1) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with too large sum of output values') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = CTxOutValue(MAX_MONEY) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-txouttotal-toolarge'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with duplicate inputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-inputs-duplicate'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A coinbase transaction') # Pick the input of the first tx we signed, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'coinbase'}], rawtxs=[tx.serialize().hex()], ) self.log.info('Some nonstandard transactions') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.nVersion = 3 # A version currently non-standard self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'version'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptpubkey'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) key = ECKey() key.generate() pubkey = key.get_pubkey().get_bytes() tx.vout[0].scriptPubKey = CScript([OP_2, pubkey, pubkey, pubkey, OP_3, OP_CHECKMULTISIG]) # Some bare multisig script (2-of-3) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bare-multisig'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-not-pushonly'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].scriptSig = CScript([b'a' * 1648]) # Some too large scriptSig (>1650 bytes) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-size'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0] = output_p2sh_burn tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() - 1) # Make output smaller, such that it is dust for our policy self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}], rawtxs=[tx.serialize().hex()], ) # Elements: We allow multi op_return outputs by default. This still fails because relay fee isn't met tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'min relay fee not met'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A timelocked transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-final'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}], rawtxs=[tx.serialize().hex()], maxfeerate=0, )
def test_independent(self): self.log.info("Test multiple independent transactions in a package") node = self.nodes[0] # For independent transactions, order doesn't matter. self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres) self.log.info( "Test an otherwise valid package with an extra garbage tx appended" ) garbage_tx = node.createrawtransaction([{ "txid": "00" * 32, "vout": 5 }], {self.address: 1}) tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(garbage_tx))) # Only the txid and wtxids are returned because validation is incomplete for the independent txns. # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package, # it terminates immediately to avoid unnecessary, expensive signature verification. package_bad = self.independent_txns_hex + [garbage_tx] testres_bad = self.independent_txns_testres_blank + [ { "txid": tx.rehash(), "wtxid": tx.getwtxid(), "allowed": False, "reject-reason": "missing-inputs" } ] self.assert_testres_equal(package_bad, testres_bad) self.log.info( "Check testmempoolaccept tells us when some transactions completed validation successfully" ) coin = self.coins.pop() tx_bad_sig_hex = node.createrawtransaction( [{ "txid": coin["txid"], "vout": 0 }], {self.address: coin["amount"] - Decimal("0.0001")}) tx_bad_sig = CTransaction() tx_bad_sig.deserialize(BytesIO(hex_str_to_bytes(tx_bad_sig_hex))) testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex]) # By the time the signature for the last transaction is checked, all the other transactions # have been fully validated, which is why the node returns full validation results for all # transactions here but empty results in other cases. assert_equal( testres_bad_sig, self.independent_txns_testres + [{ "txid": tx_bad_sig.rehash(), "wtxid": tx_bad_sig.getwtxid(), "allowed": False, "reject-reason": "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)" }]) self.log.info( "Check testmempoolaccept reports txns in packages that exceed max feerate" ) coin = self.coins.pop() tx_high_fee_raw = node.createrawtransaction( [{ "txid": coin["txid"], "vout": 0 }], {self.address: coin["amount"] - Decimal("0.999")}) tx_high_fee_signed = node.signrawtransactionwithkey( hexstring=tx_high_fee_raw, privkeys=self.privkeys) assert tx_high_fee_signed["complete"] tx_high_fee = CTransaction() tx_high_fee.deserialize( BytesIO(hex_str_to_bytes(tx_high_fee_signed["hex"]))) testres_high_fee = node.testmempoolaccept([tx_high_fee_signed["hex"]]) assert_equal(testres_high_fee, [{ "txid": tx_high_fee.rehash(), "wtxid": tx_high_fee.getwtxid(), "allowed": False, "reject-reason": "max-fee-exceeded" }]) package_high_fee = [tx_high_fee_signed["hex"] ] + self.independent_txns_hex testres_package_high_fee = node.testmempoolaccept(package_high_fee) assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank)
def run_test(self): node = self.nodes[0] # convenience reference to the node self.bootstrap_p2p() # Add one p2p connection to the node best_block = self.nodes[0].getbestblockhash() tip = int(best_block, 16) best_block_time = self.nodes[0].getblock(best_block)['time'] block_time = best_block_time + 1 self.log.info("Create a new block with an anyone-can-spend coinbase.") height = 1 block = create_block(tip, create_coinbase(height), block_time) block.solve() # Save the coinbase for later block1 = block tip = block.sha256 node.p2ps[0].send_blocks_and_test([block], node, success=True) self.log.info("Mature the block.") self.nodes[0].generatetoaddress( 100, self.nodes[0].get_deterministic_priv_key().address) # Iterate through a list of known invalid transaction types, ensuring each is # rejected. Some are consensus invalid and some just violate policy. for BadTxTemplate in invalid_txs.iter_all_templates(): self.log.info("Testing invalid transaction: %s", BadTxTemplate.__name__) template = BadTxTemplate(spend_block=block1) tx = template.get_tx() node.p2ps[0].send_txs_and_test( [tx], node, success=False, expect_disconnect=template.expect_disconnect, reject_reason=template.reject_reason, ) if template.expect_disconnect: self.log.info("Reconnecting to peer") self.reconnect_p2p() # Make two p2p connections to provide the node with orphans # * p2ps[0] will send valid orphan txs (one with low fee) # * p2ps[1] will send an invalid orphan tx (and is later disconnected for that) self.reconnect_p2p(num_connections=2) self.log.info('Test orphan transaction handling ... ') # Create a root transaction that we withhold until all dependent transactions # are sent out and in the orphan cache SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51' tx_withhold = CTransaction() tx_withhold.vin.append( CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) tx_withhold.vout.append( CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) tx_withhold.vout.append(CTxOut(12000)) # fee tx_withhold.calc_sha256() # Our first orphan tx with some outputs to create further orphan txs tx_orphan_1 = CTransaction() tx_orphan_1.vin.append( CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) tx_orphan_1.vout = [ CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE) ] * 3 tx_orphan_1.vout.append(CTxOut(20 * COIN - 12000)) # fee tx_orphan_1.calc_sha256() # A valid transaction with low fee tx_orphan_2_no_fee = CTransaction() tx_orphan_2_no_fee.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) tx_orphan_2_no_fee.vout.append( CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) # A valid transaction with sufficient fee tx_orphan_2_valid = CTransaction() tx_orphan_2_valid.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) tx_orphan_2_valid.vout.append( CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) tx_orphan_2_valid.vout.append(CTxOut(12000)) # fee tx_orphan_2_valid.calc_sha256() # An invalid transaction with negative fee tx_orphan_2_invalid = CTransaction() tx_orphan_2_invalid.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) tx_orphan_2_invalid.vout.append( CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) tx_orphan_2_invalid.calc_sha256() self.log.info('Send the orphans ... ') # Send valid orphan txs from p2ps[0] node.p2ps[0].send_txs_and_test( [tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) # Send invalid tx from p2ps[1] node.p2ps[1].send_txs_and_test([tx_orphan_2_invalid], node, success=False) assert_equal(0, node.getmempoolinfo()['size']) # Mempool should be empty assert_equal(2, len(node.getpeerinfo())) # p2ps[1] is still connected self.log.info('Send the withhold tx ... ') with node.assert_debug_log(expected_msgs=["bad-txns-in-ne-out"]): node.p2ps[0].send_txs_and_test([tx_withhold], node, success=True) # Transactions that should end up in the mempool expected_mempool = { t.hash for t in [ tx_withhold, # The transaction that is the root for all orphans tx_orphan_1, # The orphan transaction that splits the coins tx_orphan_2_valid, # The valid transaction (with sufficient fee) ] } # Transactions that do not end up in the mempool # tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx) # tx_orphan_invaid, because it has negative fee (p2ps[1] is disconnected for relaying that tx) self.wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) # p2ps[1] is no longer connected assert_equal(expected_mempool, set(node.getrawmempool())) self.log.info('Test orphan pool overflow') orphan_tx_pool = [CTransaction() for _ in range(101)] for i in range(len(orphan_tx_pool)): orphan_tx_pool[i].vin.append(CTxIn(outpoint=COutPoint(i, 333))) orphan_tx_pool[i].vout.append( CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) with node.assert_debug_log(['mapOrphan overflow, removed 1 tx']): node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) rejected_parent = CTransaction() rejected_parent.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0))) rejected_parent.vout.append( CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) rejected_parent.rehash() with node.assert_debug_log([ 'not keeping orphan with rejected parents {}'.format( rejected_parent.hash) ]): node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False)
def run_test(self): node = self.nodes[0] self.log.info('Start with empty mempool and 101 blocks') # The last 100 coinbase transactions are premature blockhash = self.generate(node, 101)[0] txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"] assert_equal(node.getmempoolinfo()['size'], 0) self.log.info("Submit parent with multiple script branches to mempool") hashlock = hash160(b'Preimage') witness_script = CScript([ OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF ]) witness_program = sha256(witness_script) script_pubkey = CScript([OP_0, witness_program]) parent = CTransaction() parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b"")) parent.vout.append(CTxOut(int(9.99998 * COIN), script_pubkey)) parent.rehash() privkeys = [node.get_deterministic_priv_key().key] raw_parent = node.signrawtransactionwithkey( hexstring=parent.serialize().hex(), privkeys=privkeys)['hex'] parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0) self.generate(node, 1) peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore()) # Create a new transaction with witness solving first branch child_witness_script = CScript([OP_TRUE]) child_witness_program = sha256(child_witness_script) child_script_pubkey = CScript([OP_0, child_witness_program]) child_one = CTransaction() child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b"")) child_one.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey)) child_one.wit.vtxinwit.append(CTxInWitness()) child_one.wit.vtxinwit[0].scriptWitness.stack = [ b'Preimage', b'\x01', witness_script ] child_one_wtxid = child_one.getwtxid() child_one_txid = child_one.rehash() # Create another identical transaction with witness solving second branch child_two = deepcopy(child_one) child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script] child_two_wtxid = child_two.getwtxid() child_two_txid = child_two.rehash() assert_equal(child_one_txid, child_two_txid) assert child_one_wtxid != child_two_wtxid self.log.info("Submit child_one to the mempool") txid_submitted = node.sendrawtransaction(child_one.serialize().hex()) assert_equal( node.getmempoolentry(txid_submitted)['wtxid'], child_one_wtxid) peer_wtxid_relay.wait_for_broadcast([child_one_wtxid]) assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0) # testmempoolaccept reports the "already in mempool" error assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{ "txid": child_one_txid, "wtxid": child_one_wtxid, "allowed": False, "reject-reason": "txn-already-in-mempool" }]) assert_equal( node.testmempoolaccept([child_two.serialize().hex()])[0], { "txid": child_two_txid, "wtxid": child_two_wtxid, "allowed": False, "reject-reason": "txn-same-nonwitness-data-in-mempool" }) # sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool node.sendrawtransaction(child_one.serialize().hex()) self.log.info("Connect another peer that hasn't seen child_one before") peer_wtxid_relay_2 = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Submit child_two to the mempool") # sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool node.sendrawtransaction(child_two.serialize().hex()) # The node should rebroadcast the transaction using the wtxid of the correct transaction # (child_one, which is in its mempool). peer_wtxid_relay_2.wait_for_broadcast([child_one_wtxid]) assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0)
def test_desc_size_limits(self): """Create 3 mempool transactions and 2 package transactions (25KvB each): Ma ^ ^ Mb Mc ^ ^ Pd Pe The top ancestor in the package exceeds descendant size limits but only if the in-mempool and in-package descendants are all considered together. """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) target_weight = 21 * 1000 * WITNESS_SCALE_FACTOR high_fee = Decimal("0.0021") # 10 sats/vB self.log.info( "Check that in-mempool and in-package descendant sizes are calculated properly in packages" ) # Top parent in mempool, Ma first_coin = self.coins.pop() parent_value = (first_coin["amount"] - high_fee) / 2 # Deduct fee and make 2 outputs inputs = [{"txid": first_coin["txid"], "vout": 0}] outputs = [{ self.address: parent_value }, { ADDRESS_BCRT1_P2WSH_OP_TRUE: parent_value }] rawtx = node.createrawtransaction(inputs, outputs) parent_tx = bulk_transaction(tx_from_hex(rawtx), node, target_weight, self.privkeys) node.sendrawtransaction(parent_tx.serialize().hex()) package_hex = [] for j in range(2): # Two legs (left and right) # Mempool transaction (Mb and Mc) mempool_tx = CTransaction() spk = parent_tx.vout[j].scriptPubKey.hex() value = Decimal(parent_tx.vout[j].nValue) / COIN txid = parent_tx.rehash() prevtxs = [{ "txid": txid, "vout": j, "scriptPubKey": spk, "amount": value, }] if j == 0: # normal key (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, j, spk, high_fee) mempool_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs) else: # OP_TRUE inputs = [{"txid": txid, "vout": 1}] outputs = {self.address: value - high_fee} small_tx = tx_from_hex( node.createrawtransaction(inputs, outputs)) mempool_tx = bulk_transaction(small_tx, node, target_weight, None, prevtxs) node.sendrawtransaction(mempool_tx.serialize().hex()) # Package transaction (Pd and Pe) spk = mempool_tx.vout[0].scriptPubKey.hex() value = Decimal(mempool_tx.vout[0].nValue) / COIN txid = mempool_tx.rehash() (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee) prevtxs = [{ "txid": txid, "vout": 0, "scriptPubKey": spk, "amount": value, }] package_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs) package_hex.append(package_tx.serialize().hex()) assert_equal(3, node.getmempoolinfo()["size"]) assert_equal(2, len(package_hex)) testres_too_heavy = node.testmempoolaccept(rawtxs=package_hex) for txres in testres_too_heavy: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now self.generate(node, 1) assert all([ res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex) ])
def run_test(self): (node, std_node) = self.nodes node.add_p2p_connection(P2PDataStore()) std_node.add_p2p_connection(P2PDataStore()) # Get out of IBD node.generatetoaddress(1, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(20): tip = self.build_block(tip) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node, success=True) self.spendable_outputs = deque(block.vtx[0] for block in blocks) self.log.info("Mature the blocks.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.info("Generating some high-sigop transactions.") # Tx with 4001 sigops (valid but non standard) tx_4001 = create_transaction(self.spendable_outputs.popleft(), [ OP_CHECKMULTISIG] * 200 + [OP_CHECKDATASIG]) # Tx with 20001 sigops (consensus-invalid) tx_20001 = create_transaction(self.spendable_outputs.popleft(), [ OP_CHECKMULTISIG] * 1000 + [OP_CHECKDATASIG]) # P2SH tx with too many sigops (valid but nonstandard for std_node) redeem_script = bytes( [OP_IF, OP_CHECKMULTISIG, OP_ENDIF, OP_TRUE]) p2sh_script = CScript([OP_HASH160, hash160(redeem_script), OP_EQUAL]) tx_fundp2sh = create_transaction( self.spendable_outputs.popleft(), p2sh_script) tx_spendp2sh = CTransaction() tx_spendp2sh.vin.append( CTxIn(COutPoint(tx_fundp2sh.sha256, 1), CScript([OP_FALSE, redeem_script]))) tx_spendp2sh.vout.append( CTxOut(0, CScript([OP_RETURN, b'pad' * 20]))) tx_spendp2sh.rehash() # Chain of 10 txes with 2000 sigops each. txes_10x2000_sigops = [] tx = self.spendable_outputs.popleft() for _ in range(10): tx = create_transaction(tx, [OP_CHECKMULTISIG] * 100) txes_10x2000_sigops.append(tx) def make_hightotalsigop_block(): # 20001 total sigops return self.build_block( tip, txes_10x2000_sigops, cbextrascript=bytes([OP_CHECKDATASIG])) def make_highsigop_coinbase_block(): # 60000 sigops in the coinbase return self.build_block( tip, cbextrascript=bytes([OP_CHECKMULTISIG] * 3000)) self.log.info( "Try various high-sigop transactions in blocks / mempool before upgrade") # mempool refuses over 4001. check_for_no_ban_on_rejected_tx(node, tx_4001, MEMPOOL_TXSIGOPS_ERROR) # it used to be that exceeding 20000 would cause a ban, but it's # important that this causes no ban: we want that upgraded nodes # can't get themselves banned by relaying huge-sigops transactions. check_for_no_ban_on_rejected_tx(node, tx_20001, MEMPOOL_TXSIGOPS_ERROR) # the 20001 tx can't be mined check_for_ban_on_rejected_block(node, self.build_block( tip, [tx_20001]), BLOCK_TXSIGOPS_ERROR) self.log.info( "The P2SH script has too many sigops (20 > 15) for a standard node.") # Mine the P2SH funding first because it's nonstandard. tip = self.build_block(tip, [tx_fundp2sh]) std_node.p2p.send_blocks_and_test([tip], node) assert_raises_rpc_error(-26, MEMPOOL_P2SH_SIGOPS_ERROR, std_node.sendrawtransaction, ToHex(tx_spendp2sh)) self.log.info( "A bunch of 2000-sigops txes can be put in mempool but not mined all at once.") # Send the 2000-sigop transactions, which are acceptable. for tx in txes_10x2000_sigops: node.sendrawtransaction(ToHex(tx)) # They can't be mined all at once if the coinbase has a single sigop # (total 20001) check_for_ban_on_rejected_block( node, make_hightotalsigop_block(), BLOCK_TOTALSIGOPS_ERROR) # 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(SIGOPS_DEACTIVATION_TIME) std_node.setmocktime(SIGOPS_DEACTIVATION_TIME) # Mine six blocks with timestamp starting at SIGOPS_DEACTIVATION_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=SIGOPS_DEACTIVATION_TIME + i) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node) assert_equal(node.getblockchaininfo()[ 'mediantime'], SIGOPS_DEACTIVATION_TIME - 1) self.log.info( "The next block will activate, but the activation block itself must follow old rules") check_for_ban_on_rejected_block(node, self.build_block( tip, [tx_20001]), BLOCK_TXSIGOPS_ERROR) check_for_ban_on_rejected_block( node, make_hightotalsigop_block(), BLOCK_TOTALSIGOPS_ERROR) check_for_ban_on_rejected_block( node, make_highsigop_coinbase_block(), BLOCK_TXSIGOPS_ERROR) self.log.info("Mine the activation block itself") tip = self.build_block(tip) node.p2p.send_blocks_and_test([tip], node) sync_blocks(self.nodes) self.log.info("We have activated!") assert_equal(node.getblockchaininfo()[ 'mediantime'], SIGOPS_DEACTIVATION_TIME) assert_equal(std_node.getblockchaininfo()[ 'mediantime'], SIGOPS_DEACTIVATION_TIME) # save this tip for later upgrade_block = tip self.log.info( "The mempool is now a free-for-all, and we can get all the high-sigops transactions in") std_node.sendrawtransaction(ToHex(tx_spendp2sh)) node.sendrawtransaction(ToHex(tx_spendp2sh)) node.sendrawtransaction(ToHex(tx_4001)) node.sendrawtransaction(ToHex(tx_20001)) # resend the 2000-sigop transactions, which will have expired due to # setmocktime. for tx in txes_10x2000_sigops: node.sendrawtransaction(ToHex(tx)) alltxes = set(tx.hash for tx in [ tx_spendp2sh, tx_4001, tx_20001] + txes_10x2000_sigops) assert_equal(set(node.getrawmempool()), alltxes) self.log.info( "The miner will include all the high-sigops transactions at once, without issue.") node.generatetoaddress(1, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) assert_equal(set(tx.rehash() for tx in tip.vtx[1:]), alltxes) # even though it is far smaller than one megabyte, we got in something # like 44000 sigops assert len(tip.serialize()) < ONE_MEGABYTE # save this tip for later postupgrade_block = tip # Deactivation tests self.log.info( "Invalidating the post-upgrade block returns the transactions to mempool") node.invalidateblock(postupgrade_block.hash) assert_equal(set(node.getrawmempool()), alltxes) self.log.info("Test some weird alternative blocks") tip = upgrade_block self.log.info("A 40000-sigop coinbase is acceptable now") tip = make_highsigop_coinbase_block() node.p2p.send_blocks_and_test([tip], node) self.log.info("We can get in our 20001 sigop total block") tip = make_hightotalsigop_block() node.p2p.send_blocks_and_test([tip], node) self.log.info( "Invalidating the upgrade block evicts the bad txes") goodtxes = alltxes - {tx_4001.hash, tx_20001.hash} # loose-rules node just evicts the too-many-sigops transactions node.invalidateblock(upgrade_block.hash) assert_equal(set(node.getrawmempool()), goodtxes) # std_node evicts everything as either nonstandard scriptpubkey or p2sh # too-many-sigops. std_node.invalidateblock(upgrade_block.hash) assert_equal(std_node.getrawmempool(), [])
def run_test(self): [node] = self.nodes node.add_p2p_connection(P2PDataStore()) # Get out of IBD node.generatetoaddress(1, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) self.log.info("Create some blocks with OP_1 coinbase for spending.") blocks = [] for _ in range(20): tip = self.build_block(tip) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node, success=True) self.spendable_outputs = deque(block.vtx[0] for block in blocks) self.log.info("Mature the blocks.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) # To make compact and fast-to-verify transactions, we'll use # CHECKDATASIG over and over with the same data. # (Using the same stuff over and over again means we get to hit the # node's signature cache and don't need to make new signatures every # time.) cds_message = b'' # r=1 and s=1 ecdsa, the minimum values. cds_signature = bytes.fromhex('3006020101020101') # Recovered pubkey cds_pubkey = bytes.fromhex( '03089b476b570d66fad5a20ae6188ebbaf793a4c2a228c65f3d79ee8111d56c932' ) def minefunding2(n): """ Mine a block with a bunch of outputs that are very dense sigchecks when spent (2 sigchecks each); return the inputs that can be used to spend. """ cds_scriptpubkey = CScript([ cds_message, cds_pubkey, OP_3DUP, OP_CHECKDATASIGVERIFY, OP_CHECKDATASIGVERIFY ]) # The scriptsig is carefully padded to have size 26, which is the # shortest allowed for 2 sigchecks for mempool admission. # The resulting inputs have size 67 bytes, 33.5 bytes/sigcheck. cds_scriptsig = CScript([b'x' * 16, cds_signature]) assert_equal(len(cds_scriptsig), 26) self.log.debug( "Gen {} with locking script {} unlocking script {} .".format( n, cds_scriptpubkey.hex(), cds_scriptsig.hex())) tx = self.spendable_outputs.popleft() usable_inputs = [] txes = [] for _ in range(n): tx = create_transaction(tx, cds_scriptpubkey) txes.append(tx) usable_inputs.append( CTxIn(COutPoint(tx.sha256, 1), cds_scriptsig)) newtip = self.build_block(tip, txes) node.p2p.send_blocks_and_test([newtip], node) return usable_inputs, newtip self.log.info("Funding special coins that have high sigchecks") # mine 5000 funded outputs (10000 sigchecks) # will be used pre-activation and post-activation usable_inputs, tip = minefunding2(5000) # assemble them into 50 txes with 100 inputs each (200 sigchecks) submittxes_1 = [] while len(usable_inputs) >= 100: tx = CTransaction() tx.vin = [usable_inputs.pop() for _ in range(100)] tx.vout = [CTxOut(0, CScript([OP_RETURN]))] tx.rehash() submittxes_1.append(tx) # mine 5000 funded outputs (10000 sigchecks) # will be used post-activation usable_inputs, tip = minefunding2(5000) # assemble them into 50 txes with 100 inputs each (200 sigchecks) submittxes_2 = [] while len(usable_inputs) >= 100: tx = CTransaction() tx.vin = [usable_inputs.pop() for _ in range(100)] tx.vout = [CTxOut(0, CScript([OP_RETURN]))] tx.rehash() submittxes_2.append(tx) # Check high sigcheck transactions self.log.info("Create transaction that have high sigchecks") fundings = [] def make_spend(sigcheckcount): # Add a funding tx to fundings, and return a tx spending that using # scriptsig. self.log.debug("Gen tx with {} sigchecks.".format(sigcheckcount)) def get_script_with_sigcheck(count): return CScript([cds_message, cds_pubkey] + (count - 1) * [OP_3DUP, OP_CHECKDATASIGVERIFY] + [OP_CHECKDATASIG]) # get funds locked with OP_1 sourcetx = self.spendable_outputs.popleft() # make funding that forwards to scriptpubkey last_sigcheck_count = ((sigcheckcount - 1) % 30) + 1 fundtx = create_transaction( sourcetx, get_script_with_sigcheck(last_sigcheck_count)) fill_sigcheck_script = get_script_with_sigcheck(30) remaining_sigcheck = sigcheckcount while remaining_sigcheck > 30: fundtx.vout[0].nValue -= 1000 fundtx.vout.append(CTxOut(100, bytes(fill_sigcheck_script))) remaining_sigcheck -= 30 fundtx.rehash() fundings.append(fundtx) # make the spending scriptsig = CScript([cds_signature]) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(fundtx.sha256, 1), scriptsig)) input_index = 2 remaining_sigcheck = sigcheckcount while remaining_sigcheck > 30: tx.vin.append( CTxIn(COutPoint(fundtx.sha256, input_index), scriptsig)) remaining_sigcheck -= 30 input_index += 1 tx.vout.append(CTxOut(0, CScript([OP_RETURN]))) pad_tx(tx) tx.rehash() return tx # Create transactions with many sigchecks. good_tx = make_spend(MAX_TX_SIGCHECK) bad_tx = make_spend(MAX_TX_SIGCHECK + 1) tip = self.build_block(tip, fundings) node.p2p.send_blocks_and_test([tip], node) # Both tx are accepted before the activation. pre_activation_sigcheck_block = self.build_block( tip, [good_tx, bad_tx]) node.p2p.send_blocks_and_test([pre_activation_sigcheck_block], node) node.invalidateblock(pre_activation_sigcheck_block.hash) # 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(SIGCHECKS_ACTIVATION_TIME + 10) # Mine six blocks with timestamp starting at # SIGCHECKS_ACTIVATION_TIME-1 blocks = [] for i in range(-1, 5): tip = self.build_block(tip, nTime=SIGCHECKS_ACTIVATION_TIME + i) blocks.append(tip) node.p2p.send_blocks_and_test(blocks, node) assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME - 1) self.log.info( "The next block will activate, but the activation block itself must follow old rules" ) # Send the 50 txes and get the node to mine as many as possible (it should do all) # The node is happy mining and validating a 10000 sigcheck block before # activation. node.p2p.send_txs_and_test(submittxes_1, node) [blockhash ] = node.generatetoaddress(1, node.get_deterministic_priv_key().address) assert_equal(set(node.getblock(blockhash, 1)["tx"][1:]), {t.hash for t in submittxes_1}) # We have activated, but let's invalidate that. assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME) node.invalidateblock(blockhash) # Try again manually and invalidate that too goodblock = self.build_block(tip, submittxes_1) node.p2p.send_blocks_and_test([goodblock], node) node.invalidateblock(goodblock.hash) # All transactions should be back in mempool. assert_equal(set(node.getrawmempool()), {t.hash for t in submittxes_1}) self.log.info("Mine the activation block itself") tip = self.build_block(tip) node.p2p.send_blocks_and_test([tip], node) self.log.info("We have activated!") assert_equal(node.getblockchaininfo()['mediantime'], SIGCHECKS_ACTIVATION_TIME) self.log.info( "Try a block with a transaction going over the limit (limit: {})". format(MAX_TX_SIGCHECK)) bad_tx_block = self.build_block(tip, [bad_tx]) check_for_ban_on_rejected_block( node, bad_tx_block, reject_reason=BLOCK_SIGCHECKS_PARALLEL_ERROR) self.log.info( "Try a block with a transaction just under the limit (limit: {})". format(MAX_TX_SIGCHECK)) good_tx_block = self.build_block(tip, [good_tx]) node.p2p.send_blocks_and_test([good_tx_block], node) node.invalidateblock(good_tx_block.hash) # save this tip for later # ~ upgrade_block = tip # Transactions still in pool: assert_equal(set(node.getrawmempool()), {t.hash for t in submittxes_1}) self.log.info( "Try sending 10000-sigcheck blocks after activation (limit: {})". format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) # Send block with same txes we just tried before activation badblock = self.build_block(tip, submittxes_1) check_for_ban_on_rejected_block( node, badblock, reject_reason=BLOCK_SIGCHECKS_CACHED_ERROR) self.log.info( "There are too many sigchecks in mempool to mine in a single block. Make sure the node won't mine invalid blocks." ) node.generatetoaddress(1, node.get_deterministic_priv_key().address) tip = self.getbestblock(node) # only 39 txes got mined. assert_equal(len(node.getrawmempool()), 11) self.log.info( "Try sending 10000-sigcheck block with fresh transactions after activation (limit: {})" .format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) # Note: in the following tests we'll be bumping timestamp in order # to bypass any kind of 'bad block' cache on the node, and get a # fresh evaluation each time. # Try another block with 10000 sigchecks but all fresh transactions badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 5) check_for_ban_on_rejected_block( node, badblock, reject_reason=BLOCK_SIGCHECKS_PARALLEL_ERROR) # Send the same txes again with different block hash. Currently we don't # cache valid transactions in invalid blocks so nothing changes. badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 6) check_for_ban_on_rejected_block( node, badblock, reject_reason=BLOCK_SIGCHECKS_PARALLEL_ERROR) # Put all the txes in mempool, in order to get them cached: node.p2p.send_txs_and_test(submittxes_2, node) # Send them again, the node still doesn't like it. But the log # error message has now changed because the txes failed from cache. badblock = self.build_block(tip, submittxes_2, nTime=SIGCHECKS_ACTIVATION_TIME + 7) check_for_ban_on_rejected_block( node, badblock, reject_reason=BLOCK_SIGCHECKS_CACHED_ERROR) self.log.info( "Try sending 8000-sigcheck block after activation (limit: {})". format(MAXBLOCKSIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) # redundant, but just to mirror the following test... node.setexcessiveblock(MAXBLOCKSIZE) badblock = self.build_block(tip, submittxes_2[:40], nTime=SIGCHECKS_ACTIVATION_TIME + 5) check_for_ban_on_rejected_block( node, badblock, reject_reason=BLOCK_SIGCHECKS_CACHED_ERROR) self.log.info( "Bump the excessiveblocksize limit by 1 byte, and send another block with same txes (new sigchecks limit: {})" .format((MAXBLOCKSIZE + 1) // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO)) node.setexcessiveblock(MAXBLOCKSIZE + 1) tip = self.build_block(tip, submittxes_2[:40], nTime=SIGCHECKS_ACTIVATION_TIME + 6) # It should succeed now since limit should be 8000. node.p2p.send_blocks_and_test([tip], node)
def run_test(self): node = self.nodes[0] self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error(-8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], ))['hex'] txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0) node.generate(1) self.mempool_size = 0 self.check_mempool_result( result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known'}], rawtxs=[raw_tx_in_block], ) self.log.info('A transaction not in the mempool') fee = 0.00000700 raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later outputs=[{node.getnewaddress(): 0.3 - fee}], ))['hex'] tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True}], rawtxs=[raw_tx_0], ) self.log.info('A final transaction not in the mempool') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL outputs=[{node.getnewaddress(): 0.025}], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0) self.mempool_size += 1 self.log.info('A transaction in the mempool') node.sendrawtransaction(hexstring=raw_tx_0) self.mempool_size += 1 self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool'}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that replaces a mempool transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True}], rawtxs=[raw_tx_0], ) self.log.info('A transaction that conflicts with an unconfirmed tx') # Send the transaction that replaces the mempool transaction and opts out of replaceability node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) # take original raw_tx_0 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) self.log.info('A transaction with missing inputs, that never existed') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with missing inputs, that existed once in the past') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[ {'txid': txid_0, 'vout': 0}, {'txid': txid_1, 'vout': 0}, ], outputs=[{node.getnewaddress(): 0.1}] ))['hex'] txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0) node.generate(1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_0], ) self.check_mempool_result( result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[raw_tx_1], ) self.log.info('Create a signed "reference" tx for later use') raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': txid_spend_both, 'vout': 0}], outputs=[{node.getnewaddress(): 0.05}], ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with no outputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [] # Skip re-signing the transaction for context independent checks from now on # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A really large transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with negative output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue *= -1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-negative'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with too large output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue = 21000000 * COIN + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-toolarge'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with too large sum of output values') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = 21000000 * COIN self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-txouttotal-toolarge'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction with duplicate inputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-inputs-duplicate'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A coinbase transaction') # Pick the input of the first tx we signed, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: coinbase'}], rawtxs=[tx.serialize().hex()], ) self.log.info('Some nonstandard transactions') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.nVersion = 3 # A version currently non-standard self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: version'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript([OP_HASH160, hash160(b'burn'), OP_EQUAL])) num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0] = output_p2sh_burn tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust'}], rawtxs=[tx.serialize().hex()], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: multi-op-return'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A timelocked transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-final'}], rawtxs=[tx.serialize().hex()], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}], rawtxs=[tx.serialize().hex()], maxfeerate=0, )
def test_witness_block_size(self): # TODO: Test that non-witness carrying blocks can't exceed 1MB # Skipping this test for now; this is covered in p2p-fullblocktest.py # Test that witness-bearing blocks are limited at ceil(base + wit/4) <= 1MB. block = self.build_next_block() assert len(self.utxo) > 0 # Create a P2WSH transaction. # The witness program will be a bunch of OP_2DROP's, followed by OP_TRUE. # This should give us plenty of room to tweak the spending tx's # virtual size. NUM_DROPS = 200 # 201 max ops per script! NUM_OUTPUTS = 50 witness_program = CScript([OP_2DROP] * NUM_DROPS + [OP_TRUE]) witness_hash = uint256_from_str(sha256(witness_program)) script_pubkey = CScript([OP_0, ser_uint256(witness_hash)]) prevout = COutPoint(self.utxo[0].sha256, self.utxo[0].n) value = self.utxo[0].nValue parent_tx = CTransaction() parent_tx.vin.append(CTxIn(prevout, b"")) child_value = int(value / NUM_OUTPUTS) for i in range(NUM_OUTPUTS): parent_tx.vout.append(CTxOut(child_value, script_pubkey)) parent_tx.vout[0].nValue -= 50000 assert parent_tx.vout[0].nValue > 0 parent_tx.rehash() filler_size = 3150 child_tx = CTransaction() for i in range(NUM_OUTPUTS): child_tx.vin.append(CTxIn(COutPoint(parent_tx.sha256, i), b"")) child_tx.vout = [CTxOut(value - 100000, CScript([OP_TRUE]))] for i in range(NUM_OUTPUTS): child_tx.wit.vtxinwit.append(CTxInWitness()) child_tx.wit.vtxinwit[-1].scriptWitness.stack = [ b'a' * filler_size ] * (2 * NUM_DROPS) + [witness_program] child_tx.rehash() self.update_witness_block_with_transactions(block, [parent_tx, child_tx]) vsize = get_virtual_size(block) assert_greater_than(MAX_BLOCK_BASE_SIZE, vsize) additional_bytes = (MAX_BLOCK_BASE_SIZE - vsize) * 4 i = 0 while additional_bytes > 0: # Add some more bytes to each input until we hit MAX_BLOCK_BASE_SIZE+1 extra_bytes = min(additional_bytes + 1, 55) block.vtx[-1].wit.vtxinwit[int( i / (2 * NUM_DROPS))].scriptWitness.stack[ i % (2 * NUM_DROPS)] = b'a' * (filler_size + extra_bytes) additional_bytes -= extra_bytes i += 1 block.vtx[0].vout.pop() # Remove old commitment add_witness_commitment(block) block.solve() vsize = get_virtual_size(block) assert_equal(vsize, MAX_BLOCK_BASE_SIZE + 1) # Make sure that our test case would exceed the old max-network-message # limit assert len(block.serialize()) > 2 * 1024 * 1024 test_witness_block(self.nodes[0], self.test_node, block, accepted=False) # Now resize the second transaction to make the block fit. cur_length = len(block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0]) block.vtx[-1].wit.vtxinwit[0].scriptWitness.stack[0] = b'a' * ( cur_length - 1) block.vtx[0].vout.pop() add_witness_commitment(block) block.solve() assert get_virtual_size(block) == MAX_BLOCK_BASE_SIZE test_witness_block(self.nodes[0], self.test_node, block, accepted=True) # Update available utxo's self.utxo.pop(0) self.utxo.append( UTXO(block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue))
def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*UNIT), CScript([b'a']))] tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * UNIT), CScript([b'a' * 35]))] tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the mempool node.sendrawtransaction(ToHex(tx)) return tx test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee*UNIT)) cur_time = int(time.time()) for i in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert tx2.hash in self.nodes[0].getrawmempool() tip_snapshot_meta = get_tip_snapshot_meta(self.nodes[0]) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee*UNIT)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time+600) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) assert tx3.hash in self.nodes[0].getrawmempool() self.nodes[0].generate(1) assert tx3.hash not in self.nodes[0].getrawmempool() # One more test, this time using height locks tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True) assert tx4.hash in self.nodes[0].getrawmempool() # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True) assert tx5.hash not in self.nodes[0].getrawmempool() utxos = self.nodes[0].listunspent() tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) tx5.vout[0].nValue += int(utxos[0]["amount"]*UNIT) raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert tx4.hash not in self.nodes[0].getrawmempool() assert tx3.hash in self.nodes[0].getrawmempool() # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. tip = int(self.nodes[0].getblockhash(self.nodes[0].getblockcount()-1), 16) height = self.nodes[0].getblockcount() # Let's get the available stake that is not already used # We must exclude tx2 outputs from the list since any stake referred to them will fail # In order to do that, we limit outputs with the number of minimum confirmations (minconf = 2) avail_stake = [x for x in self.nodes[0].listunspent(2) if x['txid'] != tx1.hash] for i in range(2): stake = avail_stake.pop() coinbase = sign_coinbase(self.nodes[0], create_coinbase(height, stake, tip_snapshot_meta.hash)) block = create_block(tip, coinbase, cur_time) block.nVersion = 3 block.solve() tip = block.sha256 tip_snapshot_meta = update_snapshot_with_tx(self.nodes[0], tip_snapshot_meta, height, coinbase) height += 1 self.nodes[0].p2p.send_and_ping(msg_block(block)) cur_time += 1 # sync as the reorg is happening self.nodes[0].p2p.sync_with_ping() mempool = self.nodes[0].getrawmempool() assert tx3.hash not in mempool assert tx2.hash in mempool # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1)) self.nodes[0].generate(10)
def run_test(self): self.test_node = self.nodes[0].add_p2p_connection(P2PDataStore()) self.log.info("Running signed block tests") assert_equal(self.nodes[0].getblockcount(), 0) genesisblock_hash = int(self.nodes[0].getbestblockhash(), 16) # create 1 spendable coinbase height = 1 block_time = int(time()) block1 = create_block(genesisblock_hash, create_coinbase(height), block_time) block1.rehash() block1.solve(self.secret) self.nodes[0].p2p.send_blocks_and_test([block1], self.nodes[0], success=True) self.nodes[0].generate(100, self.secret) previousblock_hash = int(self.nodes[0].getbestblockhash(), 16) # create a test block height = 102 block_time = int(time()) block = create_block(previousblock_hash, create_coinbase(height), block_time + 100) block.rehash() block_hex = ToHex(block) block_hash = block.getsighash() previousblock_hash = int(self.nodes[0].getbestblockhash(), 16) self.log.info("Test block : %s" % bytes_to_hex_str(block_hash)) self.log.info("Testing RPC testproposedblock with valid block") assert_equal(self.nodes[0].testproposedblock(block_hex), True) self.log.info("Testing RPC combineblocksigs with 3 valid signatures") sig0 = self.cKey[0].sign(block_hash) sig1 = self.cKey[1].sign(block_hash) sig2 = self.cKey[2].sign(block_hash) signedBlock = self.nodes[0].combineblocksigs(block_hex, [ bytes_to_hex_str(sig0), bytes_to_hex_str(sig1), bytes_to_hex_str(sig2) ]) if (len(signedBlock["warning"])): self.log.warning( "%s : signatures:%s [%d, %d, %d]" % (signedBlock["warning"], [sig0, sig1, sig2], self.cKey[0].verify(block_hash, sig0), self.cKey[1].verify( block_hash, sig1), self.cKey[2].verify(block_hash, sig2))) block.solve(self.secret) self.nodes[0].p2p.send_blocks_and_test([block], self.nodes[0], success=True) # combineblocksigs only returns true when signatures are appended and enough # are included to pass validation assert_equal(signedBlock["complete"], True) assert_equal(signedBlock["warning"], "") assert_equal( len(signedBlock["hex"]), len(block_hex) + len(bytes_to_hex_str(sig0)) + len(bytes_to_hex_str(sig1)) + len(bytes_to_hex_str(sig2)) + 6) #6 is 2 bytes for 3 length of signatures self.log.info("Testing RPC combineblocksigs with 1 invalid signature") sig0 = bytearray(sig0) sig0[2] = 30 signedBlock = self.nodes[0].combineblocksigs(block_hex, [ bytes_to_hex_str(sig0), bytes_to_hex_str(sig1), bytes_to_hex_str(sig2) ]) assert_equal(signedBlock["complete"], True) #True as threshold is 1 assert_equal( signedBlock["warning"], "invalid encoding in signature: Non-canonical DER signature %s One or more signatures were not added to block" % (bytes_to_hex_str(sig0))) assert_equal( len(signedBlock["hex"]), len(block_hex) + len(bytes_to_hex_str(sig1)) + len(bytes_to_hex_str(sig2)) + 4) self.log.info("Testing RPC combineblocksigs with 2 invalid signatures") sig1 = bytearray(sig1) sig1[2] = 30 signedBlock = self.nodes[0].combineblocksigs(block_hex, [ bytes_to_hex_str(sig0), bytes_to_hex_str(sig1), bytes_to_hex_str(sig2) ]) assert_equal(signedBlock["complete"], True) #True as threshold is 1 assert_equal(len(signedBlock["hex"]), len(block_hex) + len(bytes_to_hex_str(sig2)) + 2) self.log.info("Testing RPC combineblocksigs with 3 invalid signatures") sig2 = bytearray(sig2) sig2[2] = 30 signedBlock = self.nodes[0].combineblocksigs(block_hex, [ bytes_to_hex_str(sig0), bytes_to_hex_str(sig1), bytes_to_hex_str(sig2) ]) assert_equal(signedBlock["complete"], False) assert_equal(len(signedBlock["hex"]), len(block_hex)) self.log.info("Testing RPC combineblocksigs with invalid blocks") #invalid block hex assert_raises_rpc_error(-22, "Block decode failed", self.nodes[0].combineblocksigs, "0000", []) #no signature assert_raises_rpc_error(-32602, "Signature list was empty", self.nodes[0].combineblocksigs, block_hex, []) #too many signature assert_raises_rpc_error( -32602, "Too many signatures", self.nodes[0].combineblocksigs, block_hex, ["00", "00", "00", "00", "00", "00", "00", "00", "00", "00"]) self.log.info("Testing RPC testproposedblock with old block") #invalid block hex assert_raises_rpc_error(-22, "Block decode failed", self.nodes[0].testproposedblock, "0000") # create invalid block height = 103 invalid_block = create_block(previousblock_hash, create_coinbase(height), block_time + 110) invalid_block.solve(self.secret) assert_raises_rpc_error(-25, "proposal was not based on our best chain", self.nodes[0].testproposedblock, ToHex(invalid_block)) self.log.info("Testing RPC testproposedblock with non standard block") # create block with non-standard transaction previousblock_hash = int(self.nodes[0].getbestblockhash(), 16) height = 103 nonstd_block = create_block(previousblock_hash, create_coinbase(height), block_time + 120) # 3 of 2 multisig is non standard nonstd_script = CScript([ b'53' + self.pubkeys[0] + self.pubkeys[1] + self.pubkeys[2] + b'52ea' ]) tx = CTransaction() tx.vin.append(CTxIn(COutPoint(block1.vtx[0].malfixsha256, 0), b"")) tx.vout.append(CTxOut(50, b'0x51')) (sig_hash, err) = SignatureHash(nonstd_script, tx, 0, SIGHASH_ALL) signature = self.cKey[0].sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx.vin[0].scriptSig = CScript([signature, self.pubkeys[0]]) tx.rehash() #add non-standard tx to block nonstd_block.vtx.append(tx) nonstd_block.solve(self.secret) nonstd_block.hashMerkleRoot = nonstd_block.calc_merkle_root() nonstd_block.hashImMerkleRoot = nonstd_block.calc_immutable_merkle_root( ) assert (nonstd_block.is_valid()) #block is accepted when acceptnonstd flag is set assert_equal( self.nodes[0].testproposedblock(ToHex(nonstd_block), True), True) #block is rejected when acceptnonstd flag is not set assert_raises_rpc_error( -25, "Block proposal included a non-standard transaction", self.nodes[0].testproposedblock, ToHex(nonstd_block), False)
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 = ECKey() coinbase_key.generate() coinbase_pubkey = coinbase_key.get_pubkey().get_bytes() # 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) block.nHeight = height self.blocks.append(block) self.block_time += 1 prepare_block(block) # 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.nHeight = height prepare_block(block) 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].txid, 1), scriptSig=b"")) tx.vout.append(CTxOut(int(SUBSIDY * COIN), CScript([OP_TRUE]))) pad_tx(tx) tx.rehash() block102 = create_block(self.tip, create_coinbase(height), self.block_time) block102.nHeight = height self.block_time += 1 block102.vtx.extend([tx]) prepare_block(block102) self.blocks.append(block102) self.tip = block102.sha256 self.block_time += 1 height += 1 # Bury the assumed valid block 3400 deep for i in range(3700): block = create_block(self.tip, create_coinbase(height), self.block_time) block.nHeight = height prepare_block(block) 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)] + required_args) self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)] + required_args) 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(3802): 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(960) assert_equal( self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 3802) # Send blocks to node2. Block 102 will be rejected. self.send_blocks_until_disconnected(p2p2) self.assert_blockchain_height(self.nodes[2], 101)
def run_test(self): node = self.nodes[0] node.add_p2p_connection(P2PDataStore()) # Allocate as many UTXOs as are needed num_utxos = sum( len(test_case['sig_hash_types']) for test_case in TESTCASES if isinstance(test_case, dict)) value = int(SUBSIDY * 1_000_000) fee = 10_000 max_utxo_value = (value - fee) // num_utxos private_keys = [] public_keys = [] spendable_outputs = [] executed_scripts = [] utxo_idx = 0 # Prepare UTXOs for the tests below for test_case in TESTCASES: if test_case == 'ENABLE_REPLAY_PROTECTION': continue for _ in test_case['sig_hash_types']: private_key = ECKey() private_key.generate() private_keys.append(private_key) public_key = private_key.get_pubkey() public_keys.append(public_key) utxo_value = max_utxo_value - utxo_idx * 100 # deduct 100*i coins for unique amounts if test_case.get('opcodes', False): opcodes = test_case['opcodes'] redeem_script = CScript( opcodes + [public_key.get_bytes(), OP_CHECKSIG]) executed_scripts.append(redeem_script) utxo_script = CScript( [OP_HASH160, hash160(redeem_script), OP_EQUAL]) elif test_case.get('is_p2pk', False): utxo_script = CScript( [public_key.get_bytes(), OP_CHECKSIG]) executed_scripts.append(utxo_script) else: utxo_script = CScript([ OP_DUP, OP_HASH160, hash160(public_key.get_bytes()), OP_EQUALVERIFY, OP_CHECKSIG ]) executed_scripts.append(utxo_script) spendable_outputs.append(CTxOut(utxo_value, utxo_script)) utxo_idx += 1 anyonecanspend_address = node.decodescript('51')['p2sh'] burn_address = node.decodescript('00')['p2sh'] p2sh_script = CScript([OP_HASH160, bytes(20), OP_EQUAL]) node.generatetoaddress(1, anyonecanspend_address) node.generatetoaddress(100, burn_address) # Build and send fan-out transaction creating all the UTXOs block_hash = node.getblockhash(1) coin = int(node.getblock(block_hash)['tx'][0], 16) tx_fan_out = CTransaction() tx_fan_out.vin.append(CTxIn(COutPoint(coin, 1), CScript([b'\x51']))) tx_fan_out.vout = spendable_outputs tx_fan_out.rehash() node.p2p.send_txs_and_test([tx_fan_out], node) utxo_idx = 0 key_idx = 0 for test_case in TESTCASES: if test_case == 'ENABLE_REPLAY_PROTECTION': node.setmocktime(ACTIVATION_TIME) node.generatetoaddress(11, burn_address) continue # Build tx for this test, will broadcast later tx = CTransaction() num_inputs = len(test_case['sig_hash_types']) spent_outputs = spendable_outputs[:num_inputs] del spendable_outputs[:num_inputs] assert len(spent_outputs) == num_inputs total_input_amount = sum(output.nValue for output in spent_outputs) max_output_amount = (total_input_amount - fee) // test_case['outputs'] for i in range(test_case['outputs']): output_amount = max_output_amount - i * 77 output_script = CScript( [OP_HASH160, i.to_bytes(20, 'big'), OP_EQUAL]) tx.vout.append(CTxOut(output_amount, output_script)) for _ in test_case['sig_hash_types']: tx.vin.append( CTxIn(COutPoint(tx_fan_out.txid, utxo_idx), CScript())) utxo_idx += 1 # Keep unsigned tx for signrawtransactionwithkey below unsigned_tx = tx.serialize().hex() private_keys_wif = [] sign_inputs = [] # Make list of inputs for signrawtransactionwithkey for i, spent_output in enumerate(spent_outputs): sign_inputs.append({ 'txid': tx_fan_out.txid_hex, 'vout': key_idx + i, 'amount': Decimal(spent_output.nValue) / COIN, 'scriptPubKey': spent_output.scriptPubKey.hex(), }) for i, sig_hash_type in enumerate(test_case['sig_hash_types']): # Compute sighash for this input; we sign it manually using sign_ecdsa/sign_schnorr # and then broadcast the complete transaction sighash = SignatureHashLotus( tx_to=tx, spent_utxos=spent_outputs, sig_hash_type=sig_hash_type, input_index=i, executed_script_hash=hash256(executed_scripts[key_idx]), codeseparator_pos=test_case.get('codesep', 0xffff_ffff), ) if test_case.get('schnorr', False): signature = private_keys[key_idx].sign_schnorr(sighash) else: signature = private_keys[key_idx].sign_ecdsa(sighash) signature += bytes( [test_case.get('suffix', sig_hash_type & 0xff)]) # Build correct scriptSig if test_case.get('opcodes'): tx.vin[i].scriptSig = CScript( [signature, executed_scripts[key_idx]]) elif test_case.get('is_p2pk'): tx.vin[i].scriptSig = CScript([signature]) else: tx.vin[i].scriptSig = CScript( [signature, public_keys[key_idx].get_bytes()]) sig_hash_type_str = self.get_sig_hash_type_str(sig_hash_type) if sig_hash_type_str is not None and 'opcodes' not in test_case and 'error' not in test_case: # If we're a simple output type (P2PKH or P2KH) and aren't supposed to fail, # we sign using signrawtransactionwithkey and verify the transaction signed # the expected sighash. We won't broadcast it though. # Note: signrawtransactionwithkey will not sign using replay-protection. private_key_wif = bytes_to_wif( private_keys[key_idx].get_bytes()) raw_tx_signed = self.nodes[0].signrawtransactionwithkey( unsigned_tx, [private_key_wif], sign_inputs, sig_hash_type_str)['hex'] # Extract signature from signed signed_tx = CTransaction() signed_tx.deserialize( io.BytesIO(bytes.fromhex(raw_tx_signed))) sig = list(CScript(signed_tx.vin[i].scriptSig))[0] pubkey = private_keys[key_idx].get_pubkey() sighash = SignatureHashLotus( tx_to=tx, spent_utxos=spent_outputs, sig_hash_type=sig_hash_type & 0xff, input_index=i, executed_script_hash=hash256( executed_scripts[key_idx]), ) # Verify sig signs the above sighash and has the expected sighash type assert pubkey.verify_ecdsa(sig[:-1], sighash) assert sig[-1] == sig_hash_type & 0xff key_idx += 1 # Broadcast transaction and check success/failure tx.rehash() if 'error' not in test_case: node.p2p.send_txs_and_test([tx], node) else: node.p2p.send_txs_and_test([tx], node, success=False, reject_reason=test_case['error'])