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:
parent
fa076515b0
commit
e866f0d066
2 changed files with 130 additions and 5 deletions
|
@ -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()
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Add table
Reference in a new issue