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

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-04-22 06:12 UTC

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