Merge bitcoin/bitcoin#35164: test: cover P2SH sigop counting in test_witness_sigops

d180b891a2 test: add mixed P2SH/witness sigop accounting (Lőrinc)
6e60c362bc test: add P2SH sigop counting coverage (Musa Haruna)

Pull request description:

  Add test coverage for sigop counting in P2SH spends in `test_witness_sigops()`, addressing the existing TODO.

  The new cases mirror the existing P2WSH sigop tests by constructing transactions that:

  - remain below the block sigop limit (accepted),
  - exceed the limit (rejected with bad-blk-sigops)

  Since P2SH sigops are accounted as legacy sigops, the expected sigop cost accounts for the 4× legacy weighting applied during consensus validation.

  The added coverage verifies the enforcement of the block sigop limit for both witness and P2SH spends, including mixed P2SH/witness transactions.

  **Acknowledgement:** During review ([comment](https://github.com/bitcoin/bitcoin/pull/35164#pullrequestreview-4769420630)), **l0rinc** demonstrated, using mutation testing on his branch [here](https://github.com/l0rinc/bitcoin/pull/248), that the original test suite would not detect two consensus sigop undercounting bugs. Those experiments helped validate the coverage added by this PR and motivated the inclusion of the mixed P2SH/witness regression test.

ACKs for top commit:
  l0rinc:
    reACK d180b891a2
  Bicaru20:
    ACK d180b891a2
  sedited:
    ACK d180b891a2

Tree-SHA512: 795923f56316c3cad4d02a572ed6486a0f3f62bd524fb162d2bcd884108485974e96f38c08b5ff0a8659feecfc208c8d7e93dde2b9e0517c70f6343c2063d9b5
This commit is contained in:
merge-script
2026-09-07 10:34:59 +02:00

View File

@@ -29,6 +29,7 @@ from test_framework.messages import (
MSG_WTX,
NODE_NETWORK,
NODE_WITNESS,
WITNESS_SCALE_FACTOR,
msg_no_witness_block,
msg_getdata,
msg_headers,
@@ -1872,6 +1873,7 @@ class SegWitTest(BitcoinTestFramework):
# sig ops
outputs = (MAX_SIGOP_COST // sigops_per_script) + 2
extra_sigops_available = MAX_SIGOP_COST % sigops_per_script
p2sh_outputs = MAX_SIGOP_COST // (sigops_per_script * WITNESS_SCALE_FACTOR) + 1
# We chose the number of checkmultisigs/checksigs to make this work:
assert extra_sigops_available < 100 # steer clear of MAX_OPS_PER_SCRIPT
@@ -1895,6 +1897,7 @@ class SegWitTest(BitcoinTestFramework):
tx.vout.append(CTxOut(split_value, script_pubkey))
tx.vout[-2].scriptPubKey = script_pubkey_toomany
tx.vout[-1].scriptPubKey = script_pubkey_justright
tx.vout += [CTxOut(0, script_to_p2sh_script(witness_script)) for _ in range(p2sh_outputs)]
block_1 = self.build_next_block()
self.update_witness_block_with_transactions(block_1, [tx])
@@ -1923,7 +1926,7 @@ class SegWitTest(BitcoinTestFramework):
tx2.vout.append(CTxOut(0, script_pubkey_checksigs))
tx2.vin.pop()
tx2.wit.vtxinwit.pop()
tx2.vout[0].nValue -= tx.vout[-2].nValue
tx2.vout[0].nValue -= tx.vout[outputs - 2].nValue
block_3 = self.build_next_block()
self.update_witness_block_with_transactions(block_3, [tx2])
test_witness_block(self.nodes[0], self.test_node, block_3, accepted=False, reason='bad-blk-sigops')
@@ -1949,7 +1952,20 @@ class SegWitTest(BitcoinTestFramework):
self.update_witness_block_with_transactions(block_5, [tx2])
test_witness_block(self.nodes[0], self.test_node, block_5, accepted=True)
# TODO: test p2sh sigop counting
p2sh_tx = CTransaction()
p2sh_tx.vin = [CTxIn(COutPoint(tx.txid_int, outputs + i), CScript([witness_script])) for i in range(p2sh_outputs)]
p2sh_tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
block_6 = self.build_next_block()
self.update_witness_block_with_transactions(block_6, [p2sh_tx])
test_witness_block(self.nodes[0], self.test_node, block_6, accepted=False, reason='bad-blk-sigops')
# Add witness data to verify that the transaction's P2SH sigops are still counted.
p2sh_tx.vin.append(CTxIn(COutPoint(tx.txid_int, outputs - 2), b""))
p2sh_tx.wit.vtxinwit = [CTxInWitness() for _ in p2sh_tx.vin]
p2sh_tx.wit.vtxinwit[-1].scriptWitness.stack = [witness_script_toomany]
block_7 = self.build_next_block()
self.update_witness_block_with_transactions(block_7, [p2sh_tx])
test_witness_block(self.nodes[0], self.test_node, block_7, accepted=False, reason='bad-blk-sigops')
# Cleanup and prep for next test
self.utxo.pop(0)