0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-08 10:31:50 -05:00

[functional test] submitrawpackage RPC

This commit is contained in:
glozow 2021-07-20 11:49:43 +01:00
parent fa076515b0
commit e866f0d066
2 changed files with 130 additions and 5 deletions

View file

@ -15,16 +15,20 @@ from test_framework.messages import (
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,
)
@ -51,7 +55,7 @@ class RPCPackagesTest(BitcoinTestFramework):
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"],
@ -82,7 +86,7 @@ class RPCPackagesTest(BitcoinTestFramework):
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")
@ -132,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework):
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]])
@ -306,5 +309,127 @@ class RPCPackagesTest(BitcoinTestFramework):
}]
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()

View file

@ -130,6 +130,7 @@ BASE_SCRIPTS = [
'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
@ -230,7 +231,6 @@ BASE_SCRIPTS = [
'mempool_packages.py',
'mempool_package_onemore.py',
'rpc_createmultisig.py',
'rpc_packages.py',
'mempool_package_limits.py',
'feature_versionbits_warning.py',
'rpc_preciousblock.py',