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

Code before:

    // Only MAX_DUST_OUTPUTS_PER_TX dust is permitted(on otherwise valid ephemeral dust)
    if (GetDust(tx, dust_relay_fee).size() > MAX_DUST_OUTPUTS_PER_TX) {
        reason = "dust";
        return false;
    }

    // only one OP_RETURN txout is permitted
    if (nDataOut > 1) {
        reason = "multi-op-return";
        return false;
    }

    return true;
}

/**
 * Check the total number of non-witness sigops across the whole transaction, as per BIP54.
 */
static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs)
{
    Assert(!tx.IsCoinBase());

    unsigned int sigops{0};
    for (unsigned i{0}; i < tx.vin.size(); ++i) {
        const auto& prev_txo{inputs.AccessCoin(tx.vin[i].prevout).out};

        // Unlike the existing block wide sigop limit, BIP54 counts sigops when they are actually executed.
        // This means sigops in the spent scriptpubkey count toward the limit.
        // `fAccurate` means correctly accounting sigops for CHECKMULTISIGs with 16 pubkeys or less. This
        // method of accounting was introduced by BIP16, and BIP54 reuses it.
        sigops += tx.vin[i].scriptSig.GetSigOpCount(/*fAccurate=*/true);
        sigops += prev_txo.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);

        if (sigops > MAX_TX_LEGACY_SIGOPS) {
            return false;
        }
    }

    return true;
}

/**
 * Check transaction inputs.
 *
 * This does three things:
 *  * Prevents mempool acceptance of spends of future
 *    segwit versions we don't know how to validate
 *  * Mitigates a potential denial-of-service attack with
 *    P2SH scripts with a crazy number of expensive
 *    CHECKSIG/CHECKMULTISIG operations.

Review comment (performance):
I have a similar concern to https://github.com/bitcoin/bitcoin/pull/32521#issuecomment-3008785284, the benchmarks indicate a ~**66%** slower standardness check for me locally (similar slowdown shown by https://corecheck.dev/bitcoin/bitcoin/pulls/32521):

```bash
# latest commit, rebased: 811115c75da74ef50aa37cfcb807e4ac1a767604
%  build/bin/bench_bitcoin -filter='CCoinsCaching' --min-time=10000 # ran 3 times
```

> Before (without `CheckSigopsBIP54`):

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|              141.29 |        7,077,842.70 |    0.4% |     10.94 | `CCoinsCaching`
|              141.16 |        7,083,924.08 |    0.2% |     10.98 | `CCoinsCaching`
|              140.31 |        7,127,285.74 |    0.3% |     10.97 | `CCoinsCaching`

> After:

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|              212.20 |        4,712,429.73 |    0.3% |     11.00 | `CCoinsCaching`
|              214.75 |        4,656,677.68 |    0.3% |     10.98 | `CCoinsCaching`
|              211.30 |        4,732,499.91 |    0.1% |     11.00 | `CCoinsCaching`


Code after:
    }

    // Only MAX_DUST_OUTPUTS_PER_TX dust is permitted(on otherwise valid ephemeral dust)
    if (GetDust(tx, dust_relay_fee).size() > MAX_DUST_OUTPUTS_PER_TX) {
        reason = "dust";
        return false;
    }

    return true;
}

/**
 * Check the total number of non-witness sigops across the whole transaction, as per BIP54.
 */
static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs)
{
    Assert(!tx.IsCoinBase());

    unsigned int sigops{0};
    for (const auto& txin: tx.vin) {
        const auto& prev_txo{inputs.AccessCoin(txin.prevout).out};

        // Unlike the existing block wide sigop limit which counts sigops present in the block
        // itself (including the scriptPubKey which is not executed until spending later), BIP54
        // counts sigops in the block where they are potentially executed (only).
        // This means sigops in the spent scriptPubKey count toward the limit.
        // `fAccurate` means correctly accounting sigops for CHECKMULTISIGs(VERIFY) with 16 pubkeys
        // or fewer. This method of accounting was introduced by BIP16, and BIP54 reuses it.
        // The GetSigOpCount call on the previous scriptPubKey counts both bare and P2SH sigops.
        sigops += txin.scriptSig.GetSigOpCount(/*fAccurate=*/true);
        sigops += prev_txo.scriptPubKey.GetSigOpCount(txin.scriptSig);

        if (sigops > MAX_TX_LEGACY_SIGOPS) {
            return false;
        }
    }

    return true;
}

/**
 * Check transaction inputs.
 *
 * This does three things:
 *  * Prevents mempool acceptance of spends of future
 *    segwit versions we don't know how to validate
 *  * Mitigates a potential denial-of-service attack with
 *    P2SH scripts with a crazy number of expensive
 *    CHECKSIG/CHECKMULTISIG operations.
 *  * Prevents spends of unknown/irregular scriptPubKeys,
 *    which mitigates potential denial-of-service attacks