Skip to content

Commit e866f0d

Browse files
committed
[functional test] submitrawpackage RPC
1 parent fa07651 commit e866f0d

File tree

2 files changed

+130
-5
lines changed

2 files changed

+130
-5
lines changed

test/functional/rpc_packages.py

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
CTxInWitness,
1616
tx_from_hex,
1717
)
18+
from test_framework.p2p import P2PTxInvStore
1819
from test_framework.script import (
1920
CScript,
2021
OP_TRUE,
2122
)
2223
from test_framework.util import (
2324
assert_equal,
25+
assert_fee_amount,
26+
assert_raises_rpc_error,
2427
)
2528
from test_framework.wallet import (
2629
create_child_with_parents,
2730
create_raw_chain,
31+
DEFAULT_FEE,
2832
make_chain,
2933
)
3034

@@ -51,7 +55,7 @@ def run_test(self):
5155
self.address = node.get_deterministic_priv_key().address
5256
self.coins = []
5357
# The last 100 coinbase transactions are premature
54-
for b in self.generatetoaddress(node, 200, self.address)[:100]:
58+
for b in self.generatetoaddress(node, 220, self.address)[:-100]:
5559
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
5660
self.coins.append({
5761
"txid": coinbase["txid"],
@@ -82,7 +86,7 @@ def run_test(self):
8286
self.test_multiple_parents()
8387
self.test_conflicting()
8488
self.test_rbf()
85-
89+
self.test_submitpackage()
8690

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

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

312+
def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
313+
"""Assert that a successful submitpackage result is consistent with testmempoolaccept
314+
results and getmempoolentry info. Note that the result structs are different and, due to
315+
policy differences between testmempoolaccept and submitpackage (i.e. package feerate),
316+
some information may be different.
317+
"""
318+
for testres_tx in testmempoolaccept_result:
319+
# Grab this result from the submitpackage_result
320+
submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]]
321+
assert_equal(submitres_tx["txid"], testres_tx["txid"])
322+
# No "allowed" if the tx was already in the mempool
323+
if "allowed" in testres_tx and testres_tx["allowed"]:
324+
assert_equal(submitres_tx["vsize"], testres_tx["vsize"])
325+
assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"])
326+
entry_info = node.getmempoolentry(submitres_tx["txid"])
327+
assert_equal(submitres_tx["vsize"], entry_info["vsize"])
328+
assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"])
329+
330+
def test_submit_child_with_parents(self, num_parents, partial_submit):
331+
node = self.nodes[0]
332+
peer = node.add_p2p_connection(P2PTxInvStore())
333+
# Test a package with num_parents parents and 1 child transaction.
334+
package_hex = []
335+
package_txns = []
336+
values = []
337+
scripts = []
338+
for _ in range(num_parents):
339+
parent_coin = self.coins.pop()
340+
value = parent_coin["amount"]
341+
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value)
342+
package_hex.append(txhex)
343+
package_txns.append(tx)
344+
values.append(value)
345+
scripts.append(spk)
346+
if partial_submit and random.choice([True, False]):
347+
node.sendrawtransaction(txhex)
348+
child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts)
349+
package_hex.append(child_hex)
350+
package_txns.append(tx_from_hex(child_hex))
351+
352+
testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex)
353+
submitpackage_result = node.submitpackage(package=package_hex)
354+
355+
# Check that each result is present, with the correct size and fees
356+
for i in range(num_parents + 1):
357+
tx = package_txns[i]
358+
wtxid = tx.getwtxid()
359+
assert wtxid in submitpackage_result["tx-results"]
360+
tx_result = submitpackage_result["tx-results"][wtxid]
361+
assert_equal(tx_result, {
362+
"txid": tx.rehash(),
363+
"vsize": tx.get_vsize(),
364+
"fees": {
365+
"base": DEFAULT_FEE,
366+
}
367+
})
368+
369+
# submitpackage result should be consistent with testmempoolaccept and getmempoolentry
370+
self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result)
371+
372+
# Package feerate is calculated for the remaining transactions after deduplication and
373+
# individual submission. If only 0 or 1 transaction is left, e.g. because all transactions
374+
# had high-feerates or were already in the mempool, no package feerate is provided.
375+
# In this case, since all of the parents have high fees, each is accepted individually.
376+
assert "package-feerate" not in submitpackage_result
377+
378+
# The node should announce each transaction. No guarantees for propagation.
379+
peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
380+
self.generate(node, 1)
381+
382+
383+
def test_submit_cpfp(self):
384+
node = self.nodes[0]
385+
peer = node.add_p2p_connection(P2PTxInvStore())
386+
387+
# 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is
388+
# fee-bumped by the child.
389+
coin_rich = self.coins.pop()
390+
coin_poor = self.coins.pop()
391+
tx_rich, hex_rich, value_rich, spk_rich = make_chain(node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"])
392+
tx_poor, hex_poor, value_poor, spk_poor = make_chain(node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0)
393+
package_txns = [tx_rich, tx_poor]
394+
hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor])
395+
tx_child = tx_from_hex(hex_child)
396+
package_txns.append(tx_child)
397+
398+
submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child])
399+
400+
rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()]
401+
poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()]
402+
child_result = submitpackage_result["tx-results"][tx_child.getwtxid()]
403+
assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
404+
assert_equal(poor_parent_result["fees"]["base"], 0)
405+
assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
406+
# Package feerate is calculated for the remaining transactions after deduplication and
407+
# individual submission. Since this package had a 0-fee parent, package feerate must have
408+
# been used and returned.
409+
assert "package-feerate" in submitpackage_result
410+
assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"])
411+
412+
# The node will broadcast each transaction, still abiding by its peer's fee filter
413+
peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns])
414+
self.generate(node, 1)
415+
416+
417+
def test_submitpackage(self):
418+
node = self.nodes[0]
419+
420+
self.log.info("Submitpackage valid packages with 1 child and some number of parents")
421+
for num_parents in [1, 2, 24]:
422+
self.test_submit_child_with_parents(num_parents, False)
423+
self.test_submit_child_with_parents(num_parents, True)
424+
425+
self.log.info("Submitpackage valid packages with CPFP")
426+
self.test_submit_cpfp()
427+
428+
self.log.info("Submitpackage only allows packages of 1 child with its parents")
429+
# Chain of 3 transactions has too many generations
430+
chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3)
431+
assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)
432+
433+
309434
if __name__ == "__main__":
310435
RPCPackagesTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
'wallet_address_types.py --descriptors',
131131
'feature_bip68_sequence.py',
132132
'p2p_feefilter.py',
133+
'rpc_packages.py',
133134
'feature_reindex.py',
134135
'feature_abortnode.py',
135136
# vv Tests less than 30s vv
@@ -230,7 +231,6 @@
230231
'mempool_packages.py',
231232
'mempool_package_onemore.py',
232233
'rpc_createmultisig.py',
233-
'rpc_packages.py',
234234
'mempool_package_limits.py',
235235
'feature_versionbits_warning.py',
236236
'rpc_preciousblock.py',

0 commit comments

Comments
 (0)