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') # Pick a random coin(base) to spend coin = coins.pop() raw_tx_in_block = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ 'txid': coin['txid'], 'vout': coin['vout'] }], outputs=[{ node.getnewaddress(): 30 }, { node.getnewaddress(): 70 }], ))['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 = 0.000700 raw_tx_0 = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ "txid": txid_in_block, "vout": 0, "sequence": 0xfffffffd }], outputs=[{ node.getnewaddress(): 30 - fee }], ))['hex'] tx = FromHex(CTransaction(), raw_tx_0) tx.calc_txid() txid_0 = tx.txid_hex 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') # Pick a random coin(base) to spend coin = coins.pop() raw_tx_final = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ 'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff }], # SEQUENCE_FINAL outputs=[{ node.getnewaddress(): 2.5 }], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] tx = FromHex(CTransaction(), raw_tx_final) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, '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': 'txn-already-in-mempool' }], rawtxs=[raw_tx_0], ) # Removed RBF test # self.log.info('A transaction that replaces a mempool transaction') # ... self.log.info('A transaction that conflicts with an unconfirmed tx') # Send the transaction that conflicts with the mempool transaction node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) # take original raw_tx_0 tx = FromHex(CTransaction(), raw_tx_0) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee tx.calc_txid() # skip re-signing the tx self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, '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 = FromHex(CTransaction(), raw_tx_0) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) tx.calc_txid() # skip re-signing the tx self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'missing-inputs' }], rawtxs=[ToHex(tx)], ) self.log.info( 'A transaction with missing inputs, that existed once in the past') tx = FromHex(CTransaction(), raw_tx_0) # Set vout to 1, to spend the other outpoint (49 coins) of the # in-chain-tx we want to double spend tx.vin[0].prevout.n = 1 raw_tx_1 = node.signrawtransactionwithwallet(ToHex(tx))['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(): 10 }]))['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(): 5 }], ))['hex'] tx = FromHex(CTransaction(), raw_tx_reference) tx.calc_txid() # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': True }], rawtxs=[ToHex(tx)], maxfeerate=0, ) self.log.info('A transaction with no outputs') tx = FromHex(CTransaction(), raw_tx_reference) tx.vout = [] tx.calc_txid() # Skip re-signing the transaction for context independent checks from now on # FromHex(tx, node.signrawtransactionwithwallet(ToHex(tx))['hex']) self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-vout-empty' }], rawtxs=[ToHex(tx)], ) self.log.info('A really large transaction') tx = FromHex(CTransaction(), raw_tx_reference) tx.vin = [tx.vin[0] ] * (1 + MAX_BLOCK_BASE_SIZE // len(tx.vin[0].serialize())) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-oversize' }], rawtxs=[ToHex(tx)], ) self.log.info('A transaction with negative output value') tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0].nValue *= -1 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-vout-negative' }], rawtxs=[ToHex(tx)], ) # 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 = FromHex(CTransaction(), raw_tx_reference) tx.vout[0].nValue = MAX_MONEY + 1 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge' }], rawtxs=[ToHex(tx)], ) self.log.info('A transaction with too large sum of output values') tx = FromHex(CTransaction(), raw_tx_reference) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = MAX_MONEY tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-txouttotal-toolarge' }], rawtxs=[ToHex(tx)], ) self.log.info('A transaction with duplicate inputs') tx = FromHex(CTransaction(), raw_tx_reference) tx.vin = [tx.vin[0]] * 2 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-inputs-duplicate' }], rawtxs=[ToHex(tx)], ) 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 = FromHex(CTransaction(), raw_tx_coinbase_spent) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-tx-coinbase' }], rawtxs=[ToHex(tx)], ) self.log.info('Some nonstandard transactions') tx = FromHex(CTransaction(), raw_tx_reference) tx.nVersion = 3 # A version currently non-standard tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'version' }], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'scriptpubkey' }], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) key = ECKey() key.generate() pubkey = key.get_pubkey().get_bytes() # Some bare multisig script (2-of-3) tx.vout[0].scriptPubKey = CScript( [OP_2, pubkey, pubkey, pubkey, OP_3, OP_CHECKMULTISIG]) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bare-multisig' }], rawtxs=[tx.serialize().hex()], ) tx = FromHex(CTransaction(), raw_tx_reference) # Some not-pushonly scriptSig tx.vin[0].scriptSig = CScript([OP_HASH160]) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'scriptsig-not-pushonly' }], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) # Some too large scriptSig (>1650 bytes) tx.vin[0].scriptSig = CScript([b'a' * 1648]) tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'scriptsig-size' }], rawtxs=[tx.serialize().hex()], ) tx = FromHex(CTransaction(), raw_tx_reference) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript( [OP_HASH160, hash160(b'burn'), OP_EQUAL])) # Use enough outputs to make the tx too large for our policy num_scripts = 100000 // len(output_p2sh_burn.serialize()) tx.vout = [output_p2sh_burn] * num_scripts tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'tx-size' }], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0] = output_p2sh_burn # Make output smaller, such that it is dust for our policy tx.vout[0].nValue -= 1 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'dust' }], rawtxs=[ToHex(tx)], ) # 4 OP_RETURN outputs not allowed tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 4 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'multi-op-return' }], rawtxs=[ToHex(tx)], ) self.log.info('A timelocked transaction') tx = FromHex(CTransaction(), raw_tx_reference) # Should be non-max, so locktime is not ignored tx.vin[0].nSequence -= 1 tx.nLockTime = node.getblockcount() + 1 tx.calc_txid() self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'bad-txns-nonfinal' }], rawtxs=[ToHex(tx)], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx = FromHex(CTransaction(), raw_tx_reference) # We could include it in the second block mined from now, but not the # very next one tx.vin[0].nSequence = 2 tx.calc_txid() # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{ 'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'non-BIP68-final' }], rawtxs=[tx.serialize().hex()], maxfeerate=0, )
def fund_and_test_wallet(scriptPubKey, is_standard, expected_in_std_wallet, amount=10000, spendfee=500, nonstd_error="scriptpubkey", sign_error=None): """ Get the nonstandard node to fund a transaction, test its standardness by trying to broadcast on the standard node, then mine it and see if it ended up in the standard node's wallet. Finally, it attempts to spend the coin. """ self.log.info("Trying script {}".format(scriptPubKey.hex(), )) # get nonstandard node to fund the script tx = CTransaction() tx.vout.append(CTxOut(max(amount, 10000), scriptPubKey)) rawtx = nonstd_node.fundrawtransaction(ToHex(tx), { 'lockUnspents': True, 'changePosition': 1 })['hex'] # fundrawtransaction doesn't like to fund dust outputs, so we # have to manually override the amount. FromHex(tx, rawtx) tx.vout[0].nValue = min(amount, 10000) rawtx = nonstd_node.signrawtransactionwithwallet(ToHex(tx))['hex'] # ensure signing process did not disturb scriptPubKey signedtx = FromHex(CTransaction(), rawtx) assert_equal(scriptPubKey, signedtx.vout[0].scriptPubKey) signedtx.calc_txid() txid = signedtx.txid_hex balance_initial = std_node.getbalance() # try broadcasting it on the standard node if is_standard: std_node.sendrawtransaction(rawtx) assert txid in std_node.getrawmempool() else: assert_raises_rpc_error(-26, nonstd_error, std_node.sendrawtransaction, rawtx) assert txid not in std_node.getrawmempool() # make sure it's in nonstandard node's mempool, then mine it nonstd_node.sendrawtransaction(rawtx) assert txid in nonstd_node.getrawmempool() [blockhash] = nonstd_node.generate(1) # make sure it was mined assert txid in nonstd_node.getblock(blockhash)["tx"] self.sync_blocks() wallet_outpoints = {(entry['txid'], entry['vout']) for entry in std_node.listunspent()} # calculate wallet balance change just as a double check balance_change = std_node.getbalance() - balance_initial if expected_in_std_wallet: assert (txid, 0) in wallet_outpoints assert balance_change == amount * SATOSHI else: assert (txid, 0) not in wallet_outpoints assert balance_change == 0 # try spending the funds using the wallet. outamount = (amount - spendfee) * SATOSHI if outamount < 546 * SATOSHI: # If the final amount would be too small, then just donate # to miner fees. outputs = [{"data": b"to miner, with love".hex()}] else: outputs = [{address_nonstd: outamount}] spendtx = std_node.createrawtransaction([{ 'txid': txid, 'vout': 0 }], outputs) signresult = std_node.signrawtransactionwithwallet(spendtx) if sign_error is None: assert_equal(signresult['complete'], True) txid = std_node.sendrawtransaction(signresult['hex']) [blockhash] = std_node.generate(1) # make sure it was mined assert txid in std_node.getblock(blockhash)["tx"] self.sync_blocks() else: assert_equal(signresult['complete'], False) assert_equal(signresult['errors'][0]['error'], sign_error)