mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-10 10:52:31 -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,
|
CTxInWitness,
|
||||||
tx_from_hex,
|
tx_from_hex,
|
||||||
)
|
)
|
||||||
|
from test_framework.p2p import P2PTxInvStore
|
||||||
from test_framework.script import (
|
from test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_TRUE,
|
OP_TRUE,
|
||||||
)
|
)
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
assert_fee_amount,
|
||||||
|
assert_raises_rpc_error,
|
||||||
)
|
)
|
||||||
from test_framework.wallet import (
|
from test_framework.wallet import (
|
||||||
create_child_with_parents,
|
create_child_with_parents,
|
||||||
create_raw_chain,
|
create_raw_chain,
|
||||||
|
DEFAULT_FEE,
|
||||||
make_chain,
|
make_chain,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,7 +55,7 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||||
self.address = node.get_deterministic_priv_key().address
|
self.address = node.get_deterministic_priv_key().address
|
||||||
self.coins = []
|
self.coins = []
|
||||||
# The last 100 coinbase transactions are premature
|
# 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]
|
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
|
||||||
self.coins.append({
|
self.coins.append({
|
||||||
"txid": coinbase["txid"],
|
"txid": coinbase["txid"],
|
||||||
|
@ -82,7 +86,7 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||||
self.test_multiple_parents()
|
self.test_multiple_parents()
|
||||||
self.test_conflicting()
|
self.test_conflicting()
|
||||||
self.test_rbf()
|
self.test_rbf()
|
||||||
|
self.test_submitpackage()
|
||||||
|
|
||||||
def test_independent(self):
|
def test_independent(self):
|
||||||
self.log.info("Test multiple independent transactions in a package")
|
self.log.info("Test multiple independent transactions in a package")
|
||||||
|
@ -132,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def test_chain(self):
|
def test_chain(self):
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
first_coin = self.coins.pop()
|
(chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys)
|
||||||
(chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys)
|
|
||||||
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
|
self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency")
|
||||||
assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]),
|
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]])
|
[{"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)
|
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__":
|
if __name__ == "__main__":
|
||||||
RPCPackagesTest().main()
|
RPCPackagesTest().main()
|
||||||
|
|
|
@ -130,6 +130,7 @@ BASE_SCRIPTS = [
|
||||||
'wallet_address_types.py --descriptors',
|
'wallet_address_types.py --descriptors',
|
||||||
'feature_bip68_sequence.py',
|
'feature_bip68_sequence.py',
|
||||||
'p2p_feefilter.py',
|
'p2p_feefilter.py',
|
||||||
|
'rpc_packages.py',
|
||||||
'feature_reindex.py',
|
'feature_reindex.py',
|
||||||
'feature_abortnode.py',
|
'feature_abortnode.py',
|
||||||
# vv Tests less than 30s vv
|
# vv Tests less than 30s vv
|
||||||
|
@ -230,7 +231,6 @@ BASE_SCRIPTS = [
|
||||||
'mempool_packages.py',
|
'mempool_packages.py',
|
||||||
'mempool_package_onemore.py',
|
'mempool_package_onemore.py',
|
||||||
'rpc_createmultisig.py',
|
'rpc_createmultisig.py',
|
||||||
'rpc_packages.py',
|
|
||||||
'mempool_package_limits.py',
|
'mempool_package_limits.py',
|
||||||
'feature_versionbits_warning.py',
|
'feature_versionbits_warning.py',
|
||||||
'rpc_preciousblock.py',
|
'rpc_preciousblock.py',
|
||||||
|
|
Loading…
Add table
Reference in a new issue