test: refactor RPCPackagesTest to use MiniWallet

This commit is contained in:
w0xlt 2022-09-02 12:54:26 -03:00
parent ef5bb742f0
commit 17cad44851
2 changed files with 129 additions and 223 deletions

View File

@ -7,31 +7,24 @@
from decimal import Decimal from decimal import Decimal
import random import random
from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import ( from test_framework.messages import (
MAX_BIP125_RBF_SEQUENCE, MAX_BIP125_RBF_SEQUENCE,
COIN,
CTxInWitness,
tx_from_hex, tx_from_hex,
) )
from test_framework.p2p import P2PTxInvStore from test_framework.p2p import P2PTxInvStore
from test_framework.script import ( from test_framework.test_framework import BitcoinTestFramework
CScript,
OP_TRUE,
)
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
assert_fee_amount, assert_fee_amount,
assert_raises_rpc_error, assert_raises_rpc_error,
) )
from test_framework.wallet import ( from test_framework.wallet import (
create_child_with_parents,
create_raw_chain,
DEFAULT_FEE, DEFAULT_FEE,
make_chain, MiniWallet,
) )
class RPCPackagesTest(BitcoinTestFramework): class RPCPackagesTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
@ -49,38 +42,38 @@ class RPCPackagesTest(BitcoinTestFramework):
assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package)) assert_equal(shuffled_testres, self.nodes[0].testmempoolaccept(shuffled_package))
def run_test(self): def run_test(self):
self.log.info("Generate blocks to create UTXOs")
node = self.nodes[0] node = self.nodes[0]
self.privkeys = [node.get_deterministic_priv_key().key]
self.address = node.get_deterministic_priv_key().address # get an UTXO that requires signature to be spent
self.coins = [] deterministic_address = node.get_deterministic_priv_key().address
# The last 100 coinbase transactions are premature blockhash = self.generatetoaddress(node, 1, deterministic_address)[0]
for b in self.generatetoaddress(node, 220, self.address)[:-100]: coinbase = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] coin = {
self.coins.append({
"txid": coinbase["txid"], "txid": coinbase["txid"],
"amount": coinbase["vout"][0]["value"], "amount": coinbase["vout"][0]["value"],
"scriptPubKey": coinbase["vout"][0]["scriptPubKey"], "scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
}) "vout": 0,
"height": 0
}
self.wallet = MiniWallet(self.nodes[0])
self.generate(self.wallet, COINBASE_MATURITY + 100) # blocks generated for inputs
self.log.info("Create some transactions")
# Create some transactions that can be reused throughout the test. Never submit these to mempool. # Create some transactions that can be reused throughout the test. Never submit these to mempool.
self.independent_txns_hex = [] self.independent_txns_hex = []
self.independent_txns_testres = [] self.independent_txns_testres = []
for _ in range(3): for _ in range(3):
coin = self.coins.pop() tx_hex = self.wallet.create_self_transfer(fee_rate=Decimal("0.0001"))["hex"]
rawtx = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], testres = self.nodes[0].testmempoolaccept([tx_hex])
{self.address : coin["amount"] - Decimal("0.0001")})
signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
assert signedtx["complete"]
testres = node.testmempoolaccept([signedtx["hex"]])
assert testres[0]["allowed"] assert testres[0]["allowed"]
self.independent_txns_hex.append(signedtx["hex"]) self.independent_txns_hex.append(tx_hex)
# testmempoolaccept returns a list of length one, avoid creating a 2D list # testmempoolaccept returns a list of length one, avoid creating a 2D list
self.independent_txns_testres.append(testres[0]) self.independent_txns_testres.append(testres[0])
self.independent_txns_testres_blank = [{ self.independent_txns_testres_blank = [{
"txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres] "txid": res["txid"], "wtxid": res["wtxid"]} for res in self.independent_txns_testres]
self.test_independent() self.test_independent(coin)
self.test_chain() self.test_chain()
self.test_multiple_children() self.test_multiple_children()
self.test_multiple_parents() self.test_multiple_parents()
@ -88,14 +81,15 @@ class RPCPackagesTest(BitcoinTestFramework):
self.test_rbf() self.test_rbf()
self.test_submitpackage() self.test_submitpackage()
def test_independent(self): def test_independent(self, coin):
self.log.info("Test multiple independent transactions in a package") self.log.info("Test multiple independent transactions in a package")
node = self.nodes[0] node = self.nodes[0]
# For independent transactions, order doesn't matter. # For independent transactions, order doesn't matter.
self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres) self.assert_testres_equal(self.independent_txns_hex, self.independent_txns_testres)
self.log.info("Test an otherwise valid package with an extra garbage tx appended") self.log.info("Test an otherwise valid package with an extra garbage tx appended")
garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {self.address: 1}) address = node.get_deterministic_priv_key().address
garbage_tx = node.createrawtransaction([{"txid": "00" * 32, "vout": 5}], {address: 1})
tx = tx_from_hex(garbage_tx) tx = tx_from_hex(garbage_tx)
# Only the txid and wtxids are returned because validation is incomplete for the independent txns. # Only the txid and wtxids are returned because validation is incomplete for the independent txns.
# Package validation is atomic: if the node cannot find a UTXO for any single tx in the package, # Package validation is atomic: if the node cannot find a UTXO for any single tx in the package,
@ -105,9 +99,8 @@ class RPCPackagesTest(BitcoinTestFramework):
self.assert_testres_equal(package_bad, testres_bad) self.assert_testres_equal(package_bad, testres_bad)
self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully") self.log.info("Check testmempoolaccept tells us when some transactions completed validation successfully")
coin = self.coins.pop()
tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], tx_bad_sig_hex = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}],
{self.address : coin["amount"] - Decimal("0.0001")}) {address : coin["amount"] - Decimal("0.0001")})
tx_bad_sig = tx_from_hex(tx_bad_sig_hex) tx_bad_sig = tx_from_hex(tx_bad_sig_hex)
testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex]) testres_bad_sig = node.testmempoolaccept(self.independent_txns_hex + [tx_bad_sig_hex])
# By the time the signature for the last transaction is checked, all the other transactions # By the time the signature for the last transaction is checked, all the other transactions
@ -120,23 +113,22 @@ class RPCPackagesTest(BitcoinTestFramework):
}]) }])
self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate") self.log.info("Check testmempoolaccept reports txns in packages that exceed max feerate")
coin = self.coins.pop() tx_high_fee = self.wallet.create_self_transfer(fee=Decimal("0.999"))
tx_high_fee_raw = node.createrawtransaction([{"txid": coin["txid"], "vout": 0}], testres_high_fee = node.testmempoolaccept([tx_high_fee["hex"]])
{self.address : coin["amount"] - Decimal("0.999")})
tx_high_fee_signed = node.signrawtransactionwithkey(hexstring=tx_high_fee_raw, privkeys=self.privkeys)
assert tx_high_fee_signed["complete"]
tx_high_fee = tx_from_hex(tx_high_fee_signed["hex"])
testres_high_fee = node.testmempoolaccept([tx_high_fee_signed["hex"]])
assert_equal(testres_high_fee, [ assert_equal(testres_high_fee, [
{"txid": tx_high_fee.rehash(), "wtxid": tx_high_fee.getwtxid(), "allowed": False, "reject-reason": "max-fee-exceeded"} {"txid": tx_high_fee["txid"], "wtxid": tx_high_fee["wtxid"], "allowed": False, "reject-reason": "max-fee-exceeded"}
]) ])
package_high_fee = [tx_high_fee_signed["hex"]] + self.independent_txns_hex package_high_fee = [tx_high_fee["hex"]] + self.independent_txns_hex
testres_package_high_fee = node.testmempoolaccept(package_high_fee) testres_package_high_fee = node.testmempoolaccept(package_high_fee)
assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank) assert_equal(testres_package_high_fee, testres_high_fee + self.independent_txns_testres_blank)
def test_chain(self): def test_chain(self):
node = self.nodes[0] node = self.nodes[0]
(chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys)
chain = self.wallet.create_self_transfer_chain(chain_length=25)
chain_hex = chain["chain_hex"]
chain_txns = chain["chain_txns"]
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]])
@ -158,78 +150,57 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_multiple_children(self): def test_multiple_children(self):
node = self.nodes[0] node = self.nodes[0]
self.log.info("Testmempoolaccept a package in which a transaction has two children within the package") self.log.info("Testmempoolaccept a package in which a transaction has two children within the package")
first_coin = self.coins.pop()
value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs
inputs = [{"txid": first_coin["txid"], "vout": 0}]
outputs = [{self.address : value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : value}]
rawtx = node.createrawtransaction(inputs, outputs)
parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) parent_tx = self.wallet.create_self_transfer_multi(num_outputs=2)
assert parent_signed["complete"] assert node.testmempoolaccept([parent_tx["hex"]])[0]["allowed"]
parent_tx = tx_from_hex(parent_signed["hex"])
parent_txid = parent_tx.rehash()
assert node.testmempoolaccept([parent_signed["hex"]])[0]["allowed"]
parent_locking_script_a = parent_tx.vout[0].scriptPubKey.hex()
child_value = value - Decimal("0.0001")
# Child A # Child A
(_, tx_child_a_hex, _, _) = make_chain(node, self.address, self.privkeys, parent_txid, child_value, 0, parent_locking_script_a) child_a_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][0])
assert not node.testmempoolaccept([tx_child_a_hex])[0]["allowed"] assert not node.testmempoolaccept([child_a_tx["hex"]])[0]["allowed"]
# Child B # Child B
rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : child_value}) child_b_tx = self.wallet.create_self_transfer(utxo_to_spend=parent_tx["new_utxos"][1])
tx_child_b = tx_from_hex(rawtx_b) assert not node.testmempoolaccept([child_b_tx["hex"]])[0]["allowed"]
tx_child_b.wit.vtxinwit = [CTxInWitness()]
tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
tx_child_b_hex = tx_child_b.serialize().hex()
assert not node.testmempoolaccept([tx_child_b_hex])[0]["allowed"]
self.log.info("Testmempoolaccept with entire package, should work with children in either order") self.log.info("Testmempoolaccept with entire package, should work with children in either order")
testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]) testres_multiple_ab = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]])
testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_signed["hex"], tx_child_b_hex, tx_child_a_hex]) testres_multiple_ba = node.testmempoolaccept(rawtxs=[parent_tx["hex"], child_b_tx["hex"], child_a_tx["hex"]])
assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba]) assert all([testres["allowed"] for testres in testres_multiple_ab + testres_multiple_ba])
testres_single = [] testres_single = []
# Test accept and then submit each one individually, which should be identical to package testaccept # Test accept and then submit each one individually, which should be identical to package testaccept
for rawtx in [parent_signed["hex"], tx_child_a_hex, tx_child_b_hex]: for rawtx in [parent_tx["hex"], child_a_tx["hex"], child_b_tx["hex"]]:
testres = node.testmempoolaccept([rawtx]) testres = node.testmempoolaccept([rawtx])
testres_single.append(testres[0]) testres_single.append(testres[0])
# Submit the transaction now so its child should have no problem validating # Submit the transaction now so its child should have no problem validating
node.sendrawtransaction(rawtx) node.sendrawtransaction(rawtx)
assert_equal(testres_single, testres_multiple_ab) assert_equal(testres_single, testres_multiple_ab)
def test_multiple_parents(self): def test_multiple_parents(self):
node = self.nodes[0] node = self.nodes[0]
self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package") self.log.info("Testmempoolaccept a package in which a transaction has multiple parents within the package")
for num_parents in [2, 10, 24]: for num_parents in [2, 10, 24]:
# Test a package with num_parents parents and 1 child transaction. # Test a package with num_parents parents and 1 child transaction.
parent_coins = []
package_hex = [] package_hex = []
parents_tx = []
values = []
parent_locking_scripts = []
for _ in range(num_parents): for _ in range(num_parents):
parent_coin = self.coins.pop() # Package accept should work with the parents in any order (as long as parents come before child)
value = parent_coin["amount"] parent_tx = self.wallet.create_self_transfer()
(tx, txhex, value, parent_locking_script) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value) parent_coins.append(parent_tx["new_utxo"])
package_hex.append(txhex) package_hex.append(parent_tx["hex"])
parents_tx.append(tx)
values.append(value) child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_coins, fee_per_output=2000)
parent_locking_scripts.append(parent_locking_script)
child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, parent_locking_scripts)
# Package accept should work with the parents in any order (as long as parents come before child)
for _ in range(10): for _ in range(10):
random.shuffle(package_hex) random.shuffle(package_hex)
testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_hex]) testres_multiple = node.testmempoolaccept(rawtxs=package_hex + [child_tx['hex']])
assert all([testres["allowed"] for testres in testres_multiple]) assert all([testres["allowed"] for testres in testres_multiple])
testres_single = [] testres_single = []
# Test accept and then submit each one individually, which should be identical to package testaccept # Test accept and then submit each one individually, which should be identical to package testaccept
for rawtx in package_hex + [child_hex]: for rawtx in package_hex + [child_tx["hex"]]:
testres_single.append(node.testmempoolaccept([rawtx])[0]) testres_single.append(node.testmempoolaccept([rawtx])[0])
# Submit the transaction now so its child should have no problem validating # Submit the transaction now so its child should have no problem validating
node.sendrawtransaction(rawtx) node.sendrawtransaction(rawtx)
@ -237,77 +208,60 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_conflicting(self): def test_conflicting(self):
node = self.nodes[0] node = self.nodes[0]
prevtx = self.coins.pop() coin = self.wallet.get_utxo()
inputs = [{"txid": prevtx["txid"], "vout": 0}]
output1 = {node.get_deterministic_priv_key().address: 50 - 0.00125}
output2 = {ADDRESS_BCRT1_P2WSH_OP_TRUE: 50 - 0.00125}
# tx1 and tx2 share the same inputs # tx1 and tx2 share the same inputs
rawtx1 = node.createrawtransaction(inputs, output1) tx1 = self.wallet.create_self_transfer(utxo_to_spend=coin)
rawtx2 = node.createrawtransaction(inputs, output2) tx2 = self.wallet.create_self_transfer(utxo_to_spend=coin)
signedtx1 = node.signrawtransactionwithkey(hexstring=rawtx1, privkeys=self.privkeys)
signedtx2 = node.signrawtransactionwithkey(hexstring=rawtx2, privkeys=self.privkeys)
tx1 = tx_from_hex(signedtx1["hex"])
tx2 = tx_from_hex(signedtx2["hex"])
assert signedtx1["complete"]
assert signedtx2["complete"]
# Ensure tx1 and tx2 are valid by themselves # Ensure tx1 and tx2 are valid by themselves
assert node.testmempoolaccept([signedtx1["hex"]])[0]["allowed"] assert node.testmempoolaccept([tx1["hex"]])[0]["allowed"]
assert node.testmempoolaccept([signedtx2["hex"]])[0]["allowed"] assert node.testmempoolaccept([tx2["hex"]])[0]["allowed"]
self.log.info("Test duplicate transactions in the same package") self.log.info("Test duplicate transactions in the same package")
testres = node.testmempoolaccept([signedtx1["hex"], signedtx1["hex"]]) testres = node.testmempoolaccept([tx1["hex"], tx1["hex"]])
assert_equal(testres, [ assert_equal(testres, [
{"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"}, {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
{"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"} {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"}
]) ])
self.log.info("Test conflicting transactions in the same package") self.log.info("Test conflicting transactions in the same package")
testres = node.testmempoolaccept([signedtx1["hex"], signedtx2["hex"]]) testres = node.testmempoolaccept([tx1["hex"], tx2["hex"]])
assert_equal(testres, [ assert_equal(testres, [
{"txid": tx1.rehash(), "wtxid": tx1.getwtxid(), "package-error": "conflict-in-package"}, {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
{"txid": tx2.rehash(), "wtxid": tx2.getwtxid(), "package-error": "conflict-in-package"} {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"}
]) ])
def test_rbf(self): def test_rbf(self):
node = self.nodes[0] node = self.nodes[0]
coin = self.coins.pop()
inputs = [{"txid": coin["txid"], "vout": 0, "sequence": MAX_BIP125_RBF_SEQUENCE}] coin = self.wallet.get_utxo()
fee = Decimal('0.00125000') fee = Decimal("0.00125000")
output = {node.get_deterministic_priv_key().address: 50 - fee} replaceable_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = fee)
raw_replaceable_tx = node.createrawtransaction(inputs, output) testres_replaceable = node.testmempoolaccept([replaceable_tx["hex"]])
signed_replaceable_tx = node.signrawtransactionwithkey(hexstring=raw_replaceable_tx, privkeys=self.privkeys)
testres_replaceable = node.testmempoolaccept([signed_replaceable_tx["hex"]])
replaceable_tx = tx_from_hex(signed_replaceable_tx["hex"])
assert_equal(testres_replaceable, [ assert_equal(testres_replaceable, [
{"txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), {"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"],
"allowed": True, "vsize": replaceable_tx.get_vsize(), "fees": { "base": fee }} "allowed": True, "vsize": replaceable_tx["tx"].get_vsize(), "fees": { "base": fee }}
]) ])
# Replacement transaction is identical except has double the fee # Replacement transaction is identical except has double the fee
replacement_tx = tx_from_hex(signed_replaceable_tx["hex"]) replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=coin, sequence=MAX_BIP125_RBF_SEQUENCE, fee = 2 * fee)
replacement_tx.vout[0].nValue -= int(fee * COIN) # Doubled fee testres_rbf_conflicting = node.testmempoolaccept([replaceable_tx["hex"], replacement_tx["hex"]])
signed_replacement_tx = node.signrawtransactionwithkey(replacement_tx.serialize().hex(), self.privkeys)
replacement_tx = tx_from_hex(signed_replacement_tx["hex"])
self.log.info("Test that transactions within a package cannot replace each other")
testres_rbf_conflicting = node.testmempoolaccept([signed_replaceable_tx["hex"], signed_replacement_tx["hex"]])
assert_equal(testres_rbf_conflicting, [ assert_equal(testres_rbf_conflicting, [
{"txid": replaceable_tx.rehash(), "wtxid": replaceable_tx.getwtxid(), "package-error": "conflict-in-package"}, {"txid": replaceable_tx["txid"], "wtxid": replaceable_tx["wtxid"], "package-error": "conflict-in-package"},
{"txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "package-error": "conflict-in-package"} {"txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "package-error": "conflict-in-package"}
]) ])
self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF") self.log.info("Test that packages cannot conflict with mempool transactions, even if a valid BIP125 RBF")
node.sendrawtransaction(signed_replaceable_tx["hex"])
testres_rbf_single = node.testmempoolaccept([signed_replacement_tx["hex"]])
# This transaction is a valid BIP125 replace-by-fee # This transaction is a valid BIP125 replace-by-fee
self.wallet.sendrawtransaction(from_node=node, tx_hex=replaceable_tx["hex"])
testres_rbf_single = node.testmempoolaccept([replacement_tx["hex"]])
assert testres_rbf_single[0]["allowed"] assert testres_rbf_single[0]["allowed"]
testres_rbf_package = self.independent_txns_testres_blank + [{ testres_rbf_package = self.independent_txns_testres_blank + [{
"txid": replacement_tx.rehash(), "wtxid": replacement_tx.getwtxid(), "allowed": False, "txid": replacement_tx["txid"], "wtxid": replacement_tx["wtxid"], "allowed": False,
"reject-reason": "bip125-replacement-disallowed" "reject-reason": "bip125-replacement-disallowed"
}] }]
self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package) self.assert_testres_equal(self.independent_txns_hex + [replacement_tx["hex"]], testres_rbf_package)
def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result): def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result):
"""Assert that a successful submitpackage result is consistent with testmempoolaccept """Assert that a successful submitpackage result is consistent with testmempoolaccept
@ -330,36 +284,26 @@ class RPCPackagesTest(BitcoinTestFramework):
def test_submit_child_with_parents(self, num_parents, partial_submit): def test_submit_child_with_parents(self, num_parents, partial_submit):
node = self.nodes[0] node = self.nodes[0]
peer = node.add_p2p_connection(P2PTxInvStore()) 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) package_txns = []
submitpackage_result = node.submitpackage(package=package_hex) for _ in range(num_parents):
parent_tx = self.wallet.create_self_transfer(fee=DEFAULT_FEE)
package_txns.append(parent_tx)
if partial_submit and random.choice([True, False]):
node.sendrawtransaction(parent_tx["hex"])
child_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[tx["new_utxo"] for tx in package_txns], fee_per_output=10000) #DEFAULT_FEE
package_txns.append(child_tx)
testmempoolaccept_result = node.testmempoolaccept(rawtxs=[tx["hex"] for tx in package_txns])
submitpackage_result = node.submitpackage(package=[tx["hex"] for tx in package_txns])
# Check that each result is present, with the correct size and fees # Check that each result is present, with the correct size and fees
for i in range(num_parents + 1): for package_txn in package_txns:
tx = package_txns[i] tx = package_txn["tx"]
wtxid = tx.getwtxid() assert tx.getwtxid() in submitpackage_result["tx-results"]
assert wtxid in submitpackage_result["tx-results"] tx_result = submitpackage_result["tx-results"][tx.getwtxid()]
tx_result = submitpackage_result["tx-results"][wtxid]
assert_equal(tx_result, { assert_equal(tx_result, {
"txid": tx.rehash(), "txid": package_txn["txid"],
"vsize": tx.get_vsize(), "vsize": tx.get_vsize(),
"fees": { "fees": {
"base": DEFAULT_FEE, "base": DEFAULT_FEE,
@ -376,30 +320,25 @@ class RPCPackagesTest(BitcoinTestFramework):
assert "package-feerate" not in submitpackage_result assert "package-feerate" not in submitpackage_result
# The node should announce each transaction. No guarantees for propagation. # The node should announce each transaction. No guarantees for propagation.
peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns]) peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
self.generate(node, 1) self.generate(node, 1)
def test_submit_cpfp(self): def test_submit_cpfp(self):
node = self.nodes[0] node = self.nodes[0]
peer = node.add_p2p_connection(P2PTxInvStore()) peer = node.add_p2p_connection(P2PTxInvStore())
# 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is tx_poor = self.wallet.create_self_transfer(fee=0, fee_rate=0)
# fee-bumped by the child. tx_rich = self.wallet.create_self_transfer(fee=DEFAULT_FEE)
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] 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]) coins = [tx["new_utxo"] for tx in package_txns]
tx_child = tx_from_hex(hex_child) tx_child = self.wallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE
package_txns.append(tx_child) package_txns.append(tx_child)
submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child]) submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])
rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()] rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]
poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()] poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]
child_result = submitpackage_result["tx-results"][tx_child.getwtxid()] child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()]
assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE) assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE)
assert_equal(poor_parent_result["fees"]["base"], 0) assert_equal(poor_parent_result["fees"]["base"], 0)
assert_equal(child_result["fees"]["base"], DEFAULT_FEE) assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
@ -410,10 +349,9 @@ class RPCPackagesTest(BitcoinTestFramework):
assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"]) 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 # 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]) peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
self.generate(node, 1) self.generate(node, 1)
def test_submitpackage(self): def test_submitpackage(self):
node = self.nodes[0] node = self.nodes[0]
@ -427,7 +365,7 @@ class RPCPackagesTest(BitcoinTestFramework):
self.log.info("Submitpackage only allows packages of 1 child with its parents") self.log.info("Submitpackage only allows packages of 1 child with its parents")
# Chain of 3 transactions has too many generations # Chain of 3 transactions has too many generations
chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3) chain_hex = self.wallet.create_self_transfer_chain(chain_length=25)["chain_hex"]
assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex) assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)

View File

@ -32,7 +32,6 @@ from test_framework.messages import (
CTxIn, CTxIn,
CTxInWitness, CTxInWitness,
CTxOut, CTxOut,
tx_from_hex,
) )
from test_framework.script import ( from test_framework.script import (
CScript, CScript,
@ -338,6 +337,28 @@ class MiniWallet:
self.scan_tx(from_node.decoderawtransaction(tx_hex)) self.scan_tx(from_node.decoderawtransaction(tx_hex))
return txid return txid
def create_self_transfer_chain(self, *, chain_length):
"""
Create a "chain" of chain_length transactions. The nth transaction in
the chain is a child of the n-1th transaction and parent of the n+1th transaction.
Returns a dic {"chain_hex": chain_hex, "chain_txns" : chain_txns}
"chain_hex" is a list representing the chain's transactions in hexadecimal.
"chain_txns" is a list representing the chain's transactions in the CTransaction object.
"""
chaintip_utxo = self.get_utxo()
chain_hex = []
chain_txns = []
for _ in range(chain_length):
tx = self.create_self_transfer(utxo_to_spend=chaintip_utxo)
chaintip_utxo = tx["new_utxo"]
chain_hex.append(tx["hex"])
chain_txns.append(tx["tx"])
return {"chain_hex": chain_hex, "chain_txns" : chain_txns}
def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None): def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None):
"""Create and send a "chain" of chain_length transactions. The nth transaction in """Create and send a "chain" of chain_length transactions. The nth transaction in
the chain is a child of the n-1th transaction and parent of the n+1th transaction. the chain is a child of the n-1th transaction and parent of the n+1th transaction.
@ -388,56 +409,3 @@ def address_to_scriptpubkey(address):
# TODO: also support other address formats # TODO: also support other address formats
else: else:
assert False assert False
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
"""Build a transaction that spends parent_txid.vout[n] and produces one output with
amount = parent_value with a fee deducted.
Return tuple (CTransaction object, raw hex, nValue, scriptPubKey of the output created).
"""
inputs = [{"txid": parent_txid, "vout": n}]
my_value = parent_value - fee
outputs = {address : my_value}
rawtx = node.createrawtransaction(inputs, outputs)
prevtxs = [{
"txid": parent_txid,
"vout": n,
"scriptPubKey": parent_locking_script,
"amount": parent_value,
}] if parent_locking_script else None
signedtx = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=privkeys, prevtxs=prevtxs)
assert signedtx["complete"]
tx = tx_from_hex(signedtx["hex"])
return (tx, signedtx["hex"], my_value, tx.vout[0].scriptPubKey.hex())
def create_child_with_parents(node, address, privkeys, parents_tx, values, locking_scripts, fee=DEFAULT_FEE):
"""Creates a transaction that spends the first output of each parent in parents_tx."""
num_parents = len(parents_tx)
total_value = sum(values)
inputs = [{"txid": tx.rehash(), "vout": 0} for tx in parents_tx]
outputs = {address : total_value - fee}
rawtx_child = node.createrawtransaction(inputs, outputs)
prevtxs = []
for i in range(num_parents):
prevtxs.append({"txid": parents_tx[i].rehash(), "vout": 0, "scriptPubKey": locking_scripts[i], "amount": values[i]})
signedtx_child = node.signrawtransactionwithkey(hexstring=rawtx_child, privkeys=privkeys, prevtxs=prevtxs)
assert signedtx_child["complete"]
return signedtx_child["hex"]
def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
"""Helper function: create a "chain" of chain_length transactions. The nth transaction in the
chain is a child of the n-1th transaction and parent of the n+1th transaction.
"""
parent_locking_script = None
txid = first_coin["txid"]
chain_hex = []
chain_txns = []
value = first_coin["amount"]
for _ in range(chain_length):
(tx, txhex, value, parent_locking_script) = make_chain(node, address, privkeys, txid, value, 0, parent_locking_script)
txid = tx.rehash()
chain_hex.append(txhex)
chain_txns.append(tx)
return (chain_hex, chain_txns)