refactor: Construct g_verify_flag_names on first use #33600

pull maflcko wants to merge 1 commits into bitcoin:master from maflcko:2510-cofu changing 3 files +30 −26
  1. maflcko commented at 3:05 PM on October 10, 2025: member

    The current usage of the g_verify_flag_names map seems fine and I can not see a static initialization order fiasco here.

    However, it seems brittle to hope this remains the case in the future. Also, it triggers a msan false-positive in the fuzz CI task. (C.f https://github.com/bitcoin-core/qa-assets/actions/runs/18352815555/job/52413137315?pr=241#step:7:5245)

    So just apply the "Construct on first use" idiom.

  2. refactor: Construct g_verify_flag_names on first use faa9d10c84
  3. DrahtBot added the label Refactoring on Oct 10, 2025
  4. DrahtBot commented at 3:05 PM on October 10, 2025: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33600.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK kevkevinpal, janb84, stickies-v, ajtowns

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. maflcko commented at 7:08 PM on October 10, 2025: member

    <details><summary>for reference, the msan false positive CI logs</summary>

    master:

    ...
    + /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/test/fuzz/test_runner.py -j16 -l DEBUG /ci_container_base/ci/scratch/qa-assets/fuzz_corpora/ --empty_min_time=60
    ==11639==WARNING: MemorySanitizer: use-of-uninitialized-value
        [#0](/bitcoin-bitcoin/0/) 0xaaaad25c2408 in SetArgs(int, char**) /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/src/test/fuzz/util/./test/fuzz/fuzz.cpp:52:5
        [#1](/bitcoin-bitcoin/1/) 0xaaaad25c2408 in LLVMFuzzerInitialize /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/src/test/fuzz/util/./test/fuzz/fuzz.cpp:223:5
        [#2](/bitcoin-bitcoin/2/) 0xaaaad193a1dc in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz+0xe4a1dc) (BuildId: d44700e52b2315b458e122acf4a7dd36326c741c)
        [#3](/bitcoin-bitcoin/3/) 0xaaaad19660f4 in main (/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz+0xe760f4) (BuildId: d44700e52b2315b458e122acf4a7dd36326c741c)
        [#4](/bitcoin-bitcoin/4/) 0xffff87fb84c0  (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
        [#5](/bitcoin-bitcoin/5/) 0xffff87fb8594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
        [#6](/bitcoin-bitcoin/6/) 0xaaaad1931aec in _start (/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz+0xe41aec) (BuildId: d44700e52b2315b458e122acf4a7dd36326c741c)
    
      Member fields were destroyed
        [#0](/bitcoin-bitcoin/0/) 0xaaaad19a4b34 in __sanitizer_dtor_callback_fields (/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz+0xeb4b34) (BuildId: d44700e52b2315b458e122acf4a7dd36326c741c)
        [#1](/bitcoin-bitcoin/1/) 0xaaaad192b7ec in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::~basic_string() /cxx_build/include/c++/v1/string:903:3
        [#2](/bitcoin-bitcoin/2/) 0xaaaad192b7ec in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>::~basic_string() /cxx_build/include/c++/v1/string:1208:3
        [#3](/bitcoin-bitcoin/3/) 0xaaaad192b7ec in std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, script_verify_flag_name>::~pair() /cxx_build/include/c++/v1/__utility/pair.h:90:8
        [#4](/bitcoin-bitcoin/4/) 0xaaaad192b7ec in __cxx_global_var_init.18 /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/src/./script/interpreter.cpp:2167:54
        [#5](/bitcoin-bitcoin/5/) 0xaaaad192b7ec in _GLOBAL__sub_I_interpreter.cpp /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/src/./script/interpreter.cpp
        [#6](/bitcoin-bitcoin/6/) 0xffff87fb8610 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28610) (BuildId: d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
        [#7](/bitcoin-bitcoin/7/) 0xaaaad1931aec in _start (/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz+0xe41aec) (BuildId: d44700e52b2315b458e122acf4a7dd36326c741c)
    
    SUMMARY: MemorySanitizer: use-of-uninitialized-value /ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/src/test/fuzz/util/./test/fuzz/fuzz.cpp:52:5 in SetArgs(int, char**)
    Exiting
    Traceback (most recent call last):
      File "/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/test/fuzz/test_runner.py", line 404, in <module>
        main()
      File "/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/test/fuzz/test_runner.py", line 111, in main
        test_list_all = parse_test_list(
                        ^^^^^^^^^^^^^^^^
      File "/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/test/fuzz/test_runner.py", line 390, in parse_test_list
        test_list_all = subprocess.run(
                        ^^^^^^^^^^^^^^^
      File "/usr/lib/python3.12/subprocess.py", line 571, in run
        raise CalledProcessError(retcode, process.args,
    subprocess.CalledProcessError: Command '/ci_container_base/ci/scratch/build-aarch64-unknown-linux-gnu/bin/fuzz' returned non-zero exit status 1.
    Command '['./ci/test/02_run_container.sh']' returned non-zero exit status 1.
    

    This pull:

    (all good)

    </details>

  6. in src/test/transaction_tests.cpp:53 in faa9d10c84
      49 | @@ -50,7 +50,7 @@ typedef std::vector<unsigned char> valtype;
      50 |  static CFeeRate g_dust{DUST_RELAY_TX_FEE};
      51 |  static bool g_bare_multi{DEFAULT_PERMIT_BAREMULTISIG};
      52 |  
      53 | -static const std::map<std::string, script_verify_flag_name>& mapFlagNames = g_verify_flag_names;
      54 | +static const std::map<std::string, script_verify_flag_name>& mapFlagNames = ScriptFlagNamesToEnum();
    


    ajtowns commented at 7:06 AM on October 12, 2025:

    static const auto& mFN = SFNTE(); ?


    maflcko commented at 2:22 PM on October 15, 2025:

    thx, may use auto& when I have to re-touch.

  7. kevkevinpal commented at 4:29 PM on October 12, 2025: contributor

    ACK faa9d10

    Looks good to me, makes sense to follow the construct on first use idiom.

    I also did a quick grep to see if g_verify_flag_names was used anywhere else, and it was not

  8. janb84 commented at 12:01 PM on October 13, 2025: contributor

    lgtm ACK faa9d10c84bc6b465cbca266468990cc716b4300

    PR refactors g_verify_flag_names init to use the "Construct on first use" Idiom to prevent a "static initialization order fiasco" error. This currently gives some false positives, imho a valid change to remove the false positives and to prevent a "static initialization order fiasco" type of error in the future.

    • code review ✅
    • build en tests run ✅
  9. in src/script/interpreter.cpp:2170 in faa9d10c84
    2188 | -    FLAG_NAME(DISCOURAGE_UPGRADABLE_PUBKEYTYPE),
    2189 | -    FLAG_NAME(DISCOURAGE_OP_SUCCESS),
    2190 | -    FLAG_NAME(DISCOURAGE_UPGRADABLE_TAPROOT_VERSION),
    2191 | -};
    2192 | +    static const std::map<std::string, script_verify_flag_name> g_names_to_enum{
    2193 | +        FLAG_NAME(P2SH),
    


    stickies-v commented at 6:32 PM on October 13, 2025:

    An alternative approach would be to use a constexpr array and make the whole thing compile-time? As a nice benefit, removes the FLAG_NAME macro which imo doesn't simplify things enough to be worth it.

    <details> <summary>git diff on faa9d10c84</summary>

    diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
    index abd99fc365..731c0a070f 100644
    --- a/src/script/interpreter.cpp
    +++ b/src/script/interpreter.cpp
    @@ -2163,36 +2163,6 @@ size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey,
         return 0;
     }
     
    -const std::map<std::string, script_verify_flag_name>& ScriptFlagNamesToEnum()
    -{
    -#define FLAG_NAME(flag) {std::string(#flag), SCRIPT_VERIFY_##flag}
    -    static const std::map<std::string, script_verify_flag_name> g_names_to_enum{
    -        FLAG_NAME(P2SH),
    -        FLAG_NAME(STRICTENC),
    -        FLAG_NAME(DERSIG),
    -        FLAG_NAME(LOW_S),
    -        FLAG_NAME(SIGPUSHONLY),
    -        FLAG_NAME(MINIMALDATA),
    -        FLAG_NAME(NULLDUMMY),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_NOPS),
    -        FLAG_NAME(CLEANSTACK),
    -        FLAG_NAME(MINIMALIF),
    -        FLAG_NAME(NULLFAIL),
    -        FLAG_NAME(CHECKLOCKTIMEVERIFY),
    -        FLAG_NAME(CHECKSEQUENCEVERIFY),
    -        FLAG_NAME(WITNESS),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM),
    -        FLAG_NAME(WITNESS_PUBKEYTYPE),
    -        FLAG_NAME(CONST_SCRIPTCODE),
    -        FLAG_NAME(TAPROOT),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_PUBKEYTYPE),
    -        FLAG_NAME(DISCOURAGE_OP_SUCCESS),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_TAPROOT_VERSION),
    -    };
    -#undef FLAG_NAME
    -    return g_names_to_enum;
    -}
    -
     std::vector<std::string> GetScriptFlagNames(script_verify_flags flags)
     {
         std::vector<std::string> res;
    @@ -2200,9 +2170,9 @@ std::vector<std::string> GetScriptFlagNames(script_verify_flags flags)
             return res;
         }
         script_verify_flags leftover = flags;
    -    for (const auto& [name, flag] : ScriptFlagNamesToEnum()) {
    +    for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
             if ((flags & flag) != 0) {
    -            res.push_back(name);
    +            res.emplace_back(name);
                 leftover &= ~flag;
             }
         }
    diff --git a/src/script/interpreter.h b/src/script/interpreter.h
    index 7f8a1f7a41..04611bc5d6 100644
    --- a/src/script/interpreter.h
    +++ b/src/script/interpreter.h
    @@ -381,7 +381,29 @@ size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey,
     
     int FindAndDelete(CScript& script, const CScript& b);
     
    -const std::map<std::string, script_verify_flag_name>& ScriptFlagNamesToEnum();
    +inline constexpr std::array SCRIPT_FLAG_NAMES{std::to_array<std::pair<std::string_view, script_verify_flag_name>>({
    +    {"CHECKLOCKTIMEVERIFY", SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY},
    +    {"CHECKSEQUENCEVERIFY", SCRIPT_VERIFY_CHECKSEQUENCEVERIFY},
    +    {"CLEANSTACK", SCRIPT_VERIFY_CLEANSTACK},
    +    {"CONST_SCRIPTCODE", SCRIPT_VERIFY_CONST_SCRIPTCODE},
    +    {"DERSIG", SCRIPT_VERIFY_DERSIG},
    +    {"DISCOURAGE_OP_SUCCESS", SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS},
    +    {"DISCOURAGE_UPGRADABLE_NOPS", SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS},
    +    {"DISCOURAGE_UPGRADABLE_PUBKEYTYPE", SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE},
    +    {"DISCOURAGE_UPGRADABLE_TAPROOT_VERSION", SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION},
    +    {"DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM", SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM},
    +    {"LOW_S", SCRIPT_VERIFY_LOW_S},
    +    {"MINIMALDATA", SCRIPT_VERIFY_MINIMALDATA},
    +    {"MINIMALIF", SCRIPT_VERIFY_MINIMALIF},
    +    {"NULLDUMMY", SCRIPT_VERIFY_NULLDUMMY},
    +    {"NULLFAIL", SCRIPT_VERIFY_NULLFAIL},
    +    {"P2SH", SCRIPT_VERIFY_P2SH},
    +    {"SIGPUSHONLY", SCRIPT_VERIFY_SIGPUSHONLY},
    +    {"STRICTENC", SCRIPT_VERIFY_STRICTENC},
    +    {"TAPROOT", SCRIPT_VERIFY_TAPROOT},
    +    {"WITNESS", SCRIPT_VERIFY_WITNESS},
    +    {"WITNESS_PUBKEYTYPE", SCRIPT_VERIFY_WITNESS_PUBKEYTYPE},
    +})};
     
     std::vector<std::string> GetScriptFlagNames(script_verify_flags flags);
     
    diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
    index 0ab9fbb479..e234153825 100644
    --- a/src/test/transaction_tests.cpp
    +++ b/src/test/transaction_tests.cpp
    @@ -50,7 +50,6 @@ typedef std::vector<unsigned char> valtype;
     static CFeeRate g_dust{DUST_RELAY_TX_FEE};
     static bool g_bare_multi{DEFAULT_PERMIT_BAREMULTISIG};
     
    -static const std::map<std::string, script_verify_flag_name>& mapFlagNames = ScriptFlagNamesToEnum();
     
     script_verify_flags ParseScriptFlags(std::string strFlags)
     {
    @@ -60,20 +59,21 @@ script_verify_flags ParseScriptFlags(std::string strFlags)
         std::vector<std::string> words = SplitString(strFlags, ',');
         for (const std::string& word : words)
         {
    -        if (!mapFlagNames.count(word)) {
    +        auto it = std::ranges::find(SCRIPT_FLAG_NAMES, word, &decltype(SCRIPT_FLAG_NAMES)::value_type::first);
    +        if (it == SCRIPT_FLAG_NAMES.end()) {
                 BOOST_ERROR("Bad test: unknown verification flag '" << word << "'");
                 continue;
             }
    -        flags |= mapFlagNames.at(word);
    +        flags |= it->second;
         }
         return flags;
     }
     
    -// Check that all flags in STANDARD_SCRIPT_VERIFY_FLAGS are present in mapFlagNames.
    -bool CheckMapFlagNames()
    +// Check that all flags in STANDARD_SCRIPT_VERIFY_FLAGS are present in SCRIPT_FLAG_NAMES.
    +bool CheckScriptFlagNames()
     {
         script_verify_flags standard_flags_missing{STANDARD_SCRIPT_VERIFY_FLAGS};
    -    for (const auto& pair : mapFlagNames) {
    +    for (const auto& pair : SCRIPT_FLAG_NAMES) {
             standard_flags_missing &= ~(pair.second);
         }
         return standard_flags_missing == 0;
    @@ -142,11 +142,11 @@ script_verify_flags FillFlags(script_verify_flags flags)
     // Exclude each possible script verify flag from flags. Returns a set of these flag combinations
     // that are valid and without duplicates. For example: if flags=1111 and the 4 possible flags are
     // 0001, 0010, 0100, and 1000, this should return the set {0111, 1011, 1101, 1110}.
    -// Assumes that mapFlagNames contains all script verify flags.
    +// Assumes that SCRIPT_FLAG_NAMES contains all script verify flags.
     std::set<script_verify_flags> ExcludeIndividualFlags(script_verify_flags flags)
     {
         std::set<script_verify_flags> flags_combos;
    -    for (const auto& pair : mapFlagNames) {
    +    for (const auto& pair : SCRIPT_FLAG_NAMES) {
             script_verify_flags flags_excluding_one = TrimFlags(flags & ~(pair.second));
             if (flags != flags_excluding_one) {
                 flags_combos.insert(flags_excluding_one);
    @@ -159,7 +159,7 @@ BOOST_FIXTURE_TEST_SUITE(transaction_tests, BasicTestingSetup)
     
     BOOST_AUTO_TEST_CASE(tx_valid)
     {
    -    BOOST_CHECK_MESSAGE(CheckMapFlagNames(), "mapFlagNames is missing a script verification flag");
    +    BOOST_CHECK_MESSAGE(CheckScriptFlagNames(), "SCRIPT_FLAG_NAMES is missing a script verification flag");
         // Read tests from test/data/tx_valid.json
         UniValue tests = read_json(json_tests::tx_valid);
     
    @@ -223,7 +223,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
                                     "Tx unexpectedly failed: " << strTest);
     
                 // Backwards compatibility of script verification flags: Removing any flag(s) should not invalidate a valid transaction
    -            for (const auto& [name, flag] : mapFlagNames) {
    +            for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
                     // Removing individual flags
                     script_verify_flags flags = TrimFlags(~(verify_flags | flag));
                     if (!CheckTxScripts(tx, mapprevOutScriptPubKeys, mapprevOutValues, flags, txdata, strTest, /*expect_valid=*/true)) {
    @@ -314,7 +314,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
                                     "Tx unexpectedly passed: " << strTest);
     
                 // Backwards compatibility of script verification flags: Adding any flag(s) should not validate an invalid transaction
    -            for (const auto& [name, flag] : mapFlagNames) {
    +            for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
                     script_verify_flags flags = FillFlags(verify_flags | flag);
                     // Adding individual flags
                     if (!CheckTxScripts(tx, mapprevOutScriptPubKeys, mapprevOutValues, flags, txdata, strTest, /*expect_valid=*/false)) {
    
    

    </details>


    ajtowns commented at 8:35 PM on October 13, 2025:

    The value of the FLAG_NAME macro is that it makes any typos in the strings that would cause a mismatch with the enum names an immediate compile time error. It also makes it easier to type and easier to review. Massive -1 on dropping it. C++26 reflection would be better, when it's available, of course.


    ajtowns commented at 8:56 PM on October 13, 2025:

    I had a look at this approach too. With C++20, I believe you can ensure the array is sorted at compile time with:

    template<typename V, size_t N>
    consteval auto SortedArray(const std::array<V,N>& pairs)
    {
        std::array<V,N> result = pairs;
        std::sort(result.begin(), result.end());
        return result;
    }
    #define FLAG_NAME(flag) std::pair<std::string_view, script_verify_flag_name>{#flag, SCRIPT_VERIFY_##flag}
    constexpr auto g_verify_flag_names = SortedArray(std::array{
        FLAG_NAME(P2SH),
        ...
    });
    

    which lets the compiler avoid one source of bugs. static_assert(std::ranges::is_sorted(g_verify_flag_names)) might be equally effective though. std::ranges:binary_search then lets you do lookups essentially the same way map does, though I suppose with only up to ~60 entries, that doesn't matter that much.

    Having the initialization be private to interpreter.cpp and exposing functions that (a) converts flags to a string, (b) looks up a flag from a string, (c) return a span over the array seem like it would cover all the uses for the info.


    maflcko commented at 2:05 PM on October 15, 2025:

    std::ranges:binary_search then lets you do lookups essentially the same way map does, though I suppose with only up to ~60 entries, that doesn't matter that much.

    I think binary_search returns a bool, not an iterator, so it can not be used for lookup. I think you just meant to say "Binary search operations" (https://en.cppreference.com/w/cpp/algorithm/ranges.html#Binary_search_operations_.28on_sorted_ranges.29)

    I think I'll keep this as-is for now. I can push the array approach (keeping the macro, but I don't think it matters much.

    <details><summary>array approach (keeping the macro)</summary>

    Also has a std::ranges::sort, but I'll probably drop that too, if I end up pushing this.

    diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
    index abd99fc365..731c0a070f 100644
    --- a/src/script/interpreter.cpp
    +++ b/src/script/interpreter.cpp
    @@ -2163,36 +2163,6 @@ size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey,
         return 0;
     }
     
    -const std::map<std::string, script_verify_flag_name>& ScriptFlagNamesToEnum()
    -{
    -#define FLAG_NAME(flag) {std::string(#flag), SCRIPT_VERIFY_##flag}
    -    static const std::map<std::string, script_verify_flag_name> g_names_to_enum{
    -        FLAG_NAME(P2SH),
    -        FLAG_NAME(STRICTENC),
    -        FLAG_NAME(DERSIG),
    -        FLAG_NAME(LOW_S),
    -        FLAG_NAME(SIGPUSHONLY),
    -        FLAG_NAME(MINIMALDATA),
    -        FLAG_NAME(NULLDUMMY),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_NOPS),
    -        FLAG_NAME(CLEANSTACK),
    -        FLAG_NAME(MINIMALIF),
    -        FLAG_NAME(NULLFAIL),
    -        FLAG_NAME(CHECKLOCKTIMEVERIFY),
    -        FLAG_NAME(CHECKSEQUENCEVERIFY),
    -        FLAG_NAME(WITNESS),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM),
    -        FLAG_NAME(WITNESS_PUBKEYTYPE),
    -        FLAG_NAME(CONST_SCRIPTCODE),
    -        FLAG_NAME(TAPROOT),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_PUBKEYTYPE),
    -        FLAG_NAME(DISCOURAGE_OP_SUCCESS),
    -        FLAG_NAME(DISCOURAGE_UPGRADABLE_TAPROOT_VERSION),
    -    };
    -#undef FLAG_NAME
    -    return g_names_to_enum;
    -}
    -
     std::vector<std::string> GetScriptFlagNames(script_verify_flags flags)
     {
         std::vector<std::string> res;
    @@ -2200,9 +2170,9 @@ std::vector<std::string> GetScriptFlagNames(script_verify_flags flags)
             return res;
         }
         script_verify_flags leftover = flags;
    -    for (const auto& [name, flag] : ScriptFlagNamesToEnum()) {
    +    for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
             if ((flags & flag) != 0) {
    -            res.push_back(name);
    +            res.emplace_back(name);
                 leftover &= ~flag;
             }
         }
    diff --git a/src/script/interpreter.h b/src/script/interpreter.h
    index 7f8a1f7a41..54d7d2e933 100644
    --- a/src/script/interpreter.h
    +++ b/src/script/interpreter.h
    @@ -381,7 +381,35 @@ size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey,
     
     int FindAndDelete(CScript& script, const CScript& b);
     
    -const std::map<std::string, script_verify_flag_name>& ScriptFlagNamesToEnum();
    +inline constexpr std::array SCRIPT_FLAG_NAMES{[]() {
    +#define FLAG_NAME(flag) std::pair<std::string_view, script_verify_flag_name>{#flag, SCRIPT_VERIFY_##flag}
    +    std::array result{
    +        FLAG_NAME(P2SH),
    +        FLAG_NAME(STRICTENC),
    +        FLAG_NAME(DERSIG),
    +        FLAG_NAME(LOW_S),
    +        FLAG_NAME(SIGPUSHONLY),
    +        FLAG_NAME(MINIMALDATA),
    +        FLAG_NAME(NULLDUMMY),
    +        FLAG_NAME(DISCOURAGE_UPGRADABLE_NOPS),
    +        FLAG_NAME(CLEANSTACK),
    +        FLAG_NAME(MINIMALIF),
    +        FLAG_NAME(NULLFAIL),
    +        FLAG_NAME(CHECKLOCKTIMEVERIFY),
    +        FLAG_NAME(CHECKSEQUENCEVERIFY),
    +        FLAG_NAME(WITNESS),
    +        FLAG_NAME(DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM),
    +        FLAG_NAME(WITNESS_PUBKEYTYPE),
    +        FLAG_NAME(CONST_SCRIPTCODE),
    +        FLAG_NAME(TAPROOT),
    +        FLAG_NAME(DISCOURAGE_UPGRADABLE_PUBKEYTYPE),
    +        FLAG_NAME(DISCOURAGE_OP_SUCCESS),
    +        FLAG_NAME(DISCOURAGE_UPGRADABLE_TAPROOT_VERSION),
    +    };
    +    std::ranges::sort(result);
    +    return result;
    +#undef FLAG_NAME
    +}()};
     
     std::vector<std::string> GetScriptFlagNames(script_verify_flags flags);
     
    diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
    index 0ab9fbb479..e234153825 100644
    --- a/src/test/transaction_tests.cpp
    +++ b/src/test/transaction_tests.cpp
    @@ -50,7 +50,6 @@ typedef std::vector<unsigned char> valtype;
     static CFeeRate g_dust{DUST_RELAY_TX_FEE};
     static bool g_bare_multi{DEFAULT_PERMIT_BAREMULTISIG};
     
    -static const std::map<std::string, script_verify_flag_name>& mapFlagNames = ScriptFlagNamesToEnum();
     
     script_verify_flags ParseScriptFlags(std::string strFlags)
     {
    @@ -60,20 +59,21 @@ script_verify_flags ParseScriptFlags(std::string strFlags)
         std::vector<std::string> words = SplitString(strFlags, ',');
         for (const std::string& word : words)
         {
    -        if (!mapFlagNames.count(word)) {
    +        auto it = std::ranges::find(SCRIPT_FLAG_NAMES, word, &decltype(SCRIPT_FLAG_NAMES)::value_type::first);
    +        if (it == SCRIPT_FLAG_NAMES.end()) {
                 BOOST_ERROR("Bad test: unknown verification flag '" << word << "'");
                 continue;
             }
    -        flags |= mapFlagNames.at(word);
    +        flags |= it->second;
         }
         return flags;
     }
     
    -// Check that all flags in STANDARD_SCRIPT_VERIFY_FLAGS are present in mapFlagNames.
    -bool CheckMapFlagNames()
    +// Check that all flags in STANDARD_SCRIPT_VERIFY_FLAGS are present in SCRIPT_FLAG_NAMES.
    +bool CheckScriptFlagNames()
     {
         script_verify_flags standard_flags_missing{STANDARD_SCRIPT_VERIFY_FLAGS};
    -    for (const auto& pair : mapFlagNames) {
    +    for (const auto& pair : SCRIPT_FLAG_NAMES) {
             standard_flags_missing &= ~(pair.second);
         }
         return standard_flags_missing == 0;
    @@ -142,11 +142,11 @@ script_verify_flags FillFlags(script_verify_flags flags)
     // Exclude each possible script verify flag from flags. Returns a set of these flag combinations
     // that are valid and without duplicates. For example: if flags=1111 and the 4 possible flags are
     // 0001, 0010, 0100, and 1000, this should return the set {0111, 1011, 1101, 1110}.
    -// Assumes that mapFlagNames contains all script verify flags.
    +// Assumes that SCRIPT_FLAG_NAMES contains all script verify flags.
     std::set<script_verify_flags> ExcludeIndividualFlags(script_verify_flags flags)
     {
         std::set<script_verify_flags> flags_combos;
    -    for (const auto& pair : mapFlagNames) {
    +    for (const auto& pair : SCRIPT_FLAG_NAMES) {
             script_verify_flags flags_excluding_one = TrimFlags(flags & ~(pair.second));
             if (flags != flags_excluding_one) {
                 flags_combos.insert(flags_excluding_one);
    @@ -159,7 +159,7 @@ BOOST_FIXTURE_TEST_SUITE(transaction_tests, BasicTestingSetup)
     
     BOOST_AUTO_TEST_CASE(tx_valid)
     {
    -    BOOST_CHECK_MESSAGE(CheckMapFlagNames(), "mapFlagNames is missing a script verification flag");
    +    BOOST_CHECK_MESSAGE(CheckScriptFlagNames(), "SCRIPT_FLAG_NAMES is missing a script verification flag");
         // Read tests from test/data/tx_valid.json
         UniValue tests = read_json(json_tests::tx_valid);
     
    @@ -223,7 +223,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
                                     "Tx unexpectedly failed: " << strTest);
     
                 // Backwards compatibility of script verification flags: Removing any flag(s) should not invalidate a valid transaction
    -            for (const auto& [name, flag] : mapFlagNames) {
    +            for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
                     // Removing individual flags
                     script_verify_flags flags = TrimFlags(~(verify_flags | flag));
                     if (!CheckTxScripts(tx, mapprevOutScriptPubKeys, mapprevOutValues, flags, txdata, strTest, /*expect_valid=*/true)) {
    @@ -314,7 +314,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
                                     "Tx unexpectedly passed: " << strTest);
     
                 // Backwards compatibility of script verification flags: Adding any flag(s) should not validate an invalid transaction
    -            for (const auto& [name, flag] : mapFlagNames) {
    +            for (const auto& [name, flag] : SCRIPT_FLAG_NAMES) {
                     script_verify_flags flags = FillFlags(verify_flags | flag);
                     // Adding individual flags
                     if (!CheckTxScripts(tx, mapprevOutScriptPubKeys, mapprevOutValues, flags, txdata, strTest, /*expect_valid=*/false)) {
    

    </details>

  10. stickies-v approved
  11. stickies-v commented at 6:44 PM on October 13, 2025: contributor

    ACK faa9d10c84bc6b465cbca266468990cc716b4300

    I would prefer the constexpr approach, but either approach is acceptable to me.

  12. ajtowns commented at 2:44 PM on October 20, 2025: contributor

    ACK faa9d10c84bc6b465cbca266468990cc716b4300

    Didn't realise this wasn't already merged. This adds a trivial implicit mutex when this variable is accessed which is a little lame, but it's not accessed in performance critical places, and even if it were, it's fast anyway.

  13. maflcko commented at 9:49 AM on October 21, 2025: member

    For reference, the msan false-positive can also be worked around via #33666. However, I still think this pull makes sense, because it is already reviewed and still comes with the other mentioned benefits at basically no cost. (If there were a cost, we could go with the compile-time stuff, but that diff is a bit larger, see above).

  14. glozow merged this on Oct 23, 2025
  15. glozow closed this on Oct 23, 2025

  16. maflcko deleted the branch on Oct 23, 2025
  17. fanquake referenced this in commit f6ba97cea1 on Oct 24, 2025
  18. sedited referenced this in commit f50d90fcbb on Oct 29, 2025
  19. fanquake referenced this in commit 5ffa63d681 on Nov 4, 2025
  20. stickies-v referenced this in commit a785fe6759 on Nov 5, 2025
  21. stringintech referenced this in commit b82132c4d1 on Nov 6, 2025
  22. yuvicc referenced this in commit f620a2206b on Nov 16, 2025
  23. Kino1994 referenced this in commit c62627b201 on Jun 28, 2026
  24. Kino1994 referenced this in commit d5009a5075 on Jun 28, 2026
  25. BigcoinBGC referenced this in commit 9fe07a0c50 on Jun 30, 2026
  26. BigcoinBGC referenced this in commit d9112121d3 on Jun 30, 2026
  27. Kino1994 referenced this in commit 18d13b7f35 on Aug 19, 2026
  28. Kino1994 referenced this in commit f49a4c6f05 on Aug 19, 2026
  29. bitcoin locked this on Oct 23, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-31 12:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me