diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 39035f7cb1e149..eb2a5eabecb378 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -5,9 +5,12 @@ """Test mempool limiting together/eviction with the wallet.""" from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, create_confirmed_utxos, create_lots_of_big_transactions, gen_return_txouts +from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, gen_return_txouts +from test_framework.wallet import MiniWallet + class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): @@ -20,55 +23,56 @@ def set_test_params(self): ]] self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): txouts = gen_return_txouts() - relayfee = self.nodes[0].getnetworkinfo()['relayfee'] + + node = self.nodes[0] + miniwallet = MiniWallet(node) + relayfee = node.getnetworkinfo()['relayfee'] self.log.info('Check that mempoolminfee is minrelytxfee') - assert_equal(self.nodes[0].getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) - assert_equal(self.nodes[0].getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) + assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) + assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) + + # Generate 92 UTXOs to flood the mempool + # 1 to create a tx initially that will be evicted from the mempool later + # 90 with a fee rate much higher than the previous UTXO (3 batches of 30 with increasing fee rate) + # And 1 more to verify that this tx does not get added to the mempool with a fee rate less than the mempoolminfee + miniwallet.generate(1 + (3 * 30) + 1) - txids = [] - utxos = create_confirmed_utxos(relayfee, self.nodes[0], 91) + # Mine 99 blocks so that the UTXOs are allowed to be spent + node.generate(COINBASE_MATURITY - 1) self.log.info('Create a mempool tx that will be evicted') - us0 = utxos.pop() - inputs = [{ "txid" : us0["txid"], "vout" : us0["vout"]}] - outputs = {self.nodes[0].getnewaddress() : 0.0001} - tx = self.nodes[0].createrawtransaction(inputs, outputs) - self.nodes[0].settxfee(relayfee) # specifically fund this tx with low fee - txF = self.nodes[0].fundrawtransaction(tx) - self.nodes[0].settxfee(0) # return to automatic fee selection - txFS = self.nodes[0].signrawtransactionwithwallet(txF['hex']) - txid = self.nodes[0].sendrawtransaction(txFS['hex']) - - relayfee = self.nodes[0].getnetworkinfo()['relayfee'] - base_fee = relayfee*100 - for i in range (3): - txids.append([]) - txids[i] = create_lots_of_big_transactions(self.nodes[0], txouts, utxos[30*i:30*i+30], 30, (i+1)*base_fee) + tx_to_be_evicted_id = miniwallet.send_self_transfer( + from_node=node, fee_rate=relayfee + )["txid"] + + # Increase the tx fee rate massively now to give the next transactions a higher priority in the mempool + base_fee = relayfee*1000 + + self.log.info('Fill up the mempool with txs with higher fee rate') + no_of_large_tx_created = 0 + for batch_of_txid in range(1,4): + # Increment the tx fee rate gradually by a factor of (base_fee) for each batch of 30 transactions + no_of_large_tx_created += miniwallet.create_large_transactions( + node, txouts, 30, batch_of_txid * base_fee + ) self.log.info('The tx should be evicted by now') - assert txid not in self.nodes[0].getrawmempool() - txdata = self.nodes[0].gettransaction(txid) - assert txdata['confirmations'] == 0 #confirmation should still be 0 + # The number of transactions created should be greater than the ones present in the mempool + assert_greater_than(no_of_large_tx_created, len(node.getrawmempool())) + # Initial tx created should not be present in the mempool anymore as it had a lower fee rate + assert tx_to_be_evicted_id not in node.getrawmempool() - self.log.info('Check that mempoolminfee is larger than minrelytxfee') - assert_equal(self.nodes[0].getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) - assert_greater_than(self.nodes[0].getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) + self.log.info('Check that mempoolminfee is larger than minrelaytxfee') + assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000')) + assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000')) + # Deliberately tries to create a tx with a fee less that the minimum mempool fee to assert that it does not get added to the mempool self.log.info('Create a mempool tx that will not pass mempoolminfee') - us0 = utxos.pop() - inputs = [{ "txid" : us0["txid"], "vout" : us0["vout"]}] - outputs = {self.nodes[0].getnewaddress() : 0.0001} - tx = self.nodes[0].createrawtransaction(inputs, outputs) - # specifically fund this tx with a fee < mempoolminfee, >= than minrelaytxfee - txF = self.nodes[0].fundrawtransaction(tx, {'feeRate': relayfee}) - txFS = self.nodes[0].signrawtransactionwithwallet(txF['hex']) - assert_raises_rpc_error(-26, "mempool min fee not met", self.nodes[0].sendrawtransaction, txFS['hex']) + assert_raises_rpc_error(-26,"mempool min fee not met",miniwallet.send_self_transfer,from_node=node,fee_rate=relayfee,mempool_valid=False,) + if __name__ == '__main__': MempoolLimitTest().main() diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index ba5b95f930f850..cb3cb368977342 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -19,6 +19,7 @@ CTxInWitness, CTxOut, tx_from_hex, + from_hex, ) from test_framework.script import ( CScript, @@ -182,6 +183,26 @@ def sendrawtransaction(self, *, from_node, tx_hex): from_node.sendrawtransaction(tx_hex) self.scan_tx(from_node.decoderawtransaction(tx_hex)) + def create_large_transactions( + self, node, array_of_large_tx, no_of_tx_ids, fee_rate + ): + # Create large transactions by appending txouts in vout + no_of_tx_created = 0 + for _ in range(no_of_tx_ids): + # Create a self transfer here to get the tx details and then append the vout to increase the tx size + hex = self.create_self_transfer(from_node=node, fee_rate=fee_rate)['hex'] + # Converts it into a CTransaction() instance to append the vouts + tx_instance = from_hex(CTransaction(), hex) + for txout in array_of_large_tx: + tx_instance.vout.append(txout) + tx_hex = tx_instance.serialize().hex() + # Serializes and sends the tx to the nodes + self.sendrawtransaction(from_node=node, tx_hex=tx_hex) + no_of_tx_created += 1 + assert_equal(no_of_tx_created,no_of_tx_ids) + return no_of_tx_created + + def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): """Build a transaction that spends parent_txid.vout[n] and produces one output with amount = parent_value with a fee deducted.