test: replace ValidWitnessMalleatedTx class with function

Simplify the witness malleation test helper by converting the
ValidWitnessMalleatedTx class to a standalone function
build_malleated_tx_package() and updating call sites.

Co-authored-by: rkrux <rkrux.connect@gmail.com>
This commit is contained in:
naiyomaandrkrux committed 2026-01-21 14:07:59 +03:00
1 parent 81675a781f
commit 7cfe790820
3 files changed
+61 -54

No files matched your search

+17 -14
View File
@@ -7,17 +7,16 @@ Test mempool acceptance in case of an already known transaction
with identical non-witness data but different witness. with identical non-witness data but different witness.
""" """
from test_framework.messages import (
COIN,
)
from test_framework.p2p import P2PTxInvStore from test_framework.p2p import P2PTxInvStore
from test_framework.script_util import ValidWitnessMalleatedTx from test_framework.script_util import build_malleated_tx_package
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_not_equal, assert_not_equal,
assert_equal, assert_equal,
) )
from test_framework.wallet import (
MiniWallet,
)
class MempoolWtxidTest(BitcoinTestFramework): class MempoolWtxidTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
@@ -25,23 +24,27 @@ class MempoolWtxidTest(BitcoinTestFramework):
def run_test(self): def run_test(self):
node = self.nodes[0] node = self.nodes[0]
mini_wallet = MiniWallet(node)
self.log.info('Start with pre-generated blocks') self.log.info('Start with pre-generated blocks')
blockhash = self.nodes[0].getblockhash(1)
txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"]
assert_equal(node.getmempoolinfo()['size'], 0) assert_equal(node.getmempoolinfo()['size'], 0)
self.log.info("Submit parent with multiple script branches to mempool") self.log.info("Submit parent with multiple script branches to mempool")
txgen = ValidWitnessMalleatedTx()
parent = txgen.build_parent_tx(txid, 9.99998 * COIN)
privkeys = [node.get_deterministic_priv_key().key] parent = mini_wallet.create_self_transfer()["tx"]
raw_parent = node.signrawtransactionwithkey(hexstring=parent.serialize().hex(), privkeys=privkeys)['hex'] parent_amount = parent.vout[0].nValue - 10000
signed_parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0) child_amount = parent_amount - 10000
parent, child_one, child_two = build_malleated_tx_package(
parent=parent,
rebalance_parent_output_amount=parent_amount,
child_amount=child_amount
)
mini_wallet.sendrawtransaction(from_node=node, tx_hex=parent.serialize().hex())
self.generate(node, 1) self.generate(node, 1)
peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore()) peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore())
child_one, child_two = txgen.build_malleated_children(signed_parent_txid, 9.99996 * COIN)
child_one_wtxid = child_one.wtxid_hex child_one_wtxid = child_one.wtxid_hex
child_one_txid = child_one.txid_hex child_one_txid = child_one.txid_hex
child_two_wtxid = child_two.wtxid_hex child_two_wtxid = child_two.wtxid_hex
+8 -8
View File
@@ -18,7 +18,6 @@ from test_framework.p2p import (
from test_framework.messages import ( from test_framework.messages import (
CAddress, CAddress,
CInv, CInv,
COIN,
MSG_WTX, MSG_WTX,
malleate_tx_to_invalid_witness, malleate_tx_to_invalid_witness,
msg_inv, msg_inv,
@@ -27,7 +26,7 @@ from test_framework.messages import (
from test_framework.netutil import ( from test_framework.netutil import (
format_addr_port format_addr_port
) )
from test_framework.script_util import ValidWitnessMalleatedTx from test_framework.script_util import build_malleated_tx_package
from test_framework.socks5 import ( from test_framework.socks5 import (
Socks5Configuration, Socks5Configuration,
Socks5Server, Socks5Server,
@@ -400,16 +399,17 @@ class P2PPrivateBroadcast(BitcoinTestFramework):
tx_originator.setmocktime(0) # Let the clock tick again (it will go backwards due to this). tx_originator.setmocktime(0) # Let the clock tick again (it will go backwards due to this).
self.log.info("Sending a pair of transactions with the same txid but different valid wtxids via RPC") self.log.info("Sending a pair of transactions with the same txid but different valid wtxids via RPC")
txgen = ValidWitnessMalleatedTx() parent = wallet.create_self_transfer()["tx"]
funding = wallet.get_utxo() parent_amount = parent.vout[0].nValue - 10000
fee_sat = 1000 child_amount = parent_amount - 10000
siblings_parent = txgen.build_parent_tx(funding["txid"], amount=funding["value"] * COIN - fee_sat) siblings_parent, sibling1, sibling2 = build_malleated_tx_package(
sibling1, sibling2 = txgen.build_malleated_children(siblings_parent.txid_hex, amount=siblings_parent.vout[0].nValue - fee_sat) parent=parent,
rebalance_parent_output_amount=parent_amount,
child_amount=child_amount)
self.log.info(f" - sibling1: txid={sibling1.txid_hex}, wtxid={sibling1.wtxid_hex}") self.log.info(f" - sibling1: txid={sibling1.txid_hex}, wtxid={sibling1.wtxid_hex}")
self.log.info(f" - sibling2: txid={sibling2.txid_hex}, wtxid={sibling2.wtxid_hex}") self.log.info(f" - sibling2: txid={sibling2.txid_hex}, wtxid={sibling2.wtxid_hex}")
assert_equal(sibling1.txid_hex, sibling2.txid_hex) assert_equal(sibling1.txid_hex, sibling2.txid_hex)
assert_not_equal(sibling1.wtxid_hex, sibling2.wtxid_hex) assert_not_equal(sibling1.wtxid_hex, sibling2.wtxid_hex)
wallet.sign_tx(siblings_parent)
assert_equal(len(tx_originator.getrawmempool()), 1) assert_equal(len(tx_originator.getrawmempool()), 1)
tx_returner.send_without_ping(msg_tx(siblings_parent)) tx_returner.send_without_ping(msg_tx(siblings_parent))
self.wait_until(lambda: len(tx_originator.getrawmempool()) > 1) self.wait_until(lambda: len(tx_originator.getrawmempool()) > 1)
+36 -32
View File
@@ -36,7 +36,10 @@ from test_framework.script import (
hash160, hash160,
) )
from test_framework.util import assert_equal from test_framework.util import (
assert_greater_than_or_equal,
assert_equal,
)
# Maximum number of potentially executed legacy signature operations in validating a transaction. # Maximum number of potentially executed legacy signature operations in validating a transaction.
MAX_STD_LEGACY_SIGOPS = 2_500 MAX_STD_LEGACY_SIGOPS = 2_500
@@ -164,43 +167,44 @@ def check_script(script):
assert False assert False
class ValidWitnessMalleatedTx: def build_malleated_tx_package(*, parent: CTransaction, rebalance_parent_output_amount, child_amount):
""" """
Creates a valid witness malleation transaction test case: Returns a transaction package with valid witness:
- Parent transaction with a script supporting 2 branches - Parent transaction whose last output contains a script that has two spending conditions
- 2 child transactions with the same txid but different wtxids - Two malleated child transactions with same txid but different wtxids because of different witnesses
Args:
parent: Transaction with modifiable outputs. Either unsigned (sign after
calling this function) or anyone-can-spend (e.g., MiniWallet's OP_TRUE).
""" """
def __init__(self): hashlock = hash160(b'Preimage')
hashlock = hash160(b'Preimage') witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF])
self.witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF]) witness_program = sha256(witness_script)
script_pubkey = CScript([OP_0, witness_program])
def build_parent_tx(self, funding_txid, amount): # Append to the transaction the vout containing the script supporting 2 spending conditions
# Create an unsigned parent transaction paying to the witness script. assert_greater_than_or_equal(len(parent.vout), 1)
witness_program = sha256(self.witness_script) last_output = parent.vout[len(parent.vout) - 1]
script_pubkey = CScript([OP_0, witness_program]) assert_greater_than_or_equal(last_output.nValue, rebalance_parent_output_amount)
last_output.nValue -= rebalance_parent_output_amount
parent.vout.append(CTxOut(rebalance_parent_output_amount, script_pubkey))
parent = CTransaction()
parent.vin.append(CTxIn(COutPoint(int(funding_txid, 16), 0), b""))
parent.vout.append(CTxOut(int(amount), script_pubkey))
return parent
def build_malleated_children(self, signed_parent_txid, amount): # Create 2 valid children that differ only in witness data.
# Create 2 valid children that differ only in witness data. # 1. Create a new transaction with witness solving first branch
# 1. Create a new transaction with witness solving first branch child_witness_script = CScript([OP_TRUE])
child_witness_script = CScript([OP_TRUE]) child_witness_program = sha256(child_witness_script)
child_witness_program = sha256(child_witness_script) child_script_pubkey = CScript([OP_0, child_witness_program])
child_script_pubkey = CScript([OP_0, child_witness_program]) child_one = CTransaction()
child_one = CTransaction() child_one.vin.append(CTxIn(COutPoint(int(parent.txid_hex, 16), len(parent.vout) - 1), b""))
child_one.vin.append(CTxIn(COutPoint(int(signed_parent_txid, 16), 0), b"")) child_one.vout.append(CTxOut(child_amount, child_script_pubkey))
child_one.vout.append(CTxOut(int(amount), child_script_pubkey)) child_one.wit.vtxinwit.append(CTxInWitness())
child_one.wit.vtxinwit.append(CTxInWitness()) child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script]
child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', self.witness_script] # 2. Create another identical transaction with witness solving second branch
child_two = deepcopy(child_one)
# 2. Create another identical transaction with witness solving second branch child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script]
child_two = deepcopy(child_one) return parent, child_one, child_two
child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', self.witness_script]
return child_one, child_two
class TestFrameworkScriptUtil(unittest.TestCase): class TestFrameworkScriptUtil(unittest.TestCase):