test: add OP_CHECKTEMPLATEVERIFY functional tests

Co-authored-by: James O'Beirne <github@au92.org>
This commit is contained in:
Jeremy Rubin 2019-10-16 15:23:50 -07:00 committed by James O'Beirne
parent 2da4f34da9
commit 2b74adfb20
No known key found for this signature in database
GPG key ID: 7A935DADB2C44F05
4 changed files with 755 additions and 2 deletions

View file

@ -0,0 +1,738 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test OP_CHECKTEMPLATEVERIFY (BIP-119)
"""
from test_framework.blocktools import (
create_coinbase,
create_block,
add_witness_commitment,
)
from test_framework.messages import (
CTransaction,
CTxOut,
CTxIn,
CTxInWitness,
COutPoint,
COIN,
sha256,
)
from test_framework.p2p import P2PInterface
from test_framework.script import (
CScript,
OP_TRUE,
OP_DEPTH,
OP_ENDIF,
OP_IF,
OP_CHECKTEMPLATEVERIFY,
OP_FALSE,
OP_DROP,
taproot_construct,
)
from test_framework.script_util import script_to_p2sh_script
from test_framework.key import ECKey, compute_xonly_pubkey
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
from test_framework.wallet import MiniWallet, MiniWalletMode
from decimal import Decimal
import random
from io import BytesIO
from test_framework.address import script_to_p2sh
CHECKTEMPLATEVERIFY_ERROR = "mandatory-script-verify-flag-failed (Script failed an OP_CHECKTEMPLATEVERIFY operation)"
DISCOURAGED_ERROR = (
"non-mandatory-script-verify-flag (NOPx reserved for soft-fork upgrades)"
)
STACK_TOO_SHORT_ERROR = (
"mandatory-script-verify-flag-failed (Operation not valid with the current stack size)"
)
def random_bytes(n):
return bytes(random.getrandbits(8) for i in range(n))
def template_hash_for_outputs(outputs, nIn=0, nVin=1, vin_override=None):
c = CTransaction()
c.version = 2
c.vin = vin_override
if vin_override is None:
c.vin = [CTxIn()] * nVin
c.vout = outputs
return c.get_standard_template_hash(nIn)
def random_p2sh():
return script_to_p2sh_script(random_bytes(20))
def random_real_outputs_and_script(n, nIn=0, nVin=1, vin_override=None):
outputs = [CTxOut((x + 1) * 1000, random_p2sh()) for x in range(n)]
script = CScript(
[
template_hash_for_outputs(outputs, nIn, nVin, vin_override),
OP_CHECKTEMPLATEVERIFY,
]
)
return outputs, script
def random_secure_tree(depth):
leaf_nodes = [
CTxOut(nValue=100, scriptPubKey=CScript(bytes([0, 0x14]) + random_bytes(20)))
for x in range(2 ** depth)
]
outputs_tree = [[CTxOut()] * (2 ** i) for i in range(depth)] + [leaf_nodes]
for d in range(1, depth + 2):
idxs = zip(
range(0, len(outputs_tree[-d]), 2), range(1, len(outputs_tree[-d]), 2)
)
for (idx, (a, b)) in enumerate(
[(outputs_tree[-d][i], outputs_tree[-d][j]) for (i, j) in idxs]
):
s = CScript(
bytes([0x20])
+ template_hash_for_outputs([a, b])
+ bytes([OP_CHECKTEMPLATEVERIFY])
)
a = sum(o.nValue for o in [a, b])
t = CTxOut(a + 1000, s)
outputs_tree[-d - 1][idx] = t
return outputs_tree
def create_transaction_to_script(node, wallet, txid, script, *, amount_sats):
"""
Return signed transaction spending the first output of the
input txid. Note that the node must be able to sign for the
output that is being spent, and the node must not be running
multiple wallets.
"""
random_address = script_to_p2sh(CScript())
output = wallet.get_utxo(txid=txid)
rawtx = node.createrawtransaction(
inputs=[{"txid": output["txid"], "vout": output["vout"]}],
outputs={random_address: Decimal(amount_sats) / COIN},
)
tx = CTransaction()
tx.deserialize(BytesIO(bytes.fromhex(rawtx)))
# Replace with our script
tx.vout[0].scriptPubKey = script
# Sign
wallet.sign_tx(tx)
return tx
class CheckTemplateVerifyTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
# Use only one script thread to get the exact reject reason for testing
self.extra_args = [
["-par=1"]
]
self.setup_clean_chain = True
self.rpc_timeout = 120
def get_block(self, txs):
self.tip = self.nodes[0].getbestblockhash()
self.height = self.nodes[0].getblockcount()
self.log.debug(self.height)
block = create_block(int(self.tip, 16), create_coinbase(self.height + 1))
block.vtx.extend(txs)
add_witness_commitment(block)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
return block.serialize(True).hex(), block.hash
def add_block(self, txs):
block, h = self.get_block(txs)
reason = self.nodes[0].submitblock(block)
if reason:
self.log.debug("Reject Reason: [%s]", reason)
assert_equal(self.nodes[0].getbestblockhash(), h)
return h
def fail_block(self, txs, cause=CHECKTEMPLATEVERIFY_ERROR):
block, h = self.get_block(txs)
assert_equal(self.nodes[0].submitblock(block), cause)
assert_equal(self.nodes[0].getbestblockhash(), self.tip)
def run_test(self):
"""
The goal is to test a number of circumstances and combinations of parameters. Roughly:
- Taproot OP_CTV
- SegWit OP_CTV
- SegWit OP_CTV wrong size on stack
- fails policy
- passes consensus
- SegWit OP_CTV no argument in stack from program
- fails policy and consensus with empty stack
- passes consensus and policy when argument is the correct hash
- passes consensus when argument is non 32 bytes
- fails policy when argument is non 32 bytes
- P2SH OP_CTV (impossible to spend w/o hash cycle!)
- Bare OP_CTV
- OP_CTV at vin index 0
- OP_CTV at vin index > 0
- OP_CTV with scriptSigs set
- OP_CTV without scriptSigs set
- OP_CTV with multiple inputs
- accepting correct parameters
- rejecting incorrect parameters
- OP_CTV in a tree
A few tests may seem redundant, but it is because they are testing the cached computation of the hash
at vin index 0
"""
wallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)
self.nodes[0].add_p2p_connection(P2PInterface())
BLOCKS = 115
self.log.info("Mining %d blocks for mature coinbases", BLOCKS)
# Drop the last 100 as they're unspendable!
coinbase_txids = [
self.nodes[0].getblock(b)["tx"][0]
for b in self.generate(wallet, BLOCKS)[:-100]
]
get_coinbase = lambda: coinbase_txids.pop()
self.log.info("Creating setup transactions")
self.log.info("Creating script for 10 random outputs")
outputs, script = random_real_outputs_and_script(10)
# Add some fee satoshis
amount_sats = sum(out.nValue for out in outputs) + 200 * 500
self.log.info("Creating funding txn for 10 random outputs as a Taproot script")
private_key = ECKey()
# use simple deterministic private key (k=1)
private_key.set((1).to_bytes(32, "big"), False)
assert private_key.is_valid
public_key, _ = compute_xonly_pubkey(private_key.get_bytes())
taproot = taproot_construct(public_key, [("only-path", script, 0xC0)])
taproot_ctv_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
taproot.scriptPubKey,
amount_sats=amount_sats,
)
self.log.info("Creating funding txn for 10 random outputs as a segwit script")
segwit_ctv_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
CScript([0, sha256(script)]),
amount_sats=amount_sats,
)
self.log.info("Creating a CTV with a non 32 byte stack segwit script")
segwit_ctv_funding_tx_wrongsize_stack = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
CScript([0, sha256(CScript([OP_TRUE, OP_CHECKTEMPLATEVERIFY]))]),
amount_sats=amount_sats,
)
self.log.info("Creating a CTV with an empty stack segwit script")
# allows either calling with empty witness stack or with a 32 byte hash (cleanstack rule)
empty_stack_script = CScript(
[OP_CHECKTEMPLATEVERIFY, OP_DEPTH, OP_IF, OP_DROP, OP_ENDIF, OP_TRUE]
)
segwit_ctv_funding_tx_empty_stack = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
CScript([0, sha256(empty_stack_script)]),
amount_sats=amount_sats,
)
self.log.info(
"Creating funding txn for 10 random outputs as a p2sh script (impossible to spend)"
)
p2sh_ctv_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
script_to_p2sh_script(script),
amount_sats=amount_sats,
)
# Small tree size 4 for test speed.
# Can be set to a large value like 16 (i.e., 65K txns).
TREE_SIZE = 4
self.log.info(f"Creating script for tree size depth {TREE_SIZE}")
congestion_tree_txo = random_secure_tree(TREE_SIZE)
self.log.info(f"Creating funding txn for tree size depth {TREE_SIZE}")
bare_ctv_tree_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
congestion_tree_txo[0][0].scriptPubKey,
amount_sats=congestion_tree_txo[0][0].nValue,
)
self.log.info("Creating script for spend at position 2")
outputs_position_2, script_position_2 = random_real_outputs_and_script(10, 1, 2)
# Add some fee satoshis
amount_position_2 = sum(out.nValue for out in outputs_position_2) + 200 * 500
self.log.info("Creating funding txn for spend at position 2")
bare_ctv_position_2_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
script_position_2,
amount_sats=amount_position_2,
)
self.log.info(
"Creating script for spend at position 1 with 2 non-null scriptsigs"
)
(
outputs_specific_scriptSigs,
script_specific_scriptSigs,
) = random_real_outputs_and_script(
10,
0,
2,
[CTxIn(scriptSig=CScript([OP_TRUE])), CTxIn(scriptSig=CScript([OP_FALSE]))],
)
# Add some fee satoshis
amount_specific_scriptSigs = (
sum(out.nValue for out in outputs_specific_scriptSigs) + 200 * 500
)
self.log.info(
"Creating funding txn for spend at position 1 with 2 non-null scriptsigs"
)
bare_ctv_specific_scriptSigs_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
script_specific_scriptSigs,
amount_sats=amount_specific_scriptSigs,
)
self.log.info(
"Creating script for spend at position 2 with 2 non-null scriptsigs"
)
(
outputs_specific_scriptSigs_position_2,
script_specific_scriptSigs_position_2,
) = random_real_outputs_and_script(
10,
1,
2,
[CTxIn(scriptSig=CScript([OP_TRUE])), CTxIn(scriptSig=CScript([OP_FALSE]))],
)
# Add some fee satoshis
amount_specific_scriptSigs_position_2 = (
sum(out.nValue for out in outputs_specific_scriptSigs_position_2)
+ 200 * 500
)
self.log.info(
"Creating funding txn for spend at position 2 with 2 non-null scriptsigs"
)
bare_ctv_specific_scriptSigs_position_2_funding_tx = (
create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
script_specific_scriptSigs_position_2,
amount_sats=amount_specific_scriptSigs_position_2,
)
)
self.log.info("Creating funding txns for some anyone can spend outputs")
anyone_can_spend_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
CScript([OP_TRUE]),
amount_sats=amount_sats,
)
bare_anyone_can_spend_funding_tx = create_transaction_to_script(
self.nodes[0],
wallet,
get_coinbase(),
CScript([OP_TRUE]),
amount_sats=amount_sats,
)
funding_txs = [
taproot_ctv_funding_tx,
segwit_ctv_funding_tx,
segwit_ctv_funding_tx_wrongsize_stack,
segwit_ctv_funding_tx_empty_stack,
p2sh_ctv_funding_tx,
anyone_can_spend_funding_tx,
bare_ctv_tree_funding_tx,
bare_ctv_position_2_funding_tx,
bare_anyone_can_spend_funding_tx,
bare_ctv_specific_scriptSigs_funding_tx,
bare_ctv_specific_scriptSigs_position_2_funding_tx,
]
self.log.info("Obtaining TXIDs")
(
taproot_ctv_outpoint,
segwit_ctv_outpoint,
segwit_ctv_wrongsize_stack_outpoint,
segwit_ctv_empty_stack_outpoint,
p2sh_ctv_outpoint,
anyone_can_spend_outpoint,
bare_ctv_tree_outpoint,
bare_ctv_position_2_outpoint,
bare_anyone_can_spend_outpoint,
bare_ctv_specific_scriptSigs_outpoint,
bare_ctv_specific_scriptSigs_position_2_outpoint,
) = [COutPoint(int(tx.rehash(), 16), 0) for tx in funding_txs]
self.log.info("Funding all outputs")
self.add_block(funding_txs)
self.log.info("Testing Taproot OP_CHECKTEMPLATEVERIFY spend")
# Test sendrawtransaction
taproot_check_template_verify_tx = CTransaction()
taproot_check_template_verify_tx.version = 2
taproot_check_template_verify_tx.vin = [CTxIn(taproot_ctv_outpoint)]
taproot_check_template_verify_tx.vout = outputs
taproot_check_template_verify_tx.wit.vtxinwit += [CTxInWitness()]
taproot_check_template_verify_tx.wit.vtxinwit[0].scriptWitness.stack = [
script,
bytes([0xC0 + taproot.negflag]) + taproot.internal_pubkey,
]
assert_equal(
self.nodes[0].sendrawtransaction(
taproot_check_template_verify_tx.serialize().hex()
),
taproot_check_template_verify_tx.rehash(),
)
self.log.info(
"Taproot OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction"
)
# Now we verify that a block with this transaction is also valid
blockhash = self.add_block([taproot_check_template_verify_tx])
self.log.info("Taproot OP_CHECKTEMPLATEVERIFY spend accepted in a block")
self.log.info("Rolling back the block")
# Reset tip
self.nodes[0].invalidateblock(blockhash)
self.log.info("Testing Segwit OP_CHECKTEMPLATEVERIFY spend")
# Test sendrawtransaction
check_template_verify_tx = CTransaction()
check_template_verify_tx.version = 2
check_template_verify_tx.vin = [CTxIn(segwit_ctv_outpoint)]
check_template_verify_tx.vout = outputs
check_template_verify_tx.wit.vtxinwit += [CTxInWitness()]
check_template_verify_tx.wit.vtxinwit[0].scriptWitness.stack = [script]
assert_equal(
self.nodes[0].sendrawtransaction(
check_template_verify_tx.serialize().hex()
),
check_template_verify_tx.rehash(),
)
self.log.info(
"Segwit OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction"
)
# Now we verify that a block with this transaction is also valid
blockhash = self.add_block([check_template_verify_tx])
self.log.info("Segwit OP_CHECKTEMPLATEVERIFY spend accepted in a block")
self.log.info("Rolling back the block")
# Reset tip
self.nodes[0].invalidateblock(blockhash)
# Show any modification will break the validity
self.log.info(
"Modifying Segwit OP_CHECKTEMPLATEVERIFY spend, block should fail"
)
check_template_verify_tx_mutated_amount = check_template_verify_tx
check_template_verify_tx_mutated_amount.vout[0].nValue += 1
check_template_verify_tx_mutated_amount.rehash()
self.fail_block([check_template_verify_tx_mutated_amount])
self.log.info("Modified Segwit OP_CHECKTEMPLATEVERIFY spend failed to confirm")
# Now show that only one input allowed
self.log.info("Testing that multiple inputs are disallowed when specified")
check_template_verify_two_inputs = check_template_verify_tx
check_template_verify_two_inputs.vin += [CTxIn(anyone_can_spend_outpoint)]
check_template_verify_two_inputs.rehash()
self.fail_block([check_template_verify_two_inputs])
self.log.info(
"Testing that the second input specified was actually spendable by itself"
)
# Second UTXO was actually spendable
spendtx = CTransaction()
spendtx.version = 2
spendtx.vin = [CTxIn(anyone_can_spend_outpoint)]
spendtx.vout += [CTxOut(int(amount_sats - 1000), random_p2sh())]
spendtx.rehash()
blockhash = self.add_block([spendtx])
# Reset tip
self.nodes[0].invalidateblock(blockhash)
self.log.info(
"Testing Segwit OP_CHECKTEMPLATEVERIFY spend with a wrong size argument"
)
# Test sendrawtransaction
check_template_verify_tx_wrongsize_stack = CTransaction()
check_template_verify_tx_wrongsize_stack.version = 2
check_template_verify_tx_wrongsize_stack.vin = [
CTxIn(segwit_ctv_wrongsize_stack_outpoint)
]
check_template_verify_tx_wrongsize_stack.vout = outputs
check_template_verify_tx_wrongsize_stack.wit.vtxinwit += [CTxInWitness()]
check_template_verify_tx_wrongsize_stack.wit.vtxinwit[0].scriptWitness.stack = [
CScript([OP_TRUE, OP_CHECKTEMPLATEVERIFY])
]
assert_raises_rpc_error(
-26,
DISCOURAGED_ERROR,
self.nodes[0].sendrawtransaction,
check_template_verify_tx_wrongsize_stack.serialize().hex(),
)
self.log.info(
"OP_CHECKTEMPLATEVERIFY with wrong size stack rejected by sendrawtransaction as discouraged"
)
# Now we verify that a block with this transaction is valid
blockhash = self.add_block([check_template_verify_tx_wrongsize_stack])
self.log.info(
"Segwit OP_CHECKTEMPLATEVERIFY with wrong size stack spend accepted in a block (soft fork upgradable)"
)
self.log.info(
"Testing Segwit OP_CHECKTEMPLATEVERIFY spend with an empty stack argument"
)
# Test sendrawtransaction
check_template_verify_tx_empty_stack = CTransaction()
check_template_verify_tx_empty_stack.version = 2
check_template_verify_tx_empty_stack.vin = [
CTxIn(segwit_ctv_empty_stack_outpoint)
]
check_template_verify_tx_empty_stack.vout = outputs
check_template_verify_tx_empty_stack.wit.vtxinwit += [CTxInWitness()]
check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [
empty_stack_script
]
assert_raises_rpc_error(
-26,
STACK_TOO_SHORT_ERROR,
self.nodes[0].sendrawtransaction,
check_template_verify_tx_empty_stack.serialize().hex(),
)
self.log.info(
"OP_CHECKTEMPLATEVERIFY with wrong size stack rejected by sendrawtransaction as discouraged"
)
# Now we verify that a block with this transaction is invalid
self.fail_block([check_template_verify_tx_empty_stack], STACK_TOO_SHORT_ERROR)
self.log.info(
"Segwit OP_CHECKTEMPLATEVERIFY with wrong size stack spend rejected from block"
)
# Show that putting some element on the stack makes it succeed for consensus but fail policy
self.log.info(
"Segwit OP_CHECKTEMPLATEVERIFY with CTV argument in the program should fail policy pass consensus if witness stack is not 32 bytes"
)
check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [
CScript([OP_TRUE]),
empty_stack_script,
]
assert_raises_rpc_error(
-26,
DISCOURAGED_ERROR,
self.nodes[0].sendrawtransaction,
check_template_verify_tx_empty_stack.serialize().hex(),
)
self.log.info(
"OP_CHECKTEMPLATEVERIFY with wrong size argument on the witness stack rejected by sendrawtransaction as discouraged"
)
# Now we verify that a block with this transaction is valid
blockhash = self.add_block([check_template_verify_tx_empty_stack])
self.log.info(
"Segwit OP_CHECKTEMPLATEVERIFY with empty stack spend accepted in a block with something on the witness stack"
)
self.log.info("Rolling back the block")
# Reset tip
self.nodes[0].invalidateblock(blockhash)
# Put the correct hash on
self.log.info(
"Testing OP_CHECKTEMPLATEVERIFY spend with template hash from the witness stack"
)
h = check_template_verify_tx_empty_stack.get_standard_template_hash(0)
check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [
h,
empty_stack_script,
]
assert_equal(
self.nodes[0].sendrawtransaction(
check_template_verify_tx_empty_stack.serialize().hex()
),
check_template_verify_tx_empty_stack.rehash(),
)
self.log.info(
"Witness stack defined OP_CHECKTEMPLATEVERIFY spend accepted by sendrawtransaction"
)
# Now we verify that a block with this transaction is also valid
blockhash = self.add_block([check_template_verify_tx_empty_stack])
self.log.info(
"Witness stack defined OP_CHECKTEMPLATEVERIFY spend accepted in a block"
)
self.log.info("Rolling back the block")
# Reset tip
self.nodes[0].invalidateblock(blockhash)
self.log.info(
"Testing that other 32 byte arguments passed from the witness stack fail"
)
h = h[::-1]
check_template_verify_tx_empty_stack.wit.vtxinwit[0].scriptWitness.stack = [
h,
empty_stack_script,
]
self.fail_block([check_template_verify_tx_empty_stack])
# Test sendrawtransaction with P2SH
# Mathematically, this test cannot succeed since P2SH requires that the
# P2SH script is in the scriptsig (which contains H), and H much
# contain a commitment to the scriptsig.
#
# Note that this relies on RIPEMD160 being hard to find a cycle in.
# This *could* break one day (P2SH is no longer recommended), but
# there's no capability that would be gained by doing so (such as a
# recursive covenant), it'd just be a "party trick" (and you could
# also possibly steal all existing P2SH addresses).
p2sh_check_template_verify_tx = CTransaction()
p2sh_check_template_verify_tx.version = 2
p2sh_check_template_verify_tx.vin = [
CTxIn(p2sh_ctv_outpoint, CScript([script]))
]
p2sh_check_template_verify_tx.vout = outputs
assert_raises_rpc_error(
-26,
CHECKTEMPLATEVERIFY_ERROR,
self.nodes[0].sendrawtransaction,
p2sh_check_template_verify_tx.serialize().hex(),
)
self.log.info(
"P2SH OP_CHECKTEMPLATEVERIFY spend rejected by sendrawtransaction"
)
# Now we verify that a block with this transaction is also invalid
self.fail_block([p2sh_check_template_verify_tx])
self.log.info("P2SH OP_CHECKTEMPLATEVERIFY spend rejected in a block")
self.log.info(
"Testing a congestion control tree using bare OP_CHECKTEMPLATEVERIFY"
)
# Expand Congestion Control Tree to one specific input
out = bare_ctv_tree_outpoint
txs = []
for level in congestion_tree_txo[1:]:
spendtx = CTransaction()
spendtx.version = 2
spendtx.vin += [CTxIn(out)]
spendtx.vout += level[:2]
out = COutPoint(int(spendtx.rehash(), 16), 0)
txs.append(spendtx)
self.add_block(txs)
self.log.info("Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 2")
check_template_verify_tx_pos_2 = CTransaction()
check_template_verify_tx_pos_2.version = 2
check_template_verify_tx_pos_2.vin = [CTxIn(bare_ctv_position_2_outpoint)]
check_template_verify_tx_pos_2.vout = outputs_position_2
self.log.info(
"Testing that the transaction fails because we have too few inputs"
)
self.fail_block([check_template_verify_tx_pos_2])
check_template_verify_tx_pos_2.vin += [CTxIn(bare_anyone_can_spend_outpoint)]
check_template_verify_tx_pos_2.rehash()
self.log.info(
"Testing that the transaction fails because the inputs are in the wrong order"
)
self.fail_block([check_template_verify_tx_pos_2])
self.log.info(
"Testing that the transaction succeeds when the inputs are in the correct order"
)
check_template_verify_tx_pos_2.vin.reverse()
check_template_verify_tx_pos_2.rehash()
blockhash = self.add_block([check_template_verify_tx_pos_2])
self.nodes[0].invalidateblock(blockhash)
check_template_verify_tx_pos_2.vin[0].scriptSig = CScript([OP_TRUE])
check_template_verify_tx_pos_2.rehash()
self.log.info(
"Testing that the transaction fails because the scriptSig on the other input has been modified"
)
self.fail_block([check_template_verify_tx_pos_2])
self.log.info(
"Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 1 with specific scriptSigs"
)
check_template_verify_tx_specific_scriptSigs = CTransaction()
check_template_verify_tx_specific_scriptSigs.version = 2
check_template_verify_tx_specific_scriptSigs.vin = [
CTxIn(bare_ctv_specific_scriptSigs_outpoint, CScript([OP_TRUE])),
CTxIn(bare_anyone_can_spend_outpoint, CScript([OP_TRUE])),
]
check_template_verify_tx_specific_scriptSigs.vout = outputs_specific_scriptSigs
check_template_verify_tx_specific_scriptSigs.rehash()
self.log.info(
"Testing bare OP_CHECKTEMPLATEVERIFY rejects incorrect scriptSigs"
)
self.fail_block([check_template_verify_tx_specific_scriptSigs])
self.log.info("Testing bare OP_CHECKTEMPLATEVERIFY accepts correct scriptSigs")
check_template_verify_tx_specific_scriptSigs.vin[1].scriptSig = CScript(
[OP_FALSE]
)
check_template_verify_tx_specific_scriptSigs.rehash()
blockhash = self.add_block([check_template_verify_tx_specific_scriptSigs])
self.nodes[0].invalidateblock(blockhash)
self.log.info(
"Testing bare OP_CHECKTEMPLATEVERIFY with CTV at position 2 with specific scriptSigs"
)
# This is only really to test that uncached values work correctly with scriptSig set
check_template_verify_tx_specific_scriptSigs_position_2 = CTransaction()
check_template_verify_tx_specific_scriptSigs_position_2.version = 2
check_template_verify_tx_specific_scriptSigs_position_2.vin = [
CTxIn(bare_anyone_can_spend_outpoint, CScript([OP_TRUE])),
CTxIn(
bare_ctv_specific_scriptSigs_position_2_outpoint, CScript([OP_FALSE])
),
]
check_template_verify_tx_specific_scriptSigs_position_2.vout = (
outputs_specific_scriptSigs_position_2
)
check_template_verify_tx_specific_scriptSigs_position_2.rehash()
self.add_block([check_template_verify_tx_specific_scriptSigs_position_2])
if __name__ == "__main__":
CheckTemplateVerifyTest(__file__).main()

View file

@ -25,6 +25,7 @@ from io import BytesIO
import math
import random
import socket
import struct
import time
import unittest
@ -633,6 +634,19 @@ class CTransaction:
r += self.nLockTime.to_bytes(4, "little")
return r
def get_standard_template_hash(self, nIn):
r = b""
r += self.version.to_bytes(4, "little")
r += self.nLockTime.to_bytes(4, "little")
if any(inp.scriptSig for inp in self.vin):
r += sha256(b"".join(ser_string(inp.scriptSig) for inp in self.vin))
r += struct.pack("<I", len(self.vin))
r += sha256(b"".join(struct.pack("<I", inp.nSequence) for inp in self.vin))
r += struct.pack("<I", len(self.vout))
r += sha256(b"".join(out.serialize() for out in self.vout))
r += struct.pack("<I", nIn)
return sha256(r)
# Only serialize with witness when explicitly called for
def serialize_with_witness(self):
flags = 0

View file

@ -238,7 +238,7 @@ OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
OP_NOP1 = CScriptOp(0xb0)
OP_CHECKLOCKTIMEVERIFY = CScriptOp(0xb1)
OP_CHECKSEQUENCEVERIFY = CScriptOp(0xb2)
OP_NOP4 = CScriptOp(0xb3)
OP_CHECKTEMPLATEVERIFY = CScriptOp(0xb3)
OP_NOP5 = CScriptOp(0xb4)
OP_NOP6 = CScriptOp(0xb5)
OP_NOP7 = CScriptOp(0xb6)
@ -356,7 +356,7 @@ OPCODE_NAMES.update({
OP_NOP1: 'OP_NOP1',
OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY',
OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY',
OP_NOP4: 'OP_NOP4',
OP_CHECKTEMPLATEVERIFY : 'OP_CHECKTEMPLATEVERIFY',
OP_NOP5: 'OP_NOP5',
OP_NOP6: 'OP_NOP6',
OP_NOP7: 'OP_NOP7',

View file

@ -312,6 +312,7 @@ BASE_SCRIPTS = [
'wallet_balance.py --descriptors',
'p2p_initial_headers_sync.py',
'feature_nulldummy.py',
'feature_checktemplateverify.py',
'mempool_accept.py',
'mempool_expiry.py',
'wallet_import_with_label.py --legacy-wallet',