Repository: bitcoin/bitcoin
File: test/functional/feature_taproot.py (Python)

Code before:

    # Create a list of scripts which will be built into a taptree
    scripts = [
        # leaf label, followed by CScript
        ("encodeable_pushdata1", CScript([OP_DROP, OP_PUSHDATA1, b'aa' * 75])),
        ("nonstd_encodeable_pushdata1", CScript([OP_PUSHDATA1, b'aa'])),
        ("dummyleaf", CScript([])),
    ]

    # Build TaprootInfo using scripts and appropriate pubkey for output creation
    tap = taproot_construct(pubs[0], scripts)

    # Finally, add spender(s).
    # Each spender embodies a test with an optional failure condition.
    # These failure conditions allow for fine-grained success/failure
    # conditions that are tested randomly.
    spenders = []

    # Named comment, using first leaf from scripts, with empty string as witness data, no optional fail condition
    add_spender(spenders, comment="tutorial/pushdata1", tap=tap, leaf="encodeable_pushdata1", inputs=[b'\x00'], no_fail=True)

    # Spender with alternative failure tapscript via over-riding "failure" dictionary, along with the failure's expected err_msg / ERR_*
    add_spender(spenders, comment="tutorial/pushdata1redux", tap=tap, leaf="encodeable_pushdata1", inputs=[b'\x00'], failure={"leaf": "dummyleaf"}, **ERR_EVAL_FALSE)

    # Spender that is non-standard but otherwise valid, with extraneous signature data from inner key for optional failure condition
    add_spender(spenders, comment="tutorial/nonminpushdata1", tap=tap, leaf="nonstd_encodeable_pushdata1", key=secs[0], standard=False, failure={"inputs": [getter("sign")]}, **ERR_CLEANSTACK)

    # New scripts=[] can be defined, and rinse-repeated as necessary until the spenders list is returned for execution
    return spenders

# Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix.
LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY"
# Consensus validation flags to use in dumps for all other tests.
TAPROOT_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT"

def dump_json_test(tx, input_utxos, idx, success, failure):
    spender = input_utxos[idx].spender
    # Determine flags to dump
    flags = LEGACY_FLAGS if spender.comment.startswith("legacy/") or spender.comment.startswith("inactive/") else TAPROOT_FLAGS

    fields = [
        ("tx", tx.serialize().hex()),
        ("prevouts", [x.output.serialize().hex() for x in input_utxos]),
        ("index", idx),
        ("flags", flags),
        ("comment", spender.comment)
    ]

    # The "final" field indicates that a spend should be always valid, even with more validation flags enabled
    # than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate).
    if spender.is_standard:

Review comment (suggestion):
I don't have a firm grasp on script execution yet (as I have not dealt with enough script execution) and there are few things going on inside the "test_spenders" as well that I have not gone through in its entirety. These seem like focussed test cases that I am trying to reason through.

Are following the ways these 3 tapscript scenarios executed?

1. During script execution, the empty string witness data is dropped because of "OP_DROP" and the 2 byte data is left on the stack. I'm assuming this 2 byte data counts as 1 stack item (because the stack is a vector of vector) and that's why script execution doesn't lead to an error in script execution? References based on a quick look:
- https://github.com/bitcoin/bitcoin/blob/82d1e94838ee52b979146077a53fdaa244e9260a/src/script/script.h#L588
- https://github.com/bitcoin/bitcoin/blob/82d1e94838ee52b979146077a53fdaa244e9260a/src/script/interpreter.cpp#L2061-L2062

2. The "2byte_pushdata" leaf is overridden with the "dummyleaf", and because there is no "OP_DROP" now, thus after the script execution, there is the empty string witness data left that leads to the "Script evaluated without error but finished with a false/empty top stack element" error. This shows that the emptiness of the witness data is crucial for this spender case but not so much for the first spender case because in it any witness data gets dropped due to the subsequent "OP_DROP" - I tested with "\x11" and that test case passed.

3. The "nonstd_2byte_pushdata" leaf contains no "OP_DROP", and thus after script execution, multiple items are left on the stack: a signature from the "secs[0]" key & the 2 byte data that leads to the "Stack size must be exactly one after execution" error.

Code after:

    # Create a list of scripts which will be built into a taptree
    scripts = [
        # leaf label, followed by CScript
        ("2byte_push", CScript([OP_DROP, b'\xaa\xaa'])),
        ("nonstd_2byte_push", CScript.fromhex("4c02aaaa")),
        ("dummyleaf", CScript([])),
    ]

    # Build TaprootInfo using scripts and appropriate pubkey for output creation
    tap = taproot_construct(pubs[0], scripts)

    # Finally, add spender(s).
    # Each spender embodies a test with an optional failure condition.
    # These failure conditions allow for fine-grained success/failure
    # conditions that are tested randomly.
    spenders = []

    # Named comment, using first leaf from scripts, with empty string as witness data, no optional fail condition
    add_spender(spenders, comment="tutorial/push", tap=tap, leaf="2byte_push", inputs=[b'\x00'], no_fail=True)

    # Spender with alternative failure tapscript via over-riding "failure" dictionary, along with the failure's expected err_msg / ERR_*
    add_spender(spenders, comment="tutorial/pushredux", tap=tap, leaf="2byte_push", inputs=[b'\x00'], failure={"leaf": "dummyleaf"}, **ERR_EVAL_FALSE)

    # Spender that is non-standard but otherwise valid, with extraneous signature data from inner key for optional failure condition
    add_spender(spenders, comment="tutorial/nonminpush", tap=tap, leaf="nonstd_2byte_push", key=secs[0], standard=False, failure={"inputs": [getter("sign")]}, **ERR_CLEANSTACK)

    # New scripts=[] can be defined, and rinse-repeated as necessary until the spenders list is returned for execution
    return spenders

# Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix.
LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY"
# Consensus validation flags to use in dumps for all other tests.
TAPROOT_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT"

def dump_json_test(tx, input_utxos, idx, success, failure):
    spender = input_utxos[idx].spender
    # Determine flags to dump
    flags = LEGACY_FLAGS if spender.comment.startswith("legacy/") or spender.comment.startswith("inactive/") else TAPROOT_FLAGS

    fields = [
        ("tx", tx.serialize().hex()),
        ("prevouts", [x.output.serialize().hex() for x in input_utxos]),
        ("index", idx),
        ("flags", flags),
        ("comment", spender.comment)
    ]

    # The "final" field indicates that a spend should be always valid, even with more validation flags enabled
    # than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate).
    if spender.is_standard: