Skip to content

Commit

Permalink
Merge bitcoin#27350: test: refactor: dedup mempool_package_limits.py …
Browse files Browse the repository at this point in the history
…subtests via decorator

e669833 test: dedup package limit checks via decorator in mempool_package_limits.py (Sebastian Falbesoner)
72f25e2 test: refactor: use Satoshis for fees in mempool_package_limits.py (Sebastian Falbesoner)

Pull request description:

  The subtests in the functional test mempool_package_limits.py all follow the same pattern:
  1. first, check that the mempool is currently empty
  2. create and submit certain single txs to the mempool, prepare list of hex transactions
  3. check that `testmempoolaccept` on the package hex fails with a "package-mempool-limits" error on each tx result
  4. after mining a block, check that submitting the package succeeds

  Note that steps 1,3,4 are identical for each of the subtests and only step 2 varies, so this might be a nice opportunity to deduplicate code by using a newly introduced decorator which executes the necessary before and after the essential part of the subtest. This also makes it easier to add new subtests without having to copy-paste those parts once again.

  In addition, the first commit switches the fee unit from BTC to Satoshis, which allows to get rid of some imports (`COIN` and `Decimal`) and a comment for the `test_desc_size_limits` subtest is fixed (s/25KvB/21KvB/).

ACKs for top commit:
  ismaelsadeeq:
    ACK e669833
  glozow:
    utACK e669833

Tree-SHA512: 84a85e739de7387391c13bd46aeb015a74302ea7c6f0ca3d4e2b1b487d38df390dc118eb5b1c11d3e4206bff316a4dab60ef6b25d8feced672345d4e36ffd205
  • Loading branch information
glozow committed Mar 30, 2023
2 parents d4833e9 + e669833 commit 328087d
Showing 1 changed file with 51 additions and 75 deletions.
126 changes: 51 additions & 75 deletions test/functional/mempool_package_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,41 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test logic for limiting mempool and package ancestors/descendants."""

from decimal import Decimal

from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import (
COIN,
WITNESS_SCALE_FACTOR,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)
from test_framework.wallet import MiniWallet

# Decorator to
# 1) check that mempool is empty at the start of a subtest
# 2) run the subtest, which may submit some transaction(s) to the mempool and
# create a list of hex transactions
# 3) testmempoolaccept the package hex and check that it fails with the error
# "package-mempool-limits" for each tx
# 4) after mining a block, clearing the pre-submitted transactions from mempool,
# check that submitting the created package succeeds
def check_package_limits(func):
def func_wrapper(self, *args, **kwargs):
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
package_hex = func(self, *args, **kwargs)
testres_error_expected = node.testmempoolaccept(rawtxs=package_hex)
assert_equal(len(testres_error_expected), len(package_hex))
for txres in testres_error_expected:
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)])

return func_wrapper


class MempoolPackageLimitsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
Expand All @@ -40,9 +61,9 @@ def run_test(self):
self.test_anc_size_limits()
self.test_desc_size_limits()

@check_package_limits
def test_chain_limits_helper(self, mempool_count, package_count):
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
chain_hex = []

chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count)[-1]["new_utxo"]
Expand All @@ -51,13 +72,7 @@ def test_chain_limits_helper(self, mempool_count, package_count):
tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
chaintip_utxo = tx["new_utxo"]
chain_hex.append(tx["hex"])
testres_too_long = node.testmempoolaccept(rawtxs=chain_hex)
for txres in testres_too_long:
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=chain_hex)])
return chain_hex

def test_chain_limits(self):
"""Create chains from mempool and package transactions that are longer than 25,
Expand All @@ -76,6 +91,7 @@ def test_chain_limits(self):
# 13 transactions in the mempool and 13 in the package.
self.test_chain_limits_helper(13, 13)

@check_package_limits
def test_desc_count_limits(self):
"""Create an 'A' shaped package with 24 transactions in the mempool and 2 in the package:
M1
Expand All @@ -93,7 +109,6 @@ def test_desc_count_limits(self):
package transactions).
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
# Top parent in mempool, M1
m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
Expand All @@ -113,14 +128,9 @@ def test_desc_count_limits(self):

assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex))
testres_too_long = node.testmempoolaccept(rawtxs=package_hex)
for txres in testres_too_long:
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)])
return package_hex

@check_package_limits
def test_desc_count_limits_2(self):
"""Create a Package with 24 transaction in mempool and 2 transaction in package:
M1
Expand Down Expand Up @@ -157,15 +167,9 @@ def test_desc_count_limits_2(self):

assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex))
testres = node.testmempoolaccept(rawtxs=package_hex)
assert_equal(len(testres), len(package_hex))
for txres in testres:
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)])
return package_hex

@check_package_limits
def test_anc_count_limits(self):
"""Create a 'V' shaped chain with 24 transactions in the mempool and 3 in the package:
M1a M1b
Expand All @@ -183,7 +187,6 @@ def test_anc_count_limits(self):
and in-package ancestors are all considered together.
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
package_hex = []
pc_parent_utxos = []

Expand All @@ -203,14 +206,9 @@ def test_anc_count_limits(self):

assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(3, len(package_hex))
testres_too_long = node.testmempoolaccept(rawtxs=package_hex)
for txres in testres_too_long:
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)])
return package_hex

@check_package_limits
def test_anc_count_limits_2(self):
"""Create a 'Y' shaped chain with 24 transactions in the mempool and 2 in the package:
M1a M1b
Expand All @@ -228,7 +226,6 @@ def test_anc_count_limits_2(self):
and in-package ancestors are all considered together.
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
pc_parent_utxos = []

self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
Expand All @@ -245,14 +242,9 @@ def test_anc_count_limits_2(self):
pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0])

assert_equal(24, node.getmempoolinfo()["size"])
testres_too_long = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])
for txres in testres_too_long:
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=[pc_tx["hex"], pd_tx["hex"]])])
return [pc_tx["hex"], pd_tx["hex"]]

@check_package_limits
def test_anc_count_limits_bushy(self):
"""Create a tree with 20 transactions in the mempool and 6 in the package:
M1...M4 M5...M8 M9...M12 M13...M16 M17...M20
Expand All @@ -265,7 +257,6 @@ def test_anc_count_limits_bushy(self):
combined, PC has 25 in-mempool and in-package parents.
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
package_hex = []
pc_parent_utxos = []
for _ in range(5): # Make package transactions P0 ... P4
Expand All @@ -282,14 +273,9 @@ def test_anc_count_limits_bushy(self):

assert_equal(20, node.getmempoolinfo()["size"])
assert_equal(6, len(package_hex))
testres = node.testmempoolaccept(rawtxs=package_hex)
for txres in testres:
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)])
return package_hex

@check_package_limits
def test_anc_size_limits(self):
"""Test Case with 2 independent transactions in the mempool and a parent + child in the
package, where the package parent is the child of both mempool transactions (30KvB each):
Expand All @@ -302,10 +288,10 @@ def test_anc_size_limits(self):
and in-package ancestors are all considered together.
"""
node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"])
parent_utxos = []
target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB
high_fee = Decimal("0.003") # 10 sats/vB
target_vsize = 30_000
high_fee = 10 * target_vsize # 10 sats/vB
target_weight = target_vsize * WITNESS_SCALE_FACTOR
self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
# Mempool transactions A and B
for _ in range(2):
Expand All @@ -314,22 +300,17 @@ def test_anc_size_limits(self):
parent_utxos.append(bulked_tx["new_utxo"])

# Package transaction C
pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(high_fee * COIN), target_weight=target_weight)
pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=high_fee, target_weight=target_weight)

# Package transaction D
pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_weight=target_weight)

assert_equal(2, node.getmempoolinfo()["size"])
testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["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=[pc_tx["hex"], pd_tx["hex"]])])
return [pc_tx["hex"], pd_tx["hex"]]

@check_package_limits
def test_desc_size_limits(self):
"""Create 3 mempool transactions and 2 package transactions (25KvB each):
"""Create 3 mempool transactions and 2 package transactions (21KvB each):
Ma
^ ^
Mb Mc
Expand All @@ -339,12 +320,12 @@ def test_desc_size_limits(self):
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
target_vsize = 21_000
high_fee = 10 * target_vsize # 10 sats/vB
target_weight = target_vsize * WITNESS_SCALE_FACTOR
self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
# Top parent in mempool, Ma
ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=int(high_fee / 2 * COIN), target_weight=target_weight)
ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=high_fee // 2, target_weight=target_weight)
self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])

package_hex = []
Expand All @@ -359,13 +340,8 @@ def test_desc_size_limits(self):

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")
return package_hex

# 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)])

if __name__ == "__main__":
MempoolPackageLimitsTest().main()

0 comments on commit 328087d

Please sign in to comment.