Skip to content

Commit

Permalink
test: Run mempool_packages.py with MiniWallet
Browse files Browse the repository at this point in the history
Summary:
```
This allows to run the test even when no wallet is compiled in.

Also, it is a lot nicer to read now.
```

Backport of [core#26625](bitcoin/bitcoin#26625).

Depends on D16431.

Test Plan:
  ./test/functional/test_runner.py mempool_packages

Reviewers: #bitcoin_abc, roqqit

Reviewed By: roqqit

Differential Revision: https://reviews.bitcoinabc.org/D16432
  • Loading branch information
MarcoFalke authored and Fabcien committed Jul 9, 2024
1 parent 05ef30b commit b81cd4c
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 69 deletions.
9 changes: 6 additions & 3 deletions test/functional/abc_mempool_chainedtx.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ def run_test(self):
wallet = MiniWallet(node)

self.generate(wallet, COINBASE_MATURITY + 2)
chain_hex = wallet.create_self_transfer_chain(
chain_length=LEGACY_MAX_CHAINED_TX * 2
)["chain_hex"]
chain_hex = [
t["hex"]
for t in wallet.create_self_transfer_chain(
chain_length=LEGACY_MAX_CHAINED_TX * 2
)
]

for i, tx_hex in enumerate(chain_hex):
txid = wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex)
Expand Down
48 changes: 10 additions & 38 deletions test/functional/mempool_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test descendant package tracking code."""

from decimal import Decimal

from test_framework.blocktools import COINBASE_MATURITY
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, satoshi_round
from test_framework.wallet import MiniWallet

MAX_ANCESTORS = 50

Expand All @@ -18,49 +16,22 @@ def set_test_params(self):
self.num_nodes = 2
self.extra_args = [["-maxorphantx=1000"]] * self.num_nodes

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

# Build a transaction that spends parent_txid:vout
# Return amount sent
def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs):
send_value = satoshi_round((value - fee) / num_outputs)
inputs = [{"txid": parent_txid, "vout": vout}]
outputs = {}
for _ in range(num_outputs):
outputs[node.getnewaddress()] = send_value
rawtx = node.createrawtransaction(inputs, outputs)
signedtx = node.signrawtransactionwithwallet(rawtx)
txid = node.sendrawtransaction(signedtx["hex"])
fulltx = node.getrawtransaction(txid, 1)
# make sure we didn't generate a change output
assert len(fulltx["vout"]) == num_outputs
return (txid, send_value)

def run_test(self):
# Mine some blocks and have them mature.
# keep track of invs
self.wallet = MiniWallet(self.nodes[0])
self.wallet.rescan_utxos()

peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore())
self.generate(self.nodes[0], COINBASE_MATURITY + 1)
utxo = self.nodes[0].listunspent(10)
txid = utxo[0]["txid"]
value = utxo[0]["amount"]

fee = Decimal("100")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
for i in range(MAX_ANCESTORS):
(txid, sent_value) = self.chain_transaction(
self.nodes[0], txid, 0, value, fee, 1
)
value = sent_value
chain.append(txid)
chain = self.wallet.send_self_transfer_chain(
from_node=self.nodes[0], chain_length=MAX_ANCESTORS
)

# Wait until mempool transactions have passed initial broadcast
# (sent inv and received getdata)
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if
# unbroadcast changes in between
peer_inv_store.wait_for_broadcast(chain)
peer_inv_store.wait_for_broadcast([t["txid"] for t in chain])

# Check mempool has MAX_ANCESTORS transactions in it
mempool = self.nodes[0].getrawmempool(True)
Expand All @@ -74,7 +45,8 @@ def run_test(self):
ancestor_fees = sum([mempool[tx]["fees"]["base"] for tx in mempool])

descendants = []
ancestors = list(chain)
ancestors = [t["txid"] for t in chain]
chain = [t["txid"] for t in chain]
for x in reversed(chain):
# Check that getmempoolentry is consistent with getrawmempool
entry = self.nodes[0].getmempoolentry(x)
Expand Down
8 changes: 5 additions & 3 deletions test/functional/rpc_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ def test_chain(self):
node = self.nodes[0]

chain = self.wallet.create_self_transfer_chain(chain_length=25)
chain_hex = chain["chain_hex"]
chain_txns = chain["chain_txns"]
chain_hex = [t["hex"] for t in chain]
chain_txns = [t["tx"] for t in chain]

self.log.info(
"Check that testmempoolaccept requires packages to be sorted by dependency"
Expand Down Expand Up @@ -471,7 +471,9 @@ def test_submitpackage(self):

self.log.info("Submitpackage only allows packages of 1 child with its parents")
# Chain of 3 transactions has too many generations
chain_hex = self.wallet.create_self_transfer_chain(chain_length=25)["chain_hex"]
chain_hex = [
t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)
]
assert_raises_rpc_error(
-25, "not-child-with-parents", node.submitpackage, chain_hex
)
Expand Down
46 changes: 21 additions & 25 deletions test/functional/test_framework/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ def create_self_transfer_multi(
inputs_value_total = sum([int(XEC * utxo["value"]) for utxo in utxos_to_spend])
outputs_value_total = inputs_value_total - fee_per_output * num_outputs
amount_per_output = amount_per_output or (outputs_value_total // num_outputs)
assert amount_per_output > 0
outputs_value_total = amount_per_output * num_outputs
fee = Decimal(inputs_value_total - outputs_value_total) / XEC

# create tx
tx = CTransaction()
Expand Down Expand Up @@ -302,6 +305,7 @@ def create_self_transfer_multi(
)
for i in range(len(tx.vout))
],
"fee": fee,
"txid": txid,
"hex": tx.serialize().hex(),
"tx": tx,
Expand Down Expand Up @@ -334,7 +338,6 @@ def create_self_transfer(
send_value = satoshi_round(
utxo_to_spend["value"] - (fee or (fee_rate * (Decimal(size) / 1000)))
)
assert send_value > 0

# create tx
tx = self.create_self_transfer_multi(
Expand All @@ -347,49 +350,42 @@ def create_self_transfer(
if not target_size:
assert_equal(len(tx["tx"].serialize()), size)

return {
"txid": tx["txid"],
"hex": tx["hex"],
"tx": tx["tx"],
"new_utxo": tx["new_utxos"][0],
}
tx["new_utxo"] = tx.pop("new_utxos")[0]

return tx

def sendrawtransaction(self, *, from_node, tx_hex):
txid = from_node.sendrawtransaction(tx_hex)
self.scan_tx(from_node.decoderawtransaction(tx_hex))
return txid

def create_self_transfer_chain(self, *, chain_length):
def create_self_transfer_chain(self, *, chain_length, utxo_to_spend=None):
"""
Create a "chain" of chain_length transactions. The nth transaction in
the chain is a child of the n-1th transaction and parent of the n+1th transaction.
Returns a dic {"chain_hex": chain_hex, "chain_txns" : chain_txns}
"chain_hex" is a list representing the chain's transactions in hexadecimal.
"chain_txns" is a list representing the chain's transactions in the CTransaction object.
"""
chaintip_utxo = self.get_utxo()
chain_hex = []
chain_txns = []
chaintip_utxo = utxo_to_spend or self.get_utxo()
chain = []

for _ in range(chain_length):
tx = self.create_self_transfer(utxo_to_spend=chaintip_utxo)
chaintip_utxo = tx["new_utxo"]
chain_hex.append(tx["hex"])
chain_txns.append(tx["tx"])
chain.append(tx)

return {"chain_hex": chain_hex, "chain_txns": chain_txns}
return chain

def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None):
def send_self_transfer_chain(self, *, from_node, **kwargs):
"""Create and send a "chain" of chain_length transactions. The nth
transaction in the chain is a child of the n-1th transaction and parent
of the n+1th transaction. Returns the chaintip (nth) utxo.
of the n+1th transaction.
Returns a list of objects for each tx (see create_self_transfer_multi).
"""
chaintip_utxo = utxo_to_spend or self.get_utxo()
for _ in range(chain_length):
chaintip_utxo = self.send_self_transfer(
utxo_to_spend=chaintip_utxo, from_node=from_node
)["new_utxo"]
return chaintip_utxo
chain = self.create_self_transfer_chain(**kwargs)
for t in chain:
self.sendrawtransaction(from_node=from_node, tx_hex=t["hex"])

return chain


def getnewdestination():
Expand Down

0 comments on commit b81cd4c

Please sign in to comment.