0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-03-05 14:06:27 -05:00

test: use MiniWallet to simplify mempool_package_limits.py tests

Moved `bulk_transaction` into MiniWallet class as `_bulk_tx` private
helper method to be used when the newly added `target_weight` option is
passed to `create_self_transfer*`
This commit is contained in:
Andreas Kouloumos 2022-06-15 15:43:56 +03:00
parent ce3b75690d
commit 1d6b438ef0
2 changed files with 94 additions and 249 deletions

View file

@ -6,28 +6,16 @@
from decimal import Decimal from decimal import Decimal
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.test_framework import BitcoinTestFramework
from test_framework.messages import ( from test_framework.messages import (
COIN, COIN,
CTransaction,
CTxInWitness,
tx_from_hex,
WITNESS_SCALE_FACTOR, WITNESS_SCALE_FACTOR,
) )
from test_framework.script import (
CScript,
OP_TRUE,
)
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
) )
from test_framework.wallet import ( from test_framework.wallet import MiniWallet
bulk_transaction,
create_child_with_parents,
make_chain,
DEFAULT_FEE,
)
class MempoolPackageLimitsTest(BitcoinTestFramework): class MempoolPackageLimitsTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@ -35,19 +23,10 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
self.setup_clean_chain = True self.setup_clean_chain = True
def run_test(self): def run_test(self):
self.log.info("Generate blocks to create UTXOs") self.wallet = MiniWallet(self.nodes[0])
node = self.nodes[0] # Add enough mature utxos to the wallet so that all txs spend confirmed coins.
self.privkeys = [node.get_deterministic_priv_key().key] self.generate(self.wallet, 35)
self.address = node.get_deterministic_priv_key().address self.generate(self.nodes[0], COINBASE_MATURITY)
self.coins = []
# The last 100 coinbase transactions are premature
for b in self.generatetoaddress(node, 200, self.address)[:100]:
coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0]
self.coins.append({
"txid": coinbase["txid"],
"amount": coinbase["vout"][0]["value"],
"scriptPubKey": coinbase["vout"][0]["scriptPubKey"],
})
self.test_chain_limits() self.test_chain_limits()
self.test_desc_count_limits() self.test_desc_count_limits()
@ -64,22 +43,19 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
def test_chain_limits_helper(self, mempool_count, package_count): def test_chain_limits_helper(self, mempool_count, package_count):
node = self.nodes[0] node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
first_coin = self.coins.pop()
spk = None
txid = first_coin["txid"]
chain_hex = [] chain_hex = []
chain_txns = []
value = first_coin["amount"]
chaintip_utxo = None
for i in range(mempool_count + package_count): for i in range(mempool_count + package_count):
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
txid = tx.rehash() chaintip_utxo = tx["new_utxo"]
if i < mempool_count: if i < mempool_count:
node.sendrawtransaction(txhex) # in-mempool transactions
txid = self.wallet.sendrawtransaction(from_node=node, tx_hex=tx["hex"])
assert_equal(node.getmempoolentry(txid)["ancestorcount"], i + 1) assert_equal(node.getmempoolentry(txid)["ancestorcount"], i + 1)
else: else:
chain_hex.append(txhex) # in-package transactions
chain_txns.append(tx) chain_hex.append(tx["hex"])
testres_too_long = node.testmempoolaccept(rawtxs=chain_hex) testres_too_long = node.testmempoolaccept(rawtxs=chain_hex)
for txres in testres_too_long: for txres in testres_too_long:
assert_equal(txres["package-error"], "package-mempool-limits") assert_equal(txres["package-error"], "package-mempool-limits")
@ -125,49 +101,28 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages") self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages")
# Top parent in mempool, M1 # Top parent in mempool, M1
first_coin = self.coins.pop() m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
parent_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 : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}]
rawtx = node.createrawtransaction(inputs, outputs)
parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
assert parent_signed["complete"]
parent_tx = tx_from_hex(parent_signed["hex"])
parent_txid = parent_tx.rehash()
node.sendrawtransaction(parent_signed["hex"])
package_hex = [] package_hex = []
# Chain A # Chain A
spk = parent_tx.vout[0].scriptPubKey.hex() chain_a_utxo = m1_utxos[0]
value = parent_value
txid = parent_txid
for i in range(12): for i in range(12):
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) tx = self.wallet.create_self_transfer(utxo_to_spend=chain_a_utxo)
txid = tx.rehash() chain_a_utxo = tx["new_utxo"]
if i < 11: # M2a... M12a if i < 11: # M2a... M12a
node.sendrawtransaction(txhex) self.wallet.sendrawtransaction(from_node=node, tx_hex=tx["hex"])
else: # Pa else: # Pa
package_hex.append(txhex) package_hex.append(tx["hex"])
# Chain B # Chain B
value = parent_value - Decimal("0.0001") chain_b_utxo = m1_utxos[1]
rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : value}) for i in range(13):
tx_child_b = tx_from_hex(rawtx_b) # M2b tx = self.wallet.create_self_transfer(utxo_to_spend=chain_b_utxo)
tx_child_b.wit.vtxinwit = [CTxInWitness()] chain_b_utxo = tx["new_utxo"]
tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] if i < 12: # M3b... M13b
tx_child_b_hex = tx_child_b.serialize().hex() self.wallet.sendrawtransaction(from_node=node, tx_hex=tx["hex"])
node.sendrawtransaction(tx_child_b_hex)
spk = tx_child_b.vout[0].scriptPubKey.hex()
txid = tx_child_b.rehash()
for i in range(12):
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
txid = tx.rehash()
if i < 11: # M3b... M13b
node.sendrawtransaction(txhex)
else: # Pb else: # Pb
package_hex.append(txhex) package_hex.append(tx["hex"])
assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex)) assert_equal(2, len(package_hex))
@ -200,41 +155,20 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0] node = self.nodes[0]
package_hex = [] package_hex = []
# M1 # M1
first_coin_a = self.coins.pop() m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos']
parent_value = (first_coin_a["amount"] - DEFAULT_FEE) / 2 # Deduct reasonable fee and make 2 outputs
inputs = [{"txid": first_coin_a["txid"], "vout": 0}]
outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}]
rawtx = node.createrawtransaction(inputs, outputs)
parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys)
assert parent_signed["complete"]
parent_tx = tx_from_hex(parent_signed["hex"])
parent_txid = parent_tx.rehash()
node.sendrawtransaction(parent_signed["hex"])
# Chain M2...M24 # Chain M2...M24
spk = parent_tx.vout[0].scriptPubKey.hex() chain_utxo = m1_utxos[0]
value = parent_value for _ in range(23): # M2...M24
txid = parent_txid chain_utxo = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=chain_utxo)["new_utxo"]
for i in range(23): # M2...M24
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk)
txid = tx.rehash()
node.sendrawtransaction(txhex)
# P1 # P1
value_p1 = (parent_value - DEFAULT_FEE) p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1])
rawtx_p1 = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], [{self.address : value_p1}]) package_hex.append(p1_tx["hex"])
tx_child_p1 = tx_from_hex(rawtx_p1)
tx_child_p1.wit.vtxinwit = [CTxInWitness()]
tx_child_p1.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
tx_child_p1_hex = tx_child_p1.serialize().hex()
txid_child_p1 = tx_child_p1.rehash()
package_hex.append(tx_child_p1_hex)
tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex()
# P2 # P2
(_, tx_child_p2_hex, _, _) = make_chain(node, self.address, self.privkeys, txid_child_p1, value_p1, 0, tx_child_p1_spk) p2_tx = self.wallet.create_self_transfer(utxo_to_spend=p1_tx["new_utxo"])
package_hex.append(tx_child_p2_hex) package_hex.append(p2_tx["hex"])
assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex)) assert_equal(2, len(package_hex))
@ -266,32 +200,25 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0] node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
package_hex = [] package_hex = []
parents_tx = [] pc_parent_utxos = []
values = []
scripts = []
self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
# Two chains of 13 transactions each # Two chains of 13 transactions each
for _ in range(2): for _ in range(2):
spk = None chaintip_utxo = None
top_coin = self.coins.pop()
txid = top_coin["txid"]
value = top_coin["amount"]
for i in range(13): for i in range(13):
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo)
txid = tx.rehash() chaintip_utxo = tx["new_utxo"]
if i < 12: if i < 12:
node.sendrawtransaction(txhex) self.wallet.sendrawtransaction(from_node=node, tx_hex=tx["hex"])
else: # Save the 13th transaction for the package else: # Save the 13th transaction for the package
package_hex.append(txhex) package_hex.append(tx["hex"])
parents_tx.append(tx) pc_parent_utxos.append(chaintip_utxo)
scripts.append(spk)
values.append(value)
# Child Pc # Child Pc
child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
package_hex.append(child_hex) package_hex.append(pc_hex)
assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(24, node.getmempoolinfo()["size"])
assert_equal(3, len(package_hex)) assert_equal(3, len(package_hex))
@ -321,45 +248,32 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
""" """
node = self.nodes[0] node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
parents_tx = [] pc_parent_utxos = []
values = []
scripts = []
self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages")
# Two chains of 12 transactions each # Two chains of 12 transactions each
for _ in range(2): for _ in range(2):
spk = None chaintip_utxo = None
top_coin = self.coins.pop()
txid = top_coin["txid"]
value = top_coin["amount"]
for i in range(12): for i in range(12):
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) chaintip_utxo = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=chaintip_utxo)["new_utxo"]
txid = tx.rehash()
value -= Decimal("0.0001")
node.sendrawtransaction(txhex)
if i == 11: if i == 11:
# last 2 transactions will be the parents of Pc # last 2 transactions will be the parents of Pc
parents_tx.append(tx) pc_parent_utxos.append(chaintip_utxo)
values.append(value)
scripts.append(spk)
# Child Pc # Child Pc
pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)
pc_tx = tx_from_hex(pc_hex)
pc_value = sum(values) - Decimal("0.0002")
pc_spk = pc_tx.vout[0].scriptPubKey.hex()
# Child Pd # Child Pd
(_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk) pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0])
assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(24, node.getmempoolinfo()["size"])
testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) testres_too_long = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])
for txres in testres_too_long: for txres in testres_too_long:
assert_equal(txres["package-error"], "package-mempool-limits") assert_equal(txres["package-error"], "package-mempool-limits")
# Clear mempool and check that the package passes now # Clear mempool and check that the package passes now
self.generate(node, 1) self.generate(node, 1)
assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])]) assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])])
def test_anc_count_limits_bushy(self): def test_anc_count_limits_bushy(self):
"""Create a tree with 20 transactions in the mempool and 6 in the package: """Create a tree with 20 transactions in the mempool and 6 in the package:
@ -375,31 +289,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
node = self.nodes[0] node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
package_hex = [] package_hex = []
parent_txns = [] pc_parent_utxos = []
parent_values = []
scripts = []
for _ in range(5): # Make package transactions P0 ... P4 for _ in range(5): # Make package transactions P0 ... P4
gp_tx = [] pc_grandparent_utxos = []
gp_values = []
gp_scripts = []
for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4) for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4)
parent_coin = self.coins.pop() pc_grandparent_utxos.append(self.wallet.send_self_transfer(from_node=node)["new_utxo"])
value = parent_coin["amount"]
txid = parent_coin["txid"]
(tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value)
gp_tx.append(tx)
gp_values.append(value)
gp_scripts.append(spk)
node.sendrawtransaction(txhex)
# Package transaction Pi # Package transaction Pi
pi_hex = create_child_with_parents(node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts) pi_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_grandparent_utxos)
package_hex.append(pi_hex) package_hex.append(pi_tx["hex"])
pi_tx = tx_from_hex(pi_hex) pc_parent_utxos.append(pi_tx["new_utxos"][0])
parent_txns.append(pi_tx)
parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN)
scripts.append(pi_tx.vout[0].scriptPubKey.hex())
# Package transaction PC # Package transaction PC
package_hex.append(create_child_with_parents(node, self.address, self.privkeys, parent_txns, parent_values, scripts)) pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"]
package_hex.append(pc_hex)
assert_equal(20, node.getmempoolinfo()["size"]) assert_equal(20, node.getmempoolinfo()["size"])
assert_equal(6, len(package_hex)) assert_equal(6, len(package_hex))
@ -424,51 +325,30 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
""" """
node = self.nodes[0] node = self.nodes[0]
assert_equal(0, node.getmempoolinfo()["size"]) assert_equal(0, node.getmempoolinfo()["size"])
parents_tx = [] parent_utxos = []
values = []
scripts = []
target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB
high_fee = Decimal("0.003") # 10 sats/vB high_fee = Decimal("0.003") # 10 sats/vB
self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages") self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages")
# Mempool transactions A and B # Mempool transactions A and B
for _ in range(2): for _ in range(2):
spk = None bulked_tx = self.wallet.create_self_transfer(target_weight=target_weight)
top_coin = self.coins.pop() self.wallet.sendrawtransaction(from_node=node, tx_hex=bulked_tx["hex"])
txid = top_coin["txid"] parent_utxos.append(bulked_tx["new_utxo"])
value = top_coin["amount"]
(tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys)
node.sendrawtransaction(bulked_tx.serialize().hex())
parents_tx.append(bulked_tx)
values.append(Decimal(bulked_tx.vout[0].nValue) / COIN)
scripts.append(bulked_tx.vout[0].scriptPubKey.hex())
# Package transaction C # Package transaction C
small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee) pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(high_fee * COIN), target_weight=target_weight)
pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys)
pc_value = Decimal(pc_tx.vout[0].nValue) / COIN
pc_spk = pc_tx.vout[0].scriptPubKey.hex()
pc_hex = pc_tx.serialize().hex()
# Package transaction D # Package transaction D
(small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee) pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_weight=target_weight)
prevtxs = [{
"txid": pc_tx.rehash(),
"vout": 0,
"scriptPubKey": spk,
"amount": val,
}]
pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs)
pd_hex = pd_tx.serialize().hex()
assert_equal(2, node.getmempoolinfo()["size"]) assert_equal(2, node.getmempoolinfo()["size"])
testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])
for txres in testres_too_heavy: for txres in testres_too_heavy:
assert_equal(txres["package-error"], "package-mempool-limits") assert_equal(txres["package-error"], "package-mempool-limits")
# Clear mempool and check that the package passes now # Clear mempool and check that the package passes now
self.generate(node, 1) self.generate(node, 1)
assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])]) assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])])
def test_desc_size_limits(self): def test_desc_size_limits(self):
"""Create 3 mempool transactions and 2 package transactions (25KvB each): """Create 3 mempool transactions and 2 package transactions (25KvB each):
@ -486,50 +366,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework):
high_fee = Decimal("0.0021") # 10 sats/vB high_fee = Decimal("0.0021") # 10 sats/vB
self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages") self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages")
# Top parent in mempool, Ma # Top parent in mempool, Ma
first_coin = self.coins.pop() ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=int(high_fee / 2 * COIN), target_weight=target_weight)
parent_value = (first_coin["amount"] - high_fee) / 2 # Deduct fee and make 2 outputs self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"])
inputs = [{"txid": first_coin["txid"], "vout": 0}]
outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE: parent_value}]
rawtx = node.createrawtransaction(inputs, outputs)
parent_tx = bulk_transaction(tx_from_hex(rawtx), node, target_weight, self.privkeys)
node.sendrawtransaction(parent_tx.serialize().hex())
package_hex = [] package_hex = []
for j in range(2): # Two legs (left and right) for j in range(2): # Two legs (left and right)
# Mempool transaction (Mb and Mc) # Mempool transaction (Mb and Mc)
mempool_tx = CTransaction() mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_weight=target_weight)
spk = parent_tx.vout[j].scriptPubKey.hex() self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"])
value = Decimal(parent_tx.vout[j].nValue) / COIN
txid = parent_tx.rehash()
prevtxs = [{
"txid": txid,
"vout": j,
"scriptPubKey": spk,
"amount": value,
}]
if j == 0: # normal key
(tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, j, spk, high_fee)
mempool_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
else: # OP_TRUE
inputs = [{"txid": txid, "vout": 1}]
outputs = {self.address: value - high_fee}
small_tx = tx_from_hex(node.createrawtransaction(inputs, outputs))
mempool_tx = bulk_transaction(small_tx, node, target_weight, None, prevtxs)
node.sendrawtransaction(mempool_tx.serialize().hex())
# Package transaction (Pd and Pe) # Package transaction (Pd and Pe)
spk = mempool_tx.vout[0].scriptPubKey.hex() package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_weight=target_weight)
value = Decimal(mempool_tx.vout[0].nValue) / COIN package_hex.append(package_tx["hex"])
txid = mempool_tx.rehash()
(tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee)
prevtxs = [{
"txid": txid,
"vout": 0,
"scriptPubKey": spk,
"amount": value,
}]
package_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs)
package_hex.append(package_tx.serialize().hex())
assert_equal(3, node.getmempoolinfo()["size"]) assert_equal(3, node.getmempoolinfo()["size"])
assert_equal(2, len(package_hex)) assert_equal(2, len(package_hex))

View file

@ -7,7 +7,6 @@
from copy import deepcopy from copy import deepcopy
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from random import choice
from typing import ( from typing import (
Any, Any,
List, List,
@ -104,6 +103,16 @@ class MiniWallet:
def _create_utxo(self, *, txid, vout, value, height): def _create_utxo(self, *, txid, vout, value, height):
return {"txid": txid, "vout": vout, "value": value, "height": height} return {"txid": txid, "vout": vout, "value": value, "height": height}
def _bulk_tx(self, tx, target_weight):
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
returns the tx
"""
assert_greater_than_or_equal(target_weight, tx.get_weight())
while tx.get_weight() < target_weight:
script_pubkey = ( b"6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
+ b"01" * 512 )
tx.vout.append(CTxOut(0, script_pubkey))
def get_balance(self): def get_balance(self):
return sum(u['value'] for u in self._utxos) return sum(u['value'] for u in self._utxos)
@ -235,6 +244,7 @@ class MiniWallet:
amount_per_output=0, amount_per_output=0,
sequence=0, sequence=0,
fee_per_output=1000, fee_per_output=1000,
target_weight=0
): ):
""" """
Create and return a transaction that spends the given UTXOs and creates a Create and return a transaction that spends the given UTXOs and creates a
@ -265,6 +275,10 @@ class MiniWallet:
outputs_value_total = inputs_value_total - fee_per_output * num_outputs outputs_value_total = inputs_value_total - fee_per_output * num_outputs
for o in tx.vout: for o in tx.vout:
o.nValue = amount_per_output or (outputs_value_total // num_outputs) o.nValue = amount_per_output or (outputs_value_total // num_outputs)
if target_weight:
self._bulk_tx(tx, target_weight)
txid = tx.rehash() txid = tx.rehash()
return { return {
"new_utxos": [self._create_utxo( "new_utxos": [self._create_utxo(
@ -278,7 +292,7 @@ class MiniWallet:
"tx": tx, "tx": tx,
} }
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0): def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0, target_weight=0):
"""Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed.""" """Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed."""
utxo_to_spend = utxo_to_spend or self.get_utxo() utxo_to_spend = utxo_to_spend or self.get_utxo()
assert fee_rate >= 0 assert fee_rate >= 0
@ -305,9 +319,13 @@ class MiniWallet:
tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key]
else: else:
assert False assert False
tx_hex = tx.serialize().hex()
assert_equal(tx.get_vsize(), vsize) assert_equal(tx.get_vsize(), vsize)
if target_weight:
self._bulk_tx(tx, target_weight)
tx_hex = tx.serialize().hex()
new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0) new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0)
return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo} return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo}
@ -317,7 +335,6 @@ class MiniWallet:
self.scan_tx(from_node.decoderawtransaction(tx_hex)) self.scan_tx(from_node.decoderawtransaction(tx_hex))
return txid return txid
def getnewdestination(address_type='bech32m'): def getnewdestination(address_type='bech32m'):
"""Generate a random destination of the specified type and return the """Generate a random destination of the specified type and return the
corresponding public key, scriptPubKey and address. Supported types are corresponding public key, scriptPubKey and address. Supported types are
@ -409,23 +426,3 @@ def create_raw_chain(node, first_coin, address, privkeys, chain_length=25):
chain_txns.append(tx) chain_txns.append(tx)
return (chain_hex, chain_txns) return (chain_hex, chain_txns)
def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None):
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
returns CTransaction object
"""
tx_heavy = deepcopy(tx)
assert_greater_than_or_equal(target_weight, tx_heavy.get_weight())
while tx_heavy.get_weight() < target_weight:
random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
for _ in range(512*2):
random_spk += choice("0123456789ABCDEF")
tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk)))
# Re-sign the transaction
if privkeys:
signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs)
return tx_from_hex(signed["hex"])
# OP_TRUE
tx_heavy.wit.vtxinwit = [CTxInWitness()]
tx_heavy.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
return tx_heavy