Skip to content

Commit

Permalink
[functional test] submitrawpackage RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
glozow committed Jun 23, 2022
1 parent fa07651 commit e866f0d
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 5 deletions.
133 changes: 129 additions & 4 deletions test/functional/rpc_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
CTxInWitness,
tx_from_hex,
)
from test_framework.p2p import P2PTxInvStore
from test_framework.script import (
CScript,
OP_TRUE,
)
from test_framework.util import (
assert_equal,
assert_fee_amount,
assert_raises_rpc_error,
)
from test_framework.wallet import (
create_child_with_parents,
create_raw_chain,
DEFAULT_FEE,
make_chain,
)

Expand All @@ -51,7 +55,7 @@ def run_test(self):
self.address = node.get_deterministic_priv_key().address
self.coins = []
# The last 100 coinbase transactions are premature
for b in self.generatetoaddress(node, 200, self.address)[:100]:
for b in self.generatetoaddress(node, 220, self.address)[:-100]:
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
self.coins.append({
"txid": coinbase["txid"],
Expand Down Expand Up @@ -82,7 +86,7 @@ def run_test(self):
self.test_multiple_parents()
self.test_conflicting()
self.test_rbf()

self.test_submitpackage()

def test_independent(self):
self.log.info("Test multiple independent transactions in a package")
Expand Down Expand Up @@ -132,8 +136,7 @@ def test_independent(self):

def test_chain(self):
node = self.nodes[0]
first_coin = self.coins.pop()
(chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
(chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys)
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
[{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]])
Expand Down Expand Up @@ -306,5 +309,127 @@ def test_rbf(self):
}]
self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package)

def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
"""Assert that a successful submitpackage result is consistent with testmempoolaccept
results and getmempoolentry info. Note that the result structs are different and, due to
policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
some information may be different.
"""
for testres_tx in testmempoolaccept_result:
# Grab this result from the submitpackage_result
submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
assert_equal(submitres_tx["txid"], testres_tx["txid"])
# No "allowed" if the tx was already in the mempool
if "allowed" in testres_tx and testres_tx["allowed"]:
assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
entry_info = node.getmempoolentry(submitres_tx["txid"])
assert_equal(submitres_tx["vsize"], entry_info["vsize"])
assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])

def test_submit_child_with_parents(self, num_parents, partial_submit):
node = self.nodes[0]
peer = node.add_p2p_connection(P2PTxInvStore())
# Test a package with num_parents parents and 1 child transaction.
package_hex = []
package_txns = []
values = []
scripts = []
for _ in range(num_parents):
parent_coin = self.coins.pop()
value = parent_coin["amount"]
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
package_hex.append(txhex)
package_txns.append(tx)
values.append(value)
scripts.append(spk)
if partial_submit and random.choice([True, False]):
node.sendrawtransaction(txhex)
child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts)
package_hex.append(child_hex)
package_txns.append(tx_from_hex(child_hex))

testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex)
submitpackage_result = node.submitpackage(package=package_hex)

# Check that each result is present, with the correct size and fees
for i in range(num_parents + 1):
tx = package_txns[i]
wtxid = tx.getwtxid()
assert wtxid in submitpackage_result["tx-results"]
tx_result = submitpackage_result["tx-results"][wtxid]
assert_equal(tx_result, {
"txid": tx.rehash(),
"vsize": tx.get_vsize(),
"fees": {
"base": DEFAULT_FEE,
}
})

# submitpackage result should be consistent with testmempoolaccept and getmempoolentry
self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)

# Package feerate is calculated for the remaining transactions after deduplication and
# individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
# had high-feerates or were already in the mempool, no package feerate is provided.
# In this case, since all of the parents have high fees, each is accepted individually.
assert "package-feerate" not in submitpackage_result

# The node should announce each transaction. No guarantees for propagation.
peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
self.generate(node, 1)


def test_submit_cpfp(self):
node = self.nodes[0]
peer = node.add_p2p_connection(P2PTxInvStore())

# 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
# fee-bumped by the child.
coin_rich = self.coins.pop()
coin_poor = self.coins.pop()
tx_rich, hex_rich, value_rich, spk_rich = make_chain(node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"])
tx_poor, hex_poor, value_poor, spk_poor = make_chain(node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0)
package_txns = [tx_rich, tx_poor]
hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor])
tx_child = tx_from_hex(hex_child)
package_txns.append(tx_child)

submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child])

rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()]
poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()]
child_result = submitpackage_result["tx-results"][tx_child.getwtxid()]
assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
assert_equal(poor_parent_result["fees"]["base"], 0)
assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
# Package feerate is calculated for the remaining transactions after deduplication and
# individual submission. Since this package had a 0-fee parent, package feerate must have
# been used and returned.
assert "package-feerate" in submitpackage_result
assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"])

# The node will broadcast each transaction, still abiding by its peer's fee filter
peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
self.generate(node, 1)


def test_submitpackage(self):
node = self.nodes[0]

self.log.info("Submitpackage valid packages with 1 child and some number of parents")
for num_parents in [1, 2, 24]:
self.test_submit_child_with_parents(num_parents, False)
self.test_submit_child_with_parents(num_parents, True)

self.log.info("Submitpackage valid packages with CPFP")
self.test_submit_cpfp()

self.log.info("Submitpackage only allows packages of 1 child with its parents")
# Chain of 3 transactions has too many generations
chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3)
assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)


if __name__ == "__main__":
RPCPackagesTest().main()
2 changes: 1 addition & 1 deletion test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
'rpc_packages.py',
'feature_reindex.py',
'feature_abortnode.py',
# vv Tests less than 30s vv
Expand Down Expand Up @@ -230,7 +231,6 @@
'mempool_packages.py',
'mempool_package_onemore.py',
'rpc_createmultisig.py',
'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',
Expand Down

0 comments on commit e866f0d

Please sign in to comment.