Repository: bitcoin/bitcoin
File: src/test/transaction_tests.cpp (C++)

Code before:
    }

    // Check anchor outputs
    t.vout[0].scriptPubKey = CScript() << OP_1 << ANCHOR_BYTES;
    BOOST_CHECK(t.vout[0].scriptPubKey.IsPayToAnchor());
    t.vout[0].nValue = 240;
    CheckIsStandard(t);
    t.vout[0].nValue = 239;
    CheckIsNotStandard(t, "dust");
}

BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
{
    CCoinsView coins_dummy;
    CCoinsViewCache coins(&coins_dummy);
    SignatureData dummy_sigdata;
    FillableSigningProvider keystore;
    CKey key;
    key.MakeNewKey(true);
    BOOST_REQUIRE(keystore.AddKey(key));

    // Create a pathological P2SH script padded with as many sigops as is standard.
    CScript max_sigops_redeem_script{CScript() << std::vector<unsigned char>{} << key.GetPubKey()};
    for (unsigned i{0}; i < MAX_P2SH_SIGOPS - 1; ++i) max_sigops_redeem_script << OP_2DUP << OP_CHECKSIG << OP_DROP;
    max_sigops_redeem_script << OP_CHECKSIG << OP_NOT;
    const CScript max_sigops_p2sh{GetScriptForDestination(ScriptHash(max_sigops_redeem_script))};

    // Create a transaction fanning out as many such P2SH outputs as is standard to spend in a
    // single transaction, and a transaction spending them.
    CMutableTransaction tx_create, tx_max_sigops;
    const unsigned p2sh_inputs_count{MAX_TX_LEGACY_SIGOPS / MAX_P2SH_SIGOPS};
    tx_create.vout.reserve(p2sh_inputs_count);
    for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
        tx_create.vout.emplace_back(424242 + i, max_sigops_p2sh);
    }
    auto prev_txid{tx_create.GetHash()};
    tx_max_sigops.vin.reserve(p2sh_inputs_count);
    for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
        tx_max_sigops.vin.emplace_back(COutPoint(prev_txid, i), max_sigops_redeem_script);
    }

    // p2sh_inputs_count is truncated to 166 (from 166.6666..)
    BOOST_CHECK(p2sh_inputs_count * MAX_P2SH_SIGOPS < MAX_TX_LEGACY_SIGOPS);
    AddCoins(coins, CTransaction(tx_create), 0, false);

    // 2490 sigops is below the limit.
    BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));

    // Adding one more input will bump this to 2505, hitting the limit.
    tx_create.vout.emplace_back(424242, max_sigops_p2sh);
    prev_txid = tx_create.GetHash();

Review comment (refactor):
In most docs I saw (e.g. https://learnmeabitcoin.com/technical/script/p2sh/#scriptsig), P2SH starts with an `OP_0` - I understand that this corresponds to an empty vector, but maybe we can simplify it in the tests:
```suggestion
    CScript max_sigops_redeem_script{CScript() << OP_0 << key.GetPubKey()};
```

<details>
<summary>validation</summary>

```C++
    CScript max_sigops_redeem_script{CScript() << std::vector<unsigned char>{} << key.GetPubKey()};
    CScript max_sigops_redeem_script2{CScript() << OP_0 << key.GetPubKey()};
    assert(max_sigops_redeem_script == max_sigops_redeem_script2);
    assert(ScriptToAsmStr(max_sigops_redeem_script) == ScriptToAsmStr(max_sigops_redeem_script2));
```

</details>

Code after:
        t.vout[0].scriptPubKey = CScript() << (opcodetype)op << std::vector<unsigned char>(2, 0);
        t.vout[0].nValue = 240;
        CheckIsStandard(t);

        t.vout[0].nValue = 239;
        CheckIsNotStandard(t, "dust");
    }

    // Check anchor outputs
    t.vout[0].scriptPubKey = CScript() << OP_1 << ANCHOR_BYTES;
    BOOST_CHECK(t.vout[0].scriptPubKey.IsPayToAnchor());
    t.vout[0].nValue = 240;
    CheckIsStandard(t);
    t.vout[0].nValue = 239;
    CheckIsNotStandard(t, "dust");
}

BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
{
    CCoinsView coins_dummy;
    CCoinsViewCache coins(&coins_dummy);
    CKey key;
    key.MakeNewKey(true);

    // Create a pathological P2SH script padded with as many sigops as is standard.
    CScript max_sigops_redeem_script{CScript() << std::vector<unsigned char>{} << key.GetPubKey()};
    for (unsigned i{0}; i < MAX_P2SH_SIGOPS - 1; ++i) max_sigops_redeem_script << OP_2DUP << OP_CHECKSIG << OP_DROP;
    max_sigops_redeem_script << OP_CHECKSIG << OP_NOT;
    const CScript max_sigops_p2sh{GetScriptForDestination(ScriptHash(max_sigops_redeem_script))};

    // Create a transaction fanning out as many such P2SH outputs as is standard to spend in a
    // single transaction, and a transaction spending them.
    CMutableTransaction tx_create, tx_max_sigops;
    const unsigned p2sh_inputs_count{MAX_TX_LEGACY_SIGOPS / MAX_P2SH_SIGOPS};
    tx_create.vout.reserve(p2sh_inputs_count);
    for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
        tx_create.vout.emplace_back(424242 + i, max_sigops_p2sh);
    }
    auto prev_txid{tx_create.GetHash()};
    tx_max_sigops.vin.reserve(p2sh_inputs_count);
    for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
        tx_max_sigops.vin.emplace_back(prev_txid, i, CScript() << ToByteVector(max_sigops_redeem_script));
    }

    // p2sh_inputs_count is truncated to 166 (from 166.6666..)
    BOOST_CHECK_LT(p2sh_inputs_count * MAX_P2SH_SIGOPS, MAX_TX_LEGACY_SIGOPS);
    AddCoins(coins, CTransaction(tx_create), 0, false);

    // 2490 sigops is below the limit.
    BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2490);
    BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));