merge bitcoin#21762: Speed up mempool_spend_coinbase.py

Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
Kittywhiskers Van Gogh 2024-07-23 17:39:21 +00:00
parent 72eeb9a0d6
commit bd750140be
No known key found for this signature in database
GPG Key ID: 30CD0C065E5C4AAD
3 changed files with 42 additions and 26 deletions

View File

@ -20,40 +20,41 @@ from test_framework.wallet import MiniWallet
class MempoolSpendCoinbaseTest(BitcoinTestFramework): class MempoolSpendCoinbaseTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self): def run_test(self):
wallet = MiniWallet(self.nodes[0]) wallet = MiniWallet(self.nodes[0])
wallet.generate(200) # Invalidate two blocks, so that miniwallet has access to a coin that will mature in the next block
chain_height = self.nodes[0].getblockcount() chain_height = 198
assert_equal(chain_height, 200) self.nodes[0].invalidateblock(self.nodes[0].getblockhash(chain_height + 1))
assert_equal(chain_height, self.nodes[0].getblockcount())
# Coinbase at height chain_height-100+1 ok in mempool, should # Coinbase at height chain_height-100+1 ok in mempool, should
# get mined. Coinbase at height chain_height-100+2 is # get mined. Coinbase at height chain_height-100+2 is
# too immature to spend. # too immature to spend.
b = [self.nodes[0].getblockhash(n) for n in range(101, 103)] wallet.scan_blocks(start=chain_height - 100 + 1, num=1)
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] utxo_mature = wallet.get_utxo()
utxo_101 = wallet.get_utxo(txid=coinbase_txids[0]) wallet.scan_blocks(start=chain_height - 100 + 2, num=1)
utxo_102 = wallet.get_utxo(txid=coinbase_txids[1]) utxo_immature = wallet.get_utxo()
spend_101_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_101)["txid"] spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"]
# coinbase at height 102 should be too immature to spend # other coinbase should be too immature to spend
immature_tx = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_immature, mempool_valid=False)
assert_raises_rpc_error(-26, assert_raises_rpc_error(-26,
"bad-txns-premature-spend-of-coinbase", "bad-txns-premature-spend-of-coinbase",
lambda: wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_102)) lambda: self.nodes[0].sendrawtransaction(immature_tx['hex']))
# mempool should have just spend_101: # mempool should have just the mature one
assert_equal(self.nodes[0].getrawmempool(), [spend_101_id]) assert_equal(self.nodes[0].getrawmempool(), [spend_mature_id])
# mine a block, spend_101 should get confirmed # mine a block, mature one should get confirmed
self.nodes[0].generate(1) self.nodes[0].generate(1)
assert_equal(set(self.nodes[0].getrawmempool()), set()) assert_equal(set(self.nodes[0].getrawmempool()), set())
# ... and now height 102 can be spent: # ... and now previously immature can be spent:
spend_102_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_102)["txid"] spend_new_id = self.nodes[0].sendrawtransaction(immature_tx['hex'])
assert_equal(self.nodes[0].getrawmempool(), [spend_102_id]) assert_equal(self.nodes[0].getrawmempool(), [spend_new_id])
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -913,12 +913,13 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# This is needed so that we are out of IBD when the test starts, # This is needed so that we are out of IBD when the test starts,
# see the tip age check in IsInitialBlockDownload(). # see the tip age check in IsInitialBlockDownload().
self.set_genesis_mocktime() self.set_genesis_mocktime()
gen_addresses = [k.address for k in TestNode.PRIV_KEYS] + [ADDRESS_BCRT1_P2SH_OP_TRUE] gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [ADDRESS_BCRT1_P2SH_OP_TRUE]
assert_equal(len(gen_addresses), 4)
for i in range(8): for i in range(8):
self.bump_mocktime((25 if i != 7 else 24) * 156) self.bump_mocktime((25 if i != 7 else 24) * 156)
cache_node.generatetoaddress( cache_node.generatetoaddress(
nblocks=25 if i != 7 else 24, nblocks=25 if i != 7 else 24,
address=gen_addresses[i % 4], address=gen_addresses[i % len(gen_addresses)],
) )
assert_equal(cache_node.getblockchaininfo()["blocks"], 199) assert_equal(cache_node.getblockchaininfo()["blocks"], 199)

View File

@ -37,9 +37,13 @@ class MiniWallet:
for i in range(start, start + num): for i in range(start, start + num):
block = self._test_node.getblock(blockhash=self._test_node.getblockhash(i), verbosity=2) block = self._test_node.getblock(blockhash=self._test_node.getblockhash(i), verbosity=2)
for tx in block['tx']: for tx in block['tx']:
for out in tx['vout']: self.scan_tx(tx)
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']}) def scan_tx(self, tx):
"""Scan the tx for self._scriptPubKey outputs and add them to self._utxos"""
for out in tx['vout']:
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']})
def generate(self, num_blocks): def generate(self, num_blocks):
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
@ -69,6 +73,12 @@ class MiniWallet:
def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None): def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None):
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend)
self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex'])
return tx
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True):
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
self._utxos = sorted(self._utxos, key=lambda k: k['value']) self._utxos = sorted(self._utxos, key=lambda k: k['value'])
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
vsize = Decimal(85) vsize = Decimal(85)
@ -83,8 +93,12 @@ class MiniWallet:
tx_hex = tx.serialize().hex() tx_hex = tx.serialize().hex()
tx_info = from_node.testmempoolaccept([tx_hex])[0] tx_info = from_node.testmempoolaccept([tx_hex])[0]
self._utxos.append({'txid': tx_info['txid'], 'vout': 0, 'value': send_value}) assert_equal(mempool_valid, tx_info['allowed'])
from_node.sendrawtransaction(tx_hex) if mempool_valid:
assert_equal(len(tx_hex) // 2, vsize) assert_equal(len(tx_hex) // 2, vsize)
assert_equal(tx_info['fees']['base'], fee) assert_equal(tx_info['fees']['base'], fee)
return {'txid': tx_info['txid'], 'hex': tx_hex} return {'txid': tx_info['txid'], 'hex': tx_hex}
def sendrawtransaction(self, *, from_node, tx_hex):
from_node.sendrawtransaction(tx_hex)
self.scan_tx(from_node.decoderawtransaction(tx_hex))