Merge settings one place instead of five places #15934

pull ryanofsky wants to merge 6 commits into bitcoin:master from ryanofsky:pr/mergeset changing 8 files +613 −286
  1. ryanofsky commented at 9:48 pm on May 1, 2019: member

    This is a refactoring-only change that makes it easier to add a new settings source.

    This PR doesn’t change behavior. The util_ArgsMerge and util_ChainMerge tests added in #15869 and #15988 were written specifically to confirm that ArgsManager settings are parsed, merged, and returned the same way before and after this change.

    This change:

    The original motivation for this change was to make it easy to add a new persistent setting source without introducing more bugs and inconsistencies. Two commits building on top of this to add a persistent -wallet setting are pretty straightforward and show how the new code can be extended:

    • 70675c3e4975203ad6222ba2b00c83b4e4213793 from #15935Add <datadir>/settings.json persistent settings storage
    • 04c80c40df9fc6f4734ba238ea7f65607cf88089 from #15937Add loadwallet and createwallet RPC load_on_startup options
  2. DrahtBot added the label Build system on May 1, 2019
  3. DrahtBot added the label Tests on May 1, 2019
  4. DrahtBot added the label Utils/log/libs on May 1, 2019
  5. DrahtBot commented at 0:43 am on May 2, 2019: member

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

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #17385 (WIP: refactor: Use our own integer parsing/formatting everywhere by laanwj)
    • #15454 (Remove the automatic creation and loading of the default wallet by achow101)
    • #14866 (Improve property evaluation way in bitcoin.conf by AkioNak)
    • #11082 (Add new bitcoin_rw.conf file that is used for settings modified by this software itself by luke-jr)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  6. in src/util/system.cpp:167 in 7fcb87bcc7 outdated
    163@@ -163,103 +164,34 @@ static bool InterpretBool(const std::string& strValue)
    164     return (atoi(strValue) != 0);
    165 }
    166 
    167+static bool ValidArgStr(const std::string& arg)
    


    promag commented at 1:13 pm on May 2, 2019:
    nit, IsValidArgStr?
  7. in src/util/system.cpp:169 in 7fcb87bcc7 outdated
    163@@ -163,103 +164,34 @@ static bool InterpretBool(const std::string& strValue)
    164     return (atoi(strValue) != 0);
    165 }
    166 
    167+static bool ValidArgStr(const std::string& arg)
    168+{
    169+    return !arg.empty() && arg[0] == '-';
    


    promag commented at 1:15 pm on May 2, 2019:
    Maybe arg.size() > 1 && arg[0] == '-' or do we need -?
  8. promag commented at 1:24 pm on May 2, 2019: member

    I had a refactor (which I did’t submit) that supported chaining ArgsManager. The idea was to support changing some args when calling some RPC, so a ArgsManager is created with the “overridden” args and passed thru. Is this something you are considering supporting or do you see a different approach?

    Concept ACK.

  9. ryanofsky force-pushed on May 2, 2019
  10. ryanofsky force-pushed on May 3, 2019
  11. ryanofsky force-pushed on May 7, 2019
  12. ryanofsky force-pushed on May 8, 2019
  13. ryanofsky force-pushed on May 8, 2019
  14. ryanofsky commented at 8:45 pm on May 8, 2019: member

    re: #15934#pullrequestreview-232997353 from promag

    I had a refactor (which I did’t submit) that supported chaining ArgsManager. The idea was to support changing some args when calling some RPC, so a ArgsManager is created with the “overridden” args and passed thru. Is this something you are considering supporting or do you see a different approach?

    This change does make it easier to add new settings sources (with consistent handling of negated args and things), so it should be compatible with your idea and maybe helpful.

    Depending on the situation, I think having chained or scoped settings could be a good idea or not. I do think that in wallet code and application code generally it’s good to get away from using key-value storage classes like ArgsManager or UniValue as quickly as possible, and switch to more direct representations like CCoinControl that are type safe and can be accessed more simply.

  15. ryanofsky renamed this:
    WIP: Dedup settings merge code
    Dedup settings merge code
    on May 14, 2019
  16. ryanofsky marked this as ready for review on May 14, 2019
  17. fanquake commented at 1:58 pm on May 24, 2019: member
    Concept ACK
  18. in src/util/settings.cpp:20 in 05820024fe outdated
    15+    bool config_file = false;
    16+    bool config_file_top_level = false;
    17+
    18+    Source(SettingsSpan span) : span(span) {}
    19+    Source& SetForced() { forced = true; return *this; }
    20+    Source& SetConfigFile(bool top_level) { config_file = true; config_file_top_level = top_level; return *this; }
    


    jamesob commented at 2:55 pm on May 28, 2019:
    Digging this chainable return *this; interface.

    ryanofsky commented at 7:24 pm on May 28, 2019:

    re: #15934 (review)

    These can be nice sometimes. They are also called fluent interfaces (https://en.wikipedia.org/wiki/Fluent_interface)

  19. in src/util/settings.cpp:120 in 05820024fe outdated
    115+                    }
    116+                }
    117+            }
    118+        }
    119+
    120+        prev_negated |= source.span.negated() > 0 || source.forced;
    


    jamesob commented at 3:09 pm on May 28, 2019:
    I’m slightly confused about why source.forced will cause a negation to have occurred, but I guess the rationale here is that if we’ve forced a value, we effectively want to ignore prev/subsequent values in favor of the forced one.

    ryanofsky commented at 7:31 pm on May 28, 2019:

    re: #15934 (review)

    I guess it doesn’t cause a real negation, more of an effective negation like you described. I renamed the variable result_complete to avoid mentioning negation.

  20. in src/util/system.cpp:193 in 63ce023eba outdated
    271-            if (!found_result.first) {
    272-                return false; // not set
    273-            }
    274-        }
    275-        return InterpretBool(found_result.second); // is set, so evaluate
    276+        return GetSetting(am.m_settings, no_network ? "" : am.m_network, SettingName(arg), !UseDefaultSection(am, arg), skip_negated_command_line);
    


    jamesob commented at 3:30 pm on May 28, 2019:
    Long lines like this are hard to review. Consider breaking to 100col?

    ryanofsky commented at 3:28 pm on May 29, 2019:

    re: #15934 (review)

    Looks like github diffs are 120 columns wide. I wrapped this line and put ColumnLimit: 119 in my clang-format

  21. in src/util/system.cpp:328 in 63ce023eba outdated
    328+        std::string section;
    329+        bool negated = InterpretNegatedOption(section, key, val);
    330+        // Weird behavior preserved for backwards compatibility: command line
    331+        // options with section prefixes are allowed but ignored. It would be
    332+        // better if these options triggered the IsArgKnown error below, or were
    333+        // actually used instead of silently ignored.
    


    jamesob commented at 3:44 pm on May 28, 2019:
    Thanks for the nice comment. Potentially out of scope: could we log warnings for this instead of silently ignoring?

    ryanofsky commented at 3:49 pm on May 29, 2019:

    re: #15934 (review)

    Potentially out of scope: could we log warnings for this instead of silently ignoring?

    I’m planning on making followup PRs that simplify and clean up all these “Weird behavior preserved for backwards compatibility” instances. I’d rather not add warnings in this PR partly because I’m disinclined to mix up behavior changes and refactoring changes in the same PR, but also because having some invalid options result in warnings and other invalid options result in errors seems even more strange and complicated than what the code does now.

  22. in src/test/settings_tests.cpp:82 in 1d543adae5 outdated
    59+
    60+// Regression test covering different ways config settings can be merged. The
    61+// test parses and merges settings, representing the results as strings that get
    62+// compared against an expected hash. To debug, the result strings can be dumped
    63+// to a file (see comments below).
    64+BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup)
    


    jamesob commented at 6:32 pm on May 28, 2019:

    Cool test! The formatted output is really helpful. Encourage other reviewers to run and inspect with

    0SETTINGS_MERGE_TEST_OUT=results.txt ./src/test/test_bitcoin --run_test=settings_tests/Merge
    
  23. in src/test/util.h:68 in 1d543adae5 outdated
    63+            if (skip_string || c < min_char || c > max_char) break;
    64+            prev = c;
    65+        }
    66+        if (!skip_string) fn();
    67+    }
    68+}
    


    jamesob commented at 6:34 pm on May 28, 2019:
    Note to other reviewers: the code in this file is already in master (as of 1d543adae593bdbfd954e80ed61ac907db0c5a7b) and its appearance here is only due to this branch not being based on a more recent version of master.

    MarcoFalke commented at 9:20 pm on May 28, 2019:
    So it should be rebased, imo. Otherwise git blame will return different results for the same lines depending on what commit is currently checked out.

    ryanofsky commented at 3:50 pm on May 29, 2019:

    re: #15934 (review)

    Rebased!

  24. in src/test/settings_tests.cpp:46 in 1d543adae5 outdated
    24+    for (const auto& item : GetListSetting(settings, "section", "name", false)) {
    25+        list_value.push_back(item);
    26+    }
    27+    BOOST_CHECK_EQUAL(single_value.write().c_str(), R"("val1")");
    28+    BOOST_CHECK_EQUAL(list_value.write().c_str(), R"(["val1",2])");
    29+}
    


    jamesob commented at 6:48 pm on May 28, 2019:

    Could test precedence difference between commandline and config file with the following diff (if you end up needing to rebase for some other reason):

     0diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp
     1index 36188f8471..c82ecaea8c 100644
     2--- a/src/test/settings_tests.cpp
     3+++ b/src/test/settings_tests.cpp
     4@@ -18,14 +18,28 @@ BOOST_AUTO_TEST_CASE(Simple)
     5 {
     6     util::Settings settings;
     7     settings.command_line_options["name"].push_back("val1");
     8+    settings.command_line_options["name"].push_back("val2");
     9     settings.ro_config["section"]["name"].push_back(2);
    10-    util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false);
    11-    util::SettingsValue list_value(util::SettingsValue::VARR);
    12-    for (const auto& item : GetListSetting(settings, "section", "name", false)) {
    13-        list_value.push_back(item);
    14-    }
    15-    BOOST_CHECK_EQUAL(single_value.write().c_str(), R"("val1")");
    16-    BOOST_CHECK_EQUAL(list_value.write().c_str(), R"(["val1",2])");
    17+
    18+    auto check_values = [&](util::Settings s, std::string single_val, std::string list_val) {
    19+        util::SettingsValue single_value = GetSetting(s, "section", "name", false, false);
    20+        util::SettingsValue list_value(util::SettingsValue::VARR);
    21+        for (const auto& item : GetListSetting(s, "section", "name", false)) {
    22+            list_value.push_back(item);
    23+        }
    24+        BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);
    25+        BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);
    26+    };
    27+
    28+    // The last given arg takes precedence when specified via commandline.
    29+    check_values(settings, R"("val2")", R"(["val1","val2",2])");
    30+
    31+    util::Settings settings2;
    32+    settings2.ro_config["section"]["name"].push_back("val2");
    33+    settings2.ro_config["section"]["name"].push_back("val3");
    34+
    35+    // The first given arg takes precedence when specified via config file.
    36+    check_values(settings2, R"("val2")", R"(["val2","val3"])");
    37 }
    38 
    39 // Test different ways settings can be merged, and verify results. This test can
    

    ryanofsky commented at 7:19 pm on May 28, 2019:

    re: #15934 (review)

    Thanks! Added test.

  25. jamesob approved
  26. jamesob commented at 7:00 pm on May 28, 2019: member

    Tested ACK https://github.com/bitcoin/bitcoin/pull/15934/commits/1d543adae593bdbfd954e80ed61ac907db0c5a7b

    Generated and reviewed the test output locally. Mucked around with various argument formulations using the following config file:

    0dbcache=100
    1[main]
    2dbcache=200
    3[test]
    4dbcache=300
    

    and commandline invocations e.g.

    0./src/bitcoind -conf=$(pwd)/test.conf -dbcache=1000 -dbcache=500 | grep Using
    

    to verify dbcache being set as expected.


    This is a well-written change that cleans up a lot of gnarly, duplicated settings munging. It explicitly outlines surprising corner cases in existing behavior (with docs too), and makes reasoning about settings merge order easier. This change also introduces substantial test coverage to settings management (util::Settings).

    After this is merged, adding a read-write settings file (whether it’s JSON or something else) will be much easier.

  27. ryanofsky force-pushed on May 29, 2019
  28. ryanofsky renamed this:
    Dedup settings merge code
    Separate settings merging from parsing
    on May 29, 2019
  29. ryanofsky commented at 5:41 pm on May 29, 2019: member
    Updated 1d543adae593bdbfd954e80ed61ac907db0c5a7b -> 2dfeff1e6841b77e4e689d54c08557b938407ba1 (pr/mergeset.6 -> pr/mergeset.7), compare) with suggested changes. Rebased 2dfeff1e6841b77e4e689d54c08557b938407ba1 -> 955c782eb776669576a798122da6557fcca8ee39 (pr/mergeset.7 -> pr/mergeset.8) to share common code with #15988.
  30. jamesob commented at 6:25 pm on May 29, 2019: member
    re-tACK https://github.com/bitcoin/bitcoin/pull/15934/commits/955c782eb776669576a798122da6557fcca8ee39 based on the interdiff and running an abbreviated version of the testing above.
  31. DrahtBot added the label Needs rebase on Jun 27, 2019
  32. ryanofsky force-pushed on Jun 27, 2019
  33. DrahtBot removed the label Needs rebase on Jun 27, 2019
  34. ryanofsky commented at 8:49 pm on June 27, 2019: member
    Rebased 955c782eb776669576a798122da6557fcca8ee39 -> 14a6dfcb929d2313577788765f7dc47dd98afbe9 (pr/mergeset.8 -> pr/mergeset.9) due to conflict with #16278
  35. jamesob commented at 9:29 pm on June 27, 2019: member
  36. DrahtBot added the label Needs rebase on Jun 28, 2019
  37. ryanofsky force-pushed on Jun 28, 2019
  38. DrahtBot removed the label Needs rebase on Jun 28, 2019
  39. ryanofsky commented at 4:34 pm on June 28, 2019: member
    Rebased 14a6dfcb929d2313577788765f7dc47dd98afbe9 -> d074e431b76f156e94eb7ed2601d5af4fb65c6cb (pr/mergeset.9 -> pr/mergeset.10) due to conflict with #16300
  40. jamesob commented at 5:34 pm on June 28, 2019: member
    reACK d074e431b76f156e94eb7ed2601d5af4fb65c6cb based on an empty interdiff.
  41. MarcoFalke removed the label Build system on Jun 28, 2019
  42. MarcoFalke removed the label Tests on Jun 28, 2019
  43. MarcoFalke added the label Refactoring on Jun 28, 2019
  44. hebasto commented at 10:28 am on July 31, 2019: member
    Concept ACK.
  45. DrahtBot added the label Needs rebase on Aug 2, 2019
  46. ryanofsky force-pushed on Aug 3, 2019
  47. ryanofsky force-pushed on Aug 3, 2019
  48. DrahtBot removed the label Needs rebase on Aug 3, 2019
  49. DrahtBot added the label Needs rebase on Aug 22, 2019
  50. ryanofsky force-pushed on Aug 22, 2019
  51. DrahtBot removed the label Needs rebase on Aug 22, 2019
  52. DrahtBot added the label Needs rebase on Sep 10, 2019
  53. ryanofsky force-pushed on Sep 10, 2019
  54. DrahtBot removed the label Needs rebase on Sep 10, 2019
  55. ryanofsky renamed this:
    Separate settings merging from parsing
    Merge settings one place instead of five places
    on Oct 14, 2019
  56. DrahtBot added the label Needs rebase on Oct 16, 2019
  57. ryanofsky force-pushed on Oct 17, 2019
  58. DrahtBot removed the label Needs rebase on Oct 17, 2019
  59. in src/util/settings.h:16 in a058191db3 outdated
    11+
    12+class UniValue;
    13+
    14+namespace util {
    15+
    16+//! Settings value type (string/integer/boolean/null variant).
    


    ariard commented at 3:36 am on October 17, 2019:
    Also key-value or array as requirements ? I suppose any other variant type should be easy to use with basic types..

    ryanofsky commented at 11:25 am on October 17, 2019:
    These definitely aren’t not requirements of the type right now. There is no syntax for these things. Maybe this would be useful in the future for something like per-wallet settings (different preferences, RPC authentication setting), and I guess this is a little easier now, but otherwise the PR is neutral on this.

    ryanofsky commented at 6:52 pm on October 22, 2019:

    I suppose any other variant type should be easy to use with basic types..

    I think boost::variant would be an inferior choice, but just as an experiment I tried dropping it in here with some helper methods to make it work without changes to other code:

     0struct SettingsValue : public boost::variant<std::nullptr_t, bool, int64_t, std::string>
     1{
     2    using variant::variant;
     3    template<typename T> const T* ptr() const { return boost::get<T>(this); }
     4    bool isNull() const { return ptr<std::nullptr_t>(); }
     5    bool isBool() const { return ptr<bool>(); }
     6    bool isNum() const { return ptr<int64_t>(); }
     7    bool isFalse() const { return ptr<bool>() && !*ptr<bool>(); }
     8    bool isTrue() const { return ptr<bool>() && *ptr<bool>(); }
     9    const std::string& get_str() const { return *ptr<std::string>(); }
    10    const bool& get_bool() const { return *ptr<bool>(); }
    11    const int64_t& get_int64() const { return *ptr<int64_t>(); }
    12};
    

    This was sufficient for everything except settings_test.cpp (which uses serialization), so it confirms SettingsValue only requires storing ints, bools, strings and nulls, and the choice of type isn’t very important.


    jnewbery commented at 8:04 pm on October 25, 2019:
    This is really useful, and could IMO be included as a comment to document what interface is needed if we did want to substitute another SettingsValue class.

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    This is really useful, and could IMO be included as a comment to document what interface is needed if we did want to substitute another SettingsValue class.

    Good suggestion to document assumptions about the type. Instead of pasting the whole block of code I just listed the required methods and added a github link, but I can add the example code inline if preferred.


    jnewbery commented at 9:14 pm on October 28, 2019:

    I just listed the required methods and added a github link, but I can add the example code inline if preferred.

    Looks great to me as it is. Thanks!

  60. in src/util/settings.h:34 in a058191db3 outdated
    29+};
    30+
    31+//! Get settings value from combined sources: forced settings, command line
    32+//! arguments and the read-only config file.
    33+//!
    34+//! @param ignore_default_section_config - ignore values set in the top-level
    


    ariard commented at 3:42 am on October 17, 2019:
    what’s top-level section of the config file ? The non-network one?

    ryanofsky commented at 11:29 am on October 17, 2019:

    what’s top-level section of the config file ? The non-network one?

    Yes, literally the top of the config file before a [section] delimiter. I don’t really like the term non-network settings because the section can contain network related settings that will be used on mainnet.

  61. in src/util/settings.h:46 in a058191db3 outdated
    37+//!                         for GetChainName
    38+SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, bool get_chain_name);
    39+
    40+//! Get combined setting value similar to GetSetting(), except if setting was
    41+//! specified multiple times, return a list of all the values specified.
    42+std::vector<SettingsValue> GetListSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config);
    


    ariard commented at 3:45 am on October 17, 2019:
    nit: GetSettingAllOccurence?, given name was expecting to query multiple settings at same time

    ryanofsky commented at 11:23 am on October 17, 2019:

    nit: GetSettingAllOccurence?, given name was expecting to query multiple settings at same time

    Sure, will rename.


    ryanofsky commented at 9:00 am on October 23, 2019:

    re: #15934 (review)

    nit: GetSettingAllOccurence?, given name was expecting to query multiple settings at same time

    Sure, will rename.

    Forgot to do this last time. I think I’ll use GetSetting for single valued settings and GetSettingsList for many valued settings. I’d like to keep using the word “list” here to be consistent with #16545 which also uses it with TYPE_STRING_LIST as one of the settings types.

  62. in src/util/settings.h:44 in a058191db3 outdated
    39+
    40+//! Get combined setting value similar to GetSetting(), except if setting was
    41+//! specified multiple times, return a list of all the values specified.
    42+std::vector<SettingsValue> GetListSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config);
    43+
    44+//! Return true if has ignored config value in the default section.
    


    ariard commented at 3:50 am on October 17, 2019:
    nit: find “value is not set” better than “has ignored config value”

    ryanofsky commented at 11:31 am on October 17, 2019:

    nit: find “value is not set” better than “has ignored config value”

    I’m not sure “value is not set” is better. A value is set by the user, it’s just not applied due to the current network. Will expand command though.

  63. in src/util/settings.cpp:27 in a058191db3 outdated
    22+
    23+template <typename Fn>
    24+static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn)
    25+{
    26+    if (auto* value = FindKey(settings.forced_settings, name)) {
    27+        fn(Source(SettingsSpan(value)).SetForced());
    


    ariard commented at 4:08 am on October 17, 2019:
    nit: any reason why you don’t dereference compare to others?

    ryanofsky commented at 11:27 am on October 17, 2019:

    nit: any reason why you don’t dereference compare to others?

    All three settings sources (forced_settings, command_line_options, ro_config) are different types, so there’s not much reason to expect SettingsSpan construction to be the same for all of them:

    https://github.com/bitcoin/bitcoin/blob/61e034f1f6b3beeeed51ef8aaf7762f9244a7c8f/src/util/settings.h#L25-L29

    But I think the only reason for passing a pointer was to save a line of code and not add a fourth SettingsSpan constructor. I think I will go back and add this, though, since I think it’s generally better to pass by reference not pointer when the argument can’t be null.

  64. ariard commented at 4:56 am on October 17, 2019: member

    Just a few nits on the new code to clarify some points.

    Next step is to take every weird compatibility spotted and check if it still works the same on both master nodes and patchset nodes. And look in depth to existing code if there is no uncovered one.

    Maybe they should be documented in one place, like at the beginning of ArgsManagerHelper (or src/doc/bitcoin-conf.md ?) with general rules of precedence/syntax and all the exceptions.

    Have been through the #15935 debate, IMO I really like ability to document my config values to remember how to fine-tune them at later time. Not sure if the distinction between config file = sysadmin, dynamic settings = GUI users will hold, e.g I would like to call pruneblockchain and get my config updated for next time I reboot the system not having my disks blow up. Beyond that, don’t have hard opinion on the matter.

  65. ryanofsky commented at 12:07 pm on October 17, 2019: member

    Next step is to take every weird compatibility spotted and check if it still works the same on both master nodes and patchset nodes. And look in depth to existing code if there is no uncovered one.

    I’m not sure you’re suggesting. Maybe you could go into more detail. I added an extensive unit test in #15869 specifically to support this PR and check all these corner cases and make sure there is no loss of compatibility. I would like to get rid of the confusing behaviors by turning ambiguous configurations into errors, and I implemented flags to start to do this in #16545.

    Maybe they should be documented in one place, like at the beginning of ArgsManagerHelper (or src/doc/bitcoin-conf.md ?) with general rules of precedence/syntax and all the exceptions.

    I want to make ambiguous configurations into errors, and fix cases where settings are parsed in nonsensical ways. I tried to write clear code comments, and am happy to improve them, but I don’t want to make nonsensical behaviors permanent or document them for end users.

    Have been through the #15935 debate, IMO I really like ability to document my config values to remember how to fine-tune them at later time. Not sure if the distinction between config file = sysadmin, dynamic settings = GUI users will hold, e.g I would like to call pruneblockchain and get my config updated for next time I reboot the system not having my disks blow up. Beyond that, don’t have hard opinion on the matter.

    Thanks, and it’d be helpful if you described your use-case with as many specifics as possible to #15935. But there is definitely nothing in this PR that dictates use of any storage format or commenting style, so I think that discussion is not on topic here.

  66. ryanofsky force-pushed on Oct 18, 2019
  67. ryanofsky commented at 4:02 pm on October 18, 2019: member
    Updated 61e034f1f6b3beeeed51ef8aaf7762f9244a7c8f -> 40593ecc0bc48b22054da73eb4bdf1b3aeaedd4f (pr/mergeset.15 -> pr/mergeset.16, compare) just updating some comments discussed above and passing by reference instead of pointer to a span constructor
  68. in src/util/system.cpp:464 in 7514eba3bf outdated
    357@@ -451,69 +358,42 @@ unsigned int ArgsManager::FlagsOfKnownArg(const std::string& key) const
    358 
    359 std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
    360 {
    361-    std::vector<std::string> result = {};
    362-    if (IsArgNegated(strArg)) return result; // special case
    


    ariard commented at 0:49 am on October 23, 2019:
    Just to be sure the IsArgNegated is preserved here in GetListSetting via source.span.negated() > 0 ?

    ryanofsky commented at 9:00 am on October 23, 2019:

    re: #15934 (review)

    Just to be sure the IsArgNegated is preserved here in GetListSetting via source.span.negated() > 0 ?

    It’s handled implicitly by for (const auto& value : source.span) in GetListSetting, because the SettingsSpan iterator skips negated values in its begin() method.

    The source.span.negated() > 0 check you’re referring is used after that to set the result_complete variable, which is used to apply settings precedence command_line_options > rw_settings > ro_config, and make sure for example that a negation on the command line will clear out any lower priority settings in ro_config and rw_settings. I’ll add a comment to say this explicitly on the result_complete line.

  69. in src/util/system.cpp:338 in 7514eba3bf outdated
    323             }
    324+            // Weird behavior preserved for backwards compatibility: command
    325+            // line options with section prefixes are allowed but ignored. It
    326+            // would be better if these options triggered the Invalid parameter
    327+            // error below.
    328+            if (section.empty()) {
    


    ariard commented at 0:53 am on October 23, 2019:
    Hmmm I can’t get from where this behavior is extracted ?

    ryanofsky commented at 9:00 am on October 23, 2019:

    re: #15934 (review)

    Hmmm I can’t get from where this behavior is extracted ?

    This behavior makes no sense and was never intended, so it’s buried away in current code. It happens because of the following code in ArgsManager::ParseParameters:

    https://github.com/bitcoin/bitcoin/blob/9dd6bbba613d7462afdb6276c4002bc183478528/src/util/system.cpp#L411-L420

    A section section.name command line setting here will fail to trigger the “Invalid parameter” error because FlagsOfKnownArg strips out the section. prefix and finds that name is a known setting. But InterpretOption fails to strip out the section. prefix and stores the setting in the m_override_args map under section.name instead of name, where it is ignored and never has any effect.

    The new code improves this by documenting the behavior and making it easy to change in the future. It also avoids bugs like this by splitting the section.name string exactly once in InterpretOption instead of many different places and times.

  70. ariard approved
  71. ariard commented at 0:58 am on October 23, 2019: member
    Code review and tested ACK 40593ec
  72. ryanofsky commented at 9:48 am on October 23, 2019: member
    Thanks for the review!
  73. in src/util/system.cpp:377 in 7514eba3bf outdated
    368-        ArgsManagerHelper::AddArgs(result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg));
    369+    bool ignore_default_section_config = !ArgsManagerHelper::UseDefaultSection(*this, strArg);
    370+    std::vector<std::string> result;
    371+    for (const util::SettingsValue& value :
    372+        util::GetListSetting(m_settings, m_network, SettingName(strArg), ignore_default_section_config)) {
    373+        result.push_back(value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str());
    


    fjahr commented at 3:17 pm on October 23, 2019:
    nit: I find this a bit hard to read, would rather add a few more lines than have a nested ternary statement

    fjahr commented at 4:39 pm on October 23, 2019:
    On second thought: since these nested ternary one-liners are repeated multiple times, I would suggest a helper method that converts value to whatever gets pushed into result
  74. in src/util/system.cpp:395 in 7514eba3bf outdated
    411-    if (IsArgNegated(strArg)) return "0";
    412-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    413-    if (found_res.first) return found_res.second;
    414-    return strDefault;
    415+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    416+    return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str();
    


    fjahr commented at 3:19 pm on October 23, 2019:
    nit: would not use nested ternary for readability
  75. in src/util/system.cpp:401 in 7514eba3bf outdated
    421-    if (IsArgNegated(strArg)) return 0;
    422-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    423-    if (found_res.first) return atoi64(found_res.second);
    424-    return nDefault;
    425+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    426+    return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : atoi64(value.get_str());
    


    fjahr commented at 3:19 pm on October 23, 2019:
    nit: would not use nested ternary for readability
  76. in src/util/system.cpp:407 in 7514eba3bf outdated
    431-    if (IsArgNegated(strArg)) return false;
    432-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    433-    if (found_res.first) return InterpretBool(found_res.second);
    434-    return fDefault;
    435+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    436+    return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str());
    


    fjahr commented at 3:19 pm on October 23, 2019:
    nit: would not use nested ternary for readability
  77. ryanofsky force-pushed on Oct 23, 2019
  78. fjahr approved
  79. fjahr commented at 4:44 pm on October 23, 2019: member

    tested ACK 40593ecc0bc48b22054da73eb4bdf1b3aeaedd4f

    Reviewed code, ran tests, did some manual testing, cherry-picked the regression test to verify results are the same on current master.

    Thank you for this nice cleanup, I really like the regression test. Just had some nit about the readability with the ternary statements in system.cpp.

    Edit: I also found the changes in system.cpp hard to review in one. Maybe they could have been split up into different commits.

  80. jonatack commented at 5:02 pm on October 23, 2019: member
    Tested ACK 40593ecc0bc48b22054da73eb4bdf1b3aeaedd4f. Will review and test the latest version at 8ca6338e994000c82c813721c85ebafd61250641 tomorrow.
  81. in src/test/util_tests.cpp:167 in 8ca6338e99 outdated
    162+
    163     TestArgsManager() { m_network_only_args.clear(); }
    164     std::map<std::string, std::vector<std::string> >& GetOverrideArgs() { return m_override_args; }
    165     std::map<std::string, std::vector<std::string> >& GetConfigArgs() { return m_config_args; }
    166+    bool ParseParameters(int argc, const char* const argv[], std::string& error) {
    167+        bool result = ArgsManager::ParseParameters(argc, argv, error);
    


    Talkless commented at 5:26 pm on October 23, 2019:
    const bool would help here, it actually too a wile to see that it is not changed throughout this function.
  82. in src/test/util_tests.cpp:204 in 8ca6338e99 outdated
    200+    void UpdateSettings()
    201+    {
    202+        LOCK(cs_args);
    203+        for (const auto* args : {&m_override_args, &m_config_args}) {
    204+            for (const auto& arg : *args) {
    205+                std::string name = arg.first[0] == '-' ? arg.first.substr(1) : arg.first;
    


    Talkless commented at 5:28 pm on October 23, 2019:
    could be const std::string
  83. in src/util/settings.cpp:19 in 8ca6338e99 outdated
    14+    bool forced = false;
    15+    bool config_file = false;
    16+    bool config_file_top_level = false;
    17+
    18+    Source(SettingsSpan span) : span(span) {}
    19+    Source& SetForced() { forced = true; return *this; }
    


    Talkless commented at 5:30 pm on October 23, 2019:
    noexcept?
  84. in src/util/settings.cpp:20 in 8ca6338e99 outdated
    15+    bool config_file = false;
    16+    bool config_file_top_level = false;
    17+
    18+    Source(SettingsSpan span) : span(span) {}
    19+    Source& SetForced() { forced = true; return *this; }
    20+    Source& SetConfigFile(bool top_level) { config_file = true; config_file_top_level = top_level; return *this; }
    


    Talkless commented at 5:30 pm on October 23, 2019:
    could be noexcept
  85. ryanofsky commented at 5:32 pm on October 23, 2019: member

    Updated 40593ecc0bc48b22054da73eb4bdf1b3aeaedd4f -> 8ca6338e994000c82c813721c85ebafd61250641 (pr/mergeset.16 -> pr/mergeset.17, compare) with minor cleanups

    Sorry for the churn. Only changes were the comments and cleanups I promised Antoine this morning

  86. in src/util/settings.cpp:39 in 8ca6338e99 outdated
    34+            if (auto* values = FindKey(*map, name)) {
    35+                fn(Source(SettingsSpan(*values)).SetConfigFile(/* top_level= */ false));
    36+            }
    37+        }
    38+    }
    39+    SettingsSpan span;
    


    Talkless commented at 5:35 pm on October 23, 2019:
    Consider marking as const and applying ES.28: Use lambdas for complex initialization, especially of const variables. That re-assignment span = SettingsSpan(*values); is rather “hidden”.

    ryanofsky commented at 6:31 pm on October 23, 2019:

    re: #15934 (review)

    Consider marking as const and applying ES.28: Use lambdas for complex initialization, especially of const variables. That re-assignment span = SettingsSpan(*values); is rather “hidden”.

    It can definitely be confusing when a non-const variable has a long lifetime and its value changes over a complicated function. But the span variable here is constructed, assigned, used, and destroyed all in about 5 lines of code, so using an initialization lambda would seem make overall flow more complicated instead of less.

  87. in src/util/settings.cpp:87 in 8ca6338e99 outdated
    78+
    79+        // Skip negated command line settings.
    80+        if (skip_negated_command_line && source.span.last_negated()) return;
    81+
    82+        // Stick with highest priority value, keeping result if already set.
    83+        if (!result.isNull()) return;
    


    Talkless commented at 5:39 pm on October 23, 2019:
    This looks redundant. result is always null here, I don’t see where it can be changed above. All this could be rewritten as a return <something> instead of changing via lambda capturer reference…

    ryanofsky commented at 5:43 pm on October 23, 2019:

    re: #15934 (review)

    This looks redundant. result is always null here, I don’t see where it can be changed above. All this could be rewritten as a return <something> instead of changing via lambda capturer reference…

    I can maybe change the comment above to clarify, but return here is what keeps lower priority values from overwriting higher priority values. Bear in mind this is a lambda called repeatedly, one time for each setting source.


    Talkless commented at 5:42 am on October 24, 2019:
    Yep, I’ve missed that fact that lambda is called multiple times, thanks for clarification. So this comment is moot.
  88. in src/util/settings.cpp:158 in 8ca6338e99 outdated
    145+    return has_ignored;
    146+}
    147+
    148+SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) : SettingsSpan(vec.data(), vec.size()) {}
    149+const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
    150+const SettingsValue* SettingsSpan::end() const { return data + size; }
    


    Talkless commented at 5:48 pm on October 23, 2019:
    end() could be noexcept, not sure about all these other trivial functions (with regards to isFalse() via negated()).
  89. in src/util/settings.h:58 in 8ca6338e99 outdated
    53+//! ignored unintentionally.
    54+bool HasIgnoredDefaultSectionConfigValue(const Settings& settings, const std::string& section, const std::string& name);
    55+
    56+//! Iterable list of settings that skips negated values.
    57+struct SettingsSpan {
    58+    SettingsSpan(const SettingsValue* data, size_t size) : data(data), size(size) {}
    


    Talkless commented at 5:49 pm on October 23, 2019:
    noexcept
  90. in src/util/settings.h:60 in 8ca6338e99 outdated
    55+
    56+//! Iterable list of settings that skips negated values.
    57+struct SettingsSpan {
    58+    SettingsSpan(const SettingsValue* data, size_t size) : data(data), size(size) {}
    59+    SettingsSpan(const std::vector<SettingsValue>& vec);
    60+    SettingsSpan(const SettingsValue& value) : SettingsSpan(&value, 1) {}
    


    Talkless commented at 5:50 pm on October 23, 2019:
    explcit? noexcept?
  91. in src/util/settings.h:59 in 8ca6338e99 outdated
    54+bool HasIgnoredDefaultSectionConfigValue(const Settings& settings, const std::string& section, const std::string& name);
    55+
    56+//! Iterable list of settings that skips negated values.
    57+struct SettingsSpan {
    58+    SettingsSpan(const SettingsValue* data, size_t size) : data(data), size(size) {}
    59+    SettingsSpan(const std::vector<SettingsValue>& vec);
    


    Talkless commented at 5:50 pm on October 23, 2019:
    Missing exclicit? Do we really need to support SettigsSpan s = std::vec... ?

    ryanofsky commented at 6:45 pm on October 23, 2019:

    Do we really need to support SettigsSpan s = std::vec... ?

    We don’t really need it because the data / size constructor could be called instead but having this makes code in system.cpp less verbose and I’m not sure what the preferred alternative would be: Calling .data() and .size() manually? Helper function to call .data() and .size()? Template constructor that calls .data() and .size() without referencing std::vector?


    Talkless commented at 5:40 am on October 24, 2019:

    Oh no, I do not suggest to remove this helper constructor, maybe just adding explicit for that constructor. Currently it implicilty-converts from std::vecotor:

    0//...
    1DoSomething(const SettingsSpan& span);
    2//...
    3std::vector<SettingsValue>& vec;
    4DoSomething(vec); // implicilty conerts to SettingsSpan, kinda "sneaky".
    

    With explicit constructor:

    0DoSomething(SettingsValue{vec}); // we see that we pass SettngsValue, not "just" a vector
    

    Although looking at std::span implementations, these constructors are also implicit there, so I guess you can ignore this comment.


    ryanofsky commented at 9:02 am on October 24, 2019:

    re: #15934 (review)

    Although looking at std::span implementations, these constructors are also implicit there, so I guess you can ignore this comment.

    Yes, it’s a perfectly safe conversion that doesn’t create ownership or performance issues, and in case of std::span reduces noise and boilerplate in code. But in any case I updated this PR to use explicit here the other day based on your feedback (https://github.com/ryanofsky/bitcoin/compare/pr/mergeset.17..pr/mergeset.18) and I think it’s an improvement.

  92. in src/util/settings.h:61 in 8ca6338e99 outdated
    56+//! Iterable list of settings that skips negated values.
    57+struct SettingsSpan {
    58+    SettingsSpan(const SettingsValue* data, size_t size) : data(data), size(size) {}
    59+    SettingsSpan(const std::vector<SettingsValue>& vec);
    60+    SettingsSpan(const SettingsValue& value) : SettingsSpan(&value, 1) {}
    61+    SettingsSpan() {}
    


    Talkless commented at 5:51 pm on October 23, 2019:
    maybe just = default ?
  93. in src/util/settings.h:81 in 8ca6338e99 outdated
    71+
    72+//! Map lookup helper.
    73+template <typename Map, typename Key>
    74+auto FindKey(Map&& map, Key&& key) -> decltype(&map.at(key))
    75+{
    76+    auto it = map.find(key);
    


    Talkless commented at 5:57 pm on October 23, 2019:

    const auto it as it is not changed later.

    P.S. really sorry for not using “Review Changes” feature. I’ve been informed that these “add single comment” spams the notification channel… Will do better next time.

  94. ryanofsky force-pushed on Oct 23, 2019
  95. ryanofsky commented at 8:12 pm on October 23, 2019: member

    Thanks for the reviews!

    Updated 8ca6338e994000c82c813721c85ebafd61250641 -> 86aff9bda31c9d60cc477700da4600ad5930bcda (pr/mergeset.17 -> pr/mergeset.18, compare) just adding suggested const/noexcept/explicits.

    re: #15934#pullrequestreview-305976230

    Just had some nit about the readability with the ternary statements in system.cpp.

    I’d be fine with changing these statements, but this is just a convention of abbreviating:

    0if (condition1)
    1   value = result1;
    2else if (condition2)
    3   value = result2;
    4else
    5   value = fallback;
    

    to:

    0value = condition1 ? result1 : condition2 ? result2 : fallback;
    

    which I think is common in many languages (though famously broken in PHP).

    I also found the changes in system.cpp hard to review in one. Maybe they could have been split up into different commits.

    I’m not sure if there’s something that can be done about this. It’s the switch from old settings storage to new storage that requires all the changes:

    0-    std::map<std::string, std::vector<std::string>> m_override_args GUARDED_BY(cs_args);
    1-    std::map<std::string, std::vector<std::string>> m_config_args GUARDED_BY(cs_args);
    2+    util::Settings m_settings GUARDED_BY(cs_args);
    
  96. fjahr commented at 11:17 pm on October 23, 2019: member

    ACK 86aff9bda31c9d60cc477700da4600ad5930bcda

    Confirmed that diff is only fixing nits as discussed in reviews. Re-ran tests. Some manual testing.

  97. in src/util/settings.cpp:18 in 86aff9bda3 outdated
    13+    SettingsSpan span;
    14+    bool forced = false;
    15+    bool config_file = false;
    16+    bool config_file_top_level = false;
    17+
    18+    explicit Source(SettingsSpan span) : span(span) {}
    


    Talkless commented at 5:52 pm on October 24, 2019:

    Since SettingsSpan copy constructor is noexcept, this could be too. Though if it would change any time in the future, using noexcept() operator could make it conditionally noexcept, by checking if copy constructor is actually noexcept (if author cares):

    0explicit Source(SettingsSpan span) noexcept(noexcept(SettingsSpan{span}) : span(span) {}
    

    ryanofsky commented at 7:44 pm on October 24, 2019:

    re: #15934 (review)

    Since SettingsSpan copy constructor is noexcept, this could be too. Though if it would change any time in the future, using noexcept() operator could make it conditionally noexcept, by checking if copy constructor is actually noexcept (if author cares):

    0explicit Source(SettingsSpan span) noexcept(noexcept(SettingsSpan{span}) : span(span) {}
    

    Hmm, I’m not an expert on noexcept, and I have no problem adding it on functions that obviously will not throw, or adding it in places where it can really improve performance. But it seems dangerous and not useful to add it whereever it can be added just because some code doesn’t currently happen to throw right now. noexcept is not like const where you get a nice compile time error when a function that shouldn’t throw does. It’s a fatal runtime error, so there are safety considerations with using it, and to me the best approach would seem to be not applying it unless it’s very obviously correct or unless there’s a specific optimization desired.

  98. in src/util/settings.cpp:161 in 86aff9bda3 outdated
    148+SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
    149+const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
    150+const SettingsValue* SettingsSpan::end() const { return data + size; }
    151+bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
    152+bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
    153+size_t SettingsSpan::negated() const
    


    Talkless commented at 5:57 pm on October 24, 2019:

    This function has some rather unusual looping, so if you care about prioritizing usage of standard algorithms, it could be implemented in something like this:

    0        const std::reverse_iterator<const SettingsValue*> rbegin(end());
    1        const std::reverse_iterator<const SettingsValue*> rend(begin());
    2        const auto it = std::find_if(rbegin, rend, [](const SettingsValue &i) { return i.isFalse(); });
    3        return static_cast<size_t>(std::distance(it, rend));
    

    ryanofsky commented at 7:44 pm on October 24, 2019:

    re: #15934 (review)

    This function has some rather unusual looping, so if you care about prioritizing usage of standard algorithms, it could be implemented in something like this:

    0        const std::reverse_iterator<const SettingsValue*> rbegin(end());
    1        const std::reverse_iterator<const SettingsValue*> rend(begin());
    2        const auto it = std::find_if(rbegin, rend, [](const SettingsValue &i) { return i.isFalse(); });
    3        return static_cast<size_t>(std::distance(it, rend));
    

    I like standard algorithms, but wouldn’t prioritize using them unless they made code simpler, and I don’t think they help in that respect here.


    Talkless commented at 10:30 am on October 25, 2019:
    std::count_if would be simplest, but not sure if semantics will be the same, i.e. will there acutally be N sequental .isFalse()‘es.

    ryanofsky commented at 1:29 pm on October 25, 2019:

    re: #15934 (review)

    std::count_if would be simplest, but not sure if semantics will be the same, i.e. will there acutally be N sequental .isFalse()‘es.

    std::count_if would not be right here, but your initial std::distance suggestion would work. The behavior of negation is a little subtle, which I why I thought the code would be easier to understand as a 2 line for-loop, instead of something that might send someone looking up corner cases in std algorithm man pages.

    span.negated() is mostly just called as a boolean check to see if were are any negated values in the span and return as quickly as possible. It returns a size_t instead of a bool because in some cases, the number of negated values is important. A false bool value is negated but also negates (skips, clears) any values that came before it, so they won’t be included in span iteration, or returned by GetArgs or GetSettingsList.

  99. in src/util/settings.h:66 in 86aff9bda3 outdated
    56+//! Iterable list of settings that skips negated values.
    57+struct SettingsSpan {
    58+    explicit SettingsSpan() = default;
    59+    explicit SettingsSpan(const SettingsValue& value) noexcept : SettingsSpan(&value, 1) {}
    60+    explicit SettingsSpan(const SettingsValue* data, size_t size) noexcept : data(data), size(size) {}
    61+    explicit SettingsSpan(const std::vector<SettingsValue>& vec) noexcept;
    


    Talkless commented at 6:00 pm on October 24, 2019:
    Now there are some redundant explicit’s. There’s no need to mark SettingsSpan() = default; or more-than-one-argument-taking SettingsSpan(const SettingsValue* data, size_t size) as explicit. It’s only for the cases when constructor can initialize with single argument.

    ryanofsky commented at 7:45 pm on October 24, 2019:

    re: #15934 (review)

    Now there are some redundant explicit’s. There’s no need to mark SettingsSpan() = default; or more-than-one-argument-taking SettingsSpan(const SettingsValue* data, size_t size) as explicit. It’s only for the cases when constructor can initialize with single argument.

    I think this isn’t strictly true. Explicit keyword also affects whether you can implicitly use a constructor from a return statement, I believe. But actually the reason for adding explicit here is just aesthetics (I like the constructors to line up, and explicit as a signpost for “there be constructors here”).

  100. in src/util/settings.cpp:44 in 86aff9bda3 outdated
    35+                fn(Source(SettingsSpan(*values)).SetConfigFile(/* top_level= */ false));
    36+            }
    37+        }
    38+    }
    39+    SettingsSpan span;
    40+    if (auto* map = FindKey(settings.ro_config, "")) {
    


    Talkless commented at 6:06 pm on October 24, 2019:
    I believe "" are rather overused on codebases. This invokes std::string’s copy constructor taking const char* that will try to strlen(), etc. Creating default-constructed std::string{}/ std::string()/{} (in right context) would feel more “pedantically correct” :) .

    ryanofsky commented at 7:44 pm on October 24, 2019:

    re: #15934 (review)

    I believe "" are rather overused on codebases. This invokes std::string’s copy constructor taking const char* that will try to strlen(), etc. Creating default-constructed std::string{}/ std::string()/{} (in right context) would feel more “pedantically correct” :) .

    Will keep because constructing strings from "" is ubiquitious in our codebase, not where I would look to improve performance, and I’d prefer it to be clear this is a string lookup.


    Talkless commented at 10:26 am on October 25, 2019:
    Right, I’m used to QString too much. std::string optimizes well ("" same as {}), it’s QString where it does add cost (extra function call). Sorry for bothering.
  101. in src/util/system.cpp:351 in 86aff9bda3 outdated
    344-            }
    345-            return false;
    346+    bool success = true;
    347+    if (auto* includes = util::FindKey(m_settings.command_line_options, "includeconf")) {
    348+        for (const auto& include : util::SettingsSpan(*includes)) {
    349+            error += "-includeconf cannot be used from commandline; -includeconf=" + include.get_str() + "\n";
    


    Talkless commented at 6:13 pm on October 24, 2019:
    "\n" -> '\n', no need for const char* / std::string for single character.

    ryanofsky commented at 7:45 pm on October 24, 2019:

    re: #15934 (review)

    "\n" -> '\n', no need for const char* / std::string for single character.

    This is moved code, so I would prefer to not change it and create a spurious difference.

  102. in src/util/system.cpp:351 in 86aff9bda3 outdated
    371-    const std::string base_arg_name = '-' + key.substr(option_index);
    372-
    373     LOCK(cs_args);
    374     for (const auto& arg_map : m_available_args) {
    375-        const auto search = arg_map.second.find(base_arg_name);
    376+        const auto search = arg_map.second.find("-" + key);
    


    Talkless commented at 6:14 pm on October 24, 2019:
    "-" -> '-'

    ryanofsky commented at 7:45 pm on October 24, 2019:

    re: #15934 (review)

    "-" -> '-'

    Changed

  103. in src/util/system.cpp:373 in 86aff9bda3 outdated
    364     LOCK(cs_args);
    365-
    366-    ArgsManagerHelper::AddArgs(result, m_override_args, strArg);
    367-    if (!m_network.empty()) {
    368-        ArgsManagerHelper::AddArgs(result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg));
    369+    bool ignore_default_section_config = !ArgsManagerHelper::UseDefaultSection(*this, strArg);
    


    Talkless commented at 6:15 pm on October 24, 2019:
    const bool?

    ryanofsky commented at 7:45 pm on October 24, 2019:

    re: #15934 (review)

    const bool?

    Changed

  104. in src/util/system.cpp:383 in 86aff9bda3 outdated
    410 {
    411-    if (IsArgNegated(strArg)) return "0";
    412-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    413-    if (found_res.first) return found_res.second;
    414-    return strDefault;
    415+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    


    Talkless commented at 6:16 pm on October 24, 2019:
    const util::SettingsValue value

    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    const util::SettingsValue value

    Changed

  105. in src/util/system.cpp:389 in 86aff9bda3 outdated
    420 {
    421-    if (IsArgNegated(strArg)) return 0;
    422-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    423-    if (found_res.first) return atoi64(found_res.second);
    424-    return nDefault;
    425+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    


    Talkless commented at 6:16 pm on October 24, 2019:
    const util::SettingsValue value

    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    const util::SettingsValue value

    Changed

  106. in src/util/system.cpp:395 in 86aff9bda3 outdated
    430 {
    431-    if (IsArgNegated(strArg)) return false;
    432-    std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg);
    433-    if (found_res.first) return InterpretBool(found_res.second);
    434-    return fDefault;
    435+    util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg);
    


    Talkless commented at 6:17 pm on October 24, 2019:
    const util::SettingsValue value

    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    const util::SettingsValue value

    Changed

  107. in src/util/system.cpp:776 in 86aff9bda3 outdated
    772@@ -890,25 +773,31 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
    773         bool emptyIncludeConf;
    774         {
    775             LOCK(cs_args);
    776-            emptyIncludeConf = m_override_args.count("-includeconf") == 0;
    777+            std::vector<util::SettingsValue>* includes = util::FindKey(m_settings.command_line_options, "includeconf");
    


    Talkless commented at 6:18 pm on October 24, 2019:
    I believe it could be const std::vectorutil::SettingsValue* const includes. Neither value (vector) nor pointer is ever changed (right?).

    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    I believe it could be const std::vectorutil::SettingsValue* const includes. Neither value (vector) nor pointer is ever changed (right?).

    Changed

  108. in src/util/system.cpp:800 in 86aff9bda3 outdated
    809+            };
    810+
    811+            // We haven't set m_network yet (that happens in SelectParams()), so manually check
    812+            // for network.includeconf args.
    813+            size_t chain_includes = add_includes(chain_id);
    814+            size_t default_includes = add_includes("");
    


    Talkless commented at 6:20 pm on October 24, 2019:

    could be default-constucted instread of copy-constructed std::string: add_includes({});

    Also, both size_t’s could be const.


    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    could be default-constucted instread of copy-constructed std::string: add_includes({});

    Also, both size_t’s could be const.

    Changed

  109. in src/util/system.cpp:818 in 86aff9bda3 outdated
    812@@ -924,15 +813,14 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
    813             }
    814 
    815             // Warn about recursive -includeconf
    816-            includeconf = GetArgs("-includeconf");
    817+            includeconf.clear();
    818+            add_includes(chain_id, /* skip= */ chain_includes);
    819+            add_includes("", /* skip= */ default_includes);
    


    Talkless commented at 6:21 pm on October 24, 2019:
    "" -> {} too ?

    ryanofsky commented at 7:46 pm on October 24, 2019:

    re: #15934 (review)

    "" -> {} too ?

    Changed

  110. laanwj added this to the "Blockers" column in a project

  111. ryanofsky force-pushed on Oct 24, 2019
  112. ryanofsky commented at 8:49 pm on October 24, 2019: member
    Updated 86aff9bda31c9d60cc477700da4600ad5930bcda -> fdae2210f458d2c4a74fe0c079501bed39ec6404 (pr/mergeset.18 -> pr/mergeset.19, compare) with some suggested minor changes
  113. in src/util/system.cpp:66 in fdae2210f4 outdated
    62@@ -63,6 +63,7 @@
    63 #endif
    64 
    65 #include <thread>
    66+#include <univalue.h>
    


    Talkless commented at 3:31 pm on October 25, 2019:
    Is this the right place to put this include? Shouldn’t it be way more up, together with other bitcoin stuff?

    ryanofsky commented at 4:20 pm on October 25, 2019:

    re: #15934 (review)

    Is this the right place to put this include? Shouldn’t it be way more up, together with other bitcoin stuff?

    I don’t think there is a strict convention, but often I see and write lists of internal includes followed by lists of external includes. Univalue is technically an external include and a library that can be installed separately.


    Talkless commented at 12:55 pm on October 27, 2019:

    Univalue is technically an external include

    Oh, right then.

  114. in src/util/system.cpp:350 in fdae2210f4 outdated
    343-                error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n";
    344-            }
    345-            return false;
    346+    bool success = true;
    347+    if (auto* includes = util::FindKey(m_settings.command_line_options, "includeconf")) {
    348+        for (const auto& include : util::SettingsSpan(*includes)) {
    


    Talkless commented at 3:41 pm on October 25, 2019:

    Is it possible for if to be true, but loop be empty, i.e. not to loop not a single time (empty span), so success = false would be never executed? Or this would be expected?

    If it is impossible that for() never loops (it always loops at least once), then bool success is not needed, return false can be returned after loop body, and return true in the end of the function.


    ryanofsky commented at 4:20 pm on October 25, 2019:

    re: #15934 (review)

    Is it possible for if to be true, but loop be empty, i.e. not to loop not a single time (empty span), so success = false would be never executed? Or this would be expected?

    If it is impossible that for() never loops (it always loops at least once), then bool success is not needed, return false can be returned after loop body, and return true in the end of the function.

    Not impossible, and the assumption won’t be true if -noincludeconf is used. Even if it were true, the suggestion seems like a more fragile way to write the code, and I’m not sure what the perceived benefit would be. I would definitely look askance at code that was returning failure potentially without setting the error string.

  115. in src/util/settings.h:67 in fdae2210f4 outdated
    57+struct SettingsSpan {
    58+    explicit SettingsSpan() = default;
    59+    explicit SettingsSpan(const SettingsValue& value) noexcept : SettingsSpan(&value, 1) {}
    60+    explicit SettingsSpan(const SettingsValue* data, size_t size) noexcept : data(data), size(size) {}
    61+    explicit SettingsSpan(const std::vector<SettingsValue>& vec) noexcept;
    62+    const SettingsValue* begin() const; //<! Pointer to first non-negated value.
    


    Talkless commented at 3:50 pm on October 25, 2019:
    I understand begin(), end() and empty (for std::empty in the future) being lowercase, but shouldn’t it be LastNegated() and Negated() as Bitcoin’s PascalCase style requires?

    ryanofsky commented at 4:15 pm on October 25, 2019:

    re: #15934 (review)

    I understand begin(), end() and empty (for std::empty in the future) being lowercase, but shouldn’t it be LastNegated() and Negated() as Bitcoin’s PascalCase style requires?

    “Requires” is strong. There’s a decent amount of variation here, and I prefer just being consistent inside a targeted and narrowly used helper class.

  116. in src/test/settings_tests.cpp:25 in fdae2210f4 outdated
    20+    util::Settings settings;
    21+    settings.command_line_options["name"].push_back("val1");
    22+    settings.command_line_options["name"].push_back("val2");
    23+    settings.ro_config["section"]["name"].push_back(2);
    24+
    25+    auto check_values = [&](util::Settings s, std::string single_val, std::string list_val) {
    


    jnewbery commented at 5:00 pm on October 25, 2019:
    Is there a reason this needs to be a lambda? It’s not capturing any surrounding variables. Would it be clearer to just define this as a regular function above?

    ryanofsky commented at 11:39 am on October 26, 2019:

    re: #15934 (review)

    Is there a reason this needs to be a lambda? It’s not capturing any surrounding variables. Would it be clearer to just define this as a regular function above?

    Good catch. Made it a reusable function now

  117. ryanofsky commented at 5:01 pm on October 25, 2019: member

    For a guy called @Talkless, you do make a lot of comments… :circus_tent:

    Eyerolling puns aside though, if you are still reviewing, I might encourage taking a broader view and looking for impacts of changes here that may be more concerning than style or performance tweaks.

  118. in src/test/settings_tests.cpp:65 in fdae2210f4 outdated
    57+    //! Enumerate all possible test configurations.
    58+    template <typename Fn>
    59+    void ForEachMergeSetup(Fn&& fn)
    60+    {
    61+        ActionList arg_actions = {};
    62+        ForEachNoDup(arg_actions, SET, NEGATE, [&]{
    


    jnewbery commented at 5:25 pm on October 25, 2019:

    nit: a comment here would be useful:

    0// command_line_options do not have sections. Only iterate over SET and NEGATE
    

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    nit: a comment here would be useful:

    0// command_line_options do not have sections. Only iterate over SET and NEGATE
    

    Added comment

  119. in src/test/settings_tests.cpp:123 in fdae2210f4 outdated
    118+            push_values(conf_action, "c", use_section ? network + "." : "",
    119+                settings.ro_config[use_section ? network : ""][name]);
    120+        }
    121+
    122+        desc += " || ";
    123+        desc += GetSetting(settings, network, name, ignore_default_section_config, /* skip_nonpersistent= */ false).write();
    


    jnewbery commented at 5:27 pm on October 25, 2019:
    skip_nonpersistent inline comment is wrong. That positional argument is get_chain_name.

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    skip_nonpersistent inline comment is wrong. That positional argument is get_chain_name.

    Good catch. This came from messing up a rebase of another PR

  120. in src/test/settings_tests.cpp:129 in fdae2210f4 outdated
    124+        desc += " |";
    125+        for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) {
    126+            desc += " ";
    127+            desc += s.write();
    128+        }
    129+        if (HasIgnoredDefaultSectionConfigValue(settings, network, name)) desc += " | ignored";
    


    jnewbery commented at 5:31 pm on October 25, 2019:
    nit: I’d prefer all delimiters to be printed for each test case, so there’s a constant number of columns.

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    nit: I’d prefer all delimiters to be printed for each test case, so there’s a constant number of columns.

    Added separator unconditionally now

  121. in src/test/settings_tests.cpp:155 in fdae2210f4 outdated
    150+    //
    151+    // And verify diff against previous results to make sure the changes are expected.
    152+    //
    153+    // Results file is formatted like:
    154+    //
    155+    //   <input> || <output>
    


    jnewbery commented at 5:33 pm on October 25, 2019:

    Could you add more details here, like you have in the utils_tests.cpp:

    0    //   <input> || GetSetting() | GetSettingsList() | HasIgnoredDefaultSectionConfigValue()
    

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    Could you add more details here, like you have in the utils_tests.cpp:

    0    //   <input> || GetSetting() | GetSettingsList() | HasIgnoredDefaultSectionConfigValue()
    

    Added comment

  122. in src/test/util_tests.cpp:166 in fdae2210f4 outdated
    161+    std::map<std::string, std::vector<std::string>> m_config_args;
    162+
    163     TestArgsManager() { m_network_only_args.clear(); }
    164     std::map<std::string, std::vector<std::string> >& GetOverrideArgs() { return m_override_args; }
    165     std::map<std::string, std::vector<std::string> >& GetConfigArgs() { return m_config_args; }
    166+    bool ParseParameters(int argc, const char* const argv[], std::string& error) {
    


    jnewbery commented at 6:34 pm on October 25, 2019:

    These functions (TestArgsManager::ParseParameters(), TestArgsManager::ReadConfigString() and TestArgsManager::UpdateSettings()) are pretty confusing. TestArgsManager is essentially storing its own view of the settings, and the tests are testing that view, rather than what ArgsManager itself is storing. UpdateSettings() is even weirder - it’s reaching into ArgsManager.m_settings and changing settings.

    I think it’s ok to leave these in for now and fix up the tests in a future PR (which I’d be happy to review), but could you add some comments explaining what’s going on here?


    ryanofsky commented at 1:25 pm on October 26, 2019:

    re: #15934 (review)

    These functions (TestArgsManager::ParseParameters(), TestArgsManager::ReadConfigString() and TestArgsManager::UpdateSettings()) are pretty confusing.

    It was actually easy to update the tests and drop these so I got rid of them in the pr/mergeset.19 -> pr/mergeset.20 push (compare)

  123. ryanofsky force-pushed on Oct 25, 2019
  124. ryanofsky commented at 7:20 pm on October 25, 2019: member
    Updated fdae2210f458d2c4a74fe0c079501bed39ec6404 -> e18bf8dd30259f5c5ca63fdacadebd8b28acb2cc (pr/mergeset.19 -> pr/mergeset.20, compare) with test simplifications suggested by @jnewbery removing more m_override_args / m_config_args usages
  125. jnewbery commented at 9:45 pm on October 25, 2019: member
    Still chewing my way through this. I have a few small nits so far. I’ll try to finish review early next week.
  126. in src/util/settings.cpp:45 in e18bf8dd30 outdated
    40+    if (auto* map = FindKey(settings.ro_config, "")) {
    41+        if (auto* values = FindKey(*map, name)) {
    42+            span = SettingsSpan(*values);
    43+        }
    44+    }
    45+    fn(Source(span).SetConfigFile(/* top_level= */ true));
    


    jnewbery commented at 10:13 pm on October 25, 2019:
    It wasn’t immediately obvious to me why fn() was being called on span even if hasn’t been set. Moving this line into the if block causes the tests to fail. Can you add a comment to explain why you’re calling this even if no settings were found in the top_level of the config file?

    ryanofsky commented at 11:40 am on October 26, 2019:

    re: #15934 (review)

    It wasn’t immediately obvious to me why fn() was being called on span even if hasn’t been set. Moving this line into the if block causes the tests to fail. Can you add a comment to explain why you’re calling this even if no settings were found in the top_level of the config file?

    Got rid of this behavior now. This was an attempt to simplify HasIgnoredDefaultSectionConfigValue() by offloading complexity to MergeSetting(), but it just makes both functions less clear and was a bad idea.

  127. fanquake deleted a comment on Oct 25, 2019
  128. ryanofsky force-pushed on Oct 26, 2019
  129. ryanofsky force-pushed on Oct 26, 2019
  130. ryanofsky commented at 1:24 pm on October 26, 2019: member
    Updated e18bf8dd30259f5c5ca63fdacadebd8b28acb2cc -> 25c4ab5e0afd02e7dd19ea143f2e087def5f7b87 (pr/mergeset.20 -> pr/mergeset.21, compare) with suggested changes from John.
  131. Talkless commented at 1:02 pm on October 27, 2019: none

    I might encourage taking a broader view and looking for impacts of changes here that may be more concerning than style or performance tweaks.

    Sorry, I can’t do more in-depth review. And that’s enough for nit-picking, not to be too annoying…

  132. Talkless approved
  133. Talkless commented at 1:07 pm on October 27, 2019: none
    Concept ACK. Not tested, reviewed only cosmetics.
  134. ryanofsky force-pushed on Oct 28, 2019
  135. ryanofsky commented at 6:54 pm on October 28, 2019: member

    Sorry, I can’t do more in-depth review. And that’s enough for nit-picking, not to be too annoying…

    Thanks for review @Talkless. Your suggestions improved the PR, and having more eyes on code is always reassuring. Aside from suggesting cosmetic improvements, another thing you can try as a reviewer without specialized knowledge of the code is just asking questions. E.g. spend 20 minutes looking at a change, find the thing that seems most confusing or surprising in the code and ask about it. Chances are other people will wonder about the same thing, and usually whatever it is could stand to be clarified or at least explained better, so you learn something and help make the project more accessible, too.

  136. ryanofsky commented at 6:56 pm on October 28, 2019: member
    Updated 25c4ab5e0afd02e7dd19ea143f2e087def5f7b87 -> 69d44f3cc75a68d404ca0e1ca2b4831fd2bac4bb (pr/mergeset.21 -> pr/mergeset.22, compare) just using “default section” more and not sometimes calling it “top level” Rebased 69d44f3cc75a68d404ca0e1ca2b4831fd2bac4bb -> 202e19830854550ee4eade53fe4fd3ae323ddbdd (pr/mergeset.22 -> pr/mergeset.23) due to conflict with #17279
  137. DrahtBot added the label Needs rebase on Oct 28, 2019
  138. ryanofsky force-pushed on Oct 28, 2019
  139. DrahtBot removed the label Needs rebase on Oct 28, 2019
  140. jnewbery commented at 2:59 pm on October 29, 2019: member

    I’ve tried really hard to review this over the last few days, but the amount of change in the Deduplicate settings merge code commit has made this very difficult for me. Perhaps if that commit was broken up into smaller pieces that moved the different ArgsManager functions to use the settings logic one-by-one, it’d be easier for me to satisfy myself that this isn’t changing behaviour.

    I also find the new logic in settings.cpp very difficult to follow, where a callback is being passed from the GetSetting()/GetSettingsList()/OnlyHasDefaultSectionSetting() functions into MergeSettings(), and that callback is using variables from various places to update result.

    The new regression test is very nice and helps reassure me that this hasn’t changed behaviour, but I unfortunately can’t give an code-review ACK.

  141. ryanofsky commented at 8:52 pm on October 30, 2019: member

    I think this PR could use a re-ack or two from previous reviewers, but otherwise should be close to ready for merge, given all the review it’s had so far, and the exhaustive tests util_ArgsMerge and util_ChainMerge added in supporting PRs #15869 and #15988 to guarantee this PR doesn’t change behavior.

    Here is a list of previous reviews:

    promag concept ack 2019-05-02 #15934#pullrequestreview-232997353 fanquake concept ack 2019-05-24 #15934 (comment) jamesob tested ack 2019-05-28 #15934#pullrequestreview-242727281 jamesob reack 2019-05-29 #15934 (comment) jamesob reack 2019-06-27 #15934 (comment) jamesob reack 2019-06-28 #15934 (comment) hebasto concept ack 2019-06-31 #15934 (comment) ariard review 2019-10-17 #15934#pullrequestreview-302987362 ariard tested ack 2019-10-22 #15934#pullrequestreview-305582886 fjahr tested ack 2019-10-23 #15934#pullrequestreview-305976230 jonatack tested ack 2019-10-23 #15934#pullrequestreview-306047236 fjahr reack 2019-10-23 #15934 (comment) talkless review 2019-10-24 #15934#pullrequestreview-306757104 jnewbery review 2019-10-25 #15934#pullrequestreview-307333248 jnewbery review 2019-10-29 #15934 (comment)


    re: #15934 (comment) from jnewbery

    I’ve tried really hard to review this over the last few days, but the amount of change in the Deduplicate settings merge code commit has made this very difficult for me. Perhaps if that commit was broken up into smaller pieces that moved the different ArgsManager functions to use the settings logic one-by-one, it’d be easier for me to satisfy myself that this isn’t changing behaviour.

    I looked into this and it’s possible to make the PR do a more gradual transition between the old and the new merge implementations by adding new temporary code and then deleting it within the PR. For a first step (which maybe is sufficient for your purpose of understanding the previous code better) I was able to come up with eadbe7c164454a9faf0992d1605742a5626e729 from pr/merge2 which adds an initial commit at something like a halfway point between the new code and old code.

    But what you’re trying to do strikes me as an unnecessarily painful way to approach the review. The PR is replacing duplicated, sprawled out, buggy, opaque, and completely insane merging code, with a new, slightly less insane, more contained and better documented implementation. There isn’t a reason as a reviewer to dig into details of the old implementation when there are regression tests covering all the ArgsManager inputs and outputs to guarantee the new implementation is equivalent. If the new implementation can be improved, I’d like to improve it! If understanding details of how the new and old implementations map on to each other is interesting, I am happy to walk through them. But I don’t think a longer, more meandering PR that gets to the same result would be an improvement in this particular case.

    I also find the new logic in settings.cpp very difficult to follow, where a callback is being passed from the GetSetting()/GetSettingsList()/OnlyHasDefaultSectionSetting() functions into MergeSettings(), and that callback is using variables from various places to update result.

    I wonder if would help to rename MergeSettings to ForEachSettingsSource. This is just using an idiom for looping which is pretty common in other codebases, with MergeSettings here looping over fields of a struct. If you were trying to initially understand for(int i = 1; i < 10; ++i) { code; } and ForInterval(1, 10, [&](int i) { code; }); you could easily get hung up on either one, but once you’ve seen the idiom they should both become pretty straightforward.

    The new regression test is very nice and helps reassure me that this hasn’t changed behaviour, but I unfortunately can’t give an code-review ACK.

    That’s ok and thanks for the improvements you suggested before. If there’s anything I can do to help, like showing where things correspond in the old and new implementations, or improving the new implementation, I’m happy to make the effort.

  142. in src/util/settings.cpp:20 in b70e95b233 outdated
    15+    bool config_file = false;
    16+    bool config_file_default_section = false;
    17+
    18+    explicit Source(SettingsSpan span) : span(span) {}
    19+    Source& SetForced() noexcept { forced = true; return *this; }
    20+    Source& SetConfigFile(bool default_section) noexcept { config_file = true; config_file_default_section = default_section; return *this; }
    


    jamesob commented at 6:32 pm on October 31, 2019:
    nit: line is long enough to overflow the github UI
  143. jamesob approved
  144. jamesob commented at 8:52 pm on October 31, 2019: member

    ACK https://github.com/bitcoin/bitcoin/pull/15934/commits/202e19830854550ee4eade53fe4fd3ae323ddbdd

    ReACKing this after rereviewing the code commit-by-commit. Admittedly my reading of the code this time around was less comprehensive than my last review – paging in an understanding of the existing settings code is very difficult and time-consuming – but I read enough to verify there’s nothing malicious here. Given how well covered settings parsing is by tests and the fact that this region of the code is low-risk, I feel good about merging this change.

    I built the code locally and repeated the manual tests outlined in #15934#pullrequestreview-242727281.

    Edit: forgot to mention that the second commit is most easily reviewed in a split view IMO.

  145. in src/util/system.cpp:326 in 422d3784d6 outdated
    320@@ -408,49 +321,43 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin
    321         if (key.length() > 1 && key[1] == '-')
    322             key.erase(0, 1);
    323 
    324+        key.erase(0, 1);
    


    ariard commented at 6:01 pm on November 7, 2019:
    nit: add comment “transform -foo to foo” (over-commenting parsing here doesn’t seem gross to me)

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    nit: add comment “transform -foo to foo” (over-commenting parsing here doesn’t seem gross to me)

    Agree, will add.

  146. in src/util/system.cpp:782 in 202e198308 outdated
    781@@ -899,25 +782,31 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
    782         bool emptyIncludeConf;
    


    jnewbery commented at 6:06 pm on November 7, 2019:

    I can’t comment on the exact line above (L897), but that comment refers to ‘override args’ and so should be changed in this PR.

    I think the comment and bool name are pretty confusing, and could be made much clearer. Suggestion:

     0        // `-includeconf` cannot be included in the command line arguments except
     1        // as `-noincludeconf` (which indicates that no conf file should be used).
     2        bool use_conf_file{true};
     3        {
     4            LOCK(cs_args);
     5            const std::vector<util::SettingsValue>* const includes = util::FindKey(m_settings.command_line_options, "includeconf");
     6            if (includes) {
     7                // ParseParameters() fails if a non-negated -includeconf is passed on the command-line
     8                assert(util::SettingsSpan(*includes).last_negated());
     9                use_conf_file = false;
    10            }
    11        }
    12        if (use_conf_file) {
    

    ryanofsky commented at 10:20 pm on November 7, 2019:

    re: #15934 (review)

    I can’t comment on the exact line above (L897), but that comment refers to ‘override args’ and so should be changed in this PR.

    I think the comment and bool name are pretty confusing, and could be made much clearer. Suggestion:

    Agree, will take your suggestion.

  147. in src/util/system.cpp:790 in 202e198308 outdated
    793-                // We haven't set m_network yet (that happens in SelectParams()), so manually check
    794-                // for network.includeconf args.
    795-                std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf"));
    796-                includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end());
    797-            }
    798+            std::vector<std::string> includeconf;
    


    jnewbery commented at 6:30 pm on November 7, 2019:

    These variable names (which are unchanged by your PR) aren’t very helpful. I’d suggest the following renames for a later PR:

    includeconf -> conf_file_names to_include -> conf_file_name include_conf -> conf_file_stream


    ryanofsky commented at 10:20 pm on November 7, 2019:

    re: #15934 (review)

    These variable names (which are unchanged by your PR) aren’t very helpful. I’d suggest the following renames for a later PR:

    includeconf -> conf_file_names to_include -> conf_file_name include_conf -> conf_file_stream

    Yes. If it’s ok I think I will do these cleanups first and then rebase my changes on top.

  148. in src/util/system.cpp:828 in 202e198308 outdated
    821@@ -933,15 +822,14 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
    822             }
    823 
    824             // Warn about recursive -includeconf
    825-            includeconf = GetArgs("-includeconf");
    826+            includeconf.clear();
    827+            add_includes(chain_id, /* skip= */ chain_includes);
    828+            add_includes({}, /* skip= */ default_includes);
    829             {
    


    jnewbery commented at 6:32 pm on November 7, 2019:
    (unchanged but touched by this PR): I don’t understand why this needs to be its own scope. Perhaps it can be flattened in a follow-up PR?

    ryanofsky commented at 10:20 pm on November 7, 2019:

    re: #15934 (review)

    (unchanged but touched by this PR): I don’t understand why this needs to be its own scope. Perhaps it can be flattened in a follow-up PR?

    Probably this was just done as a style thing to make it clearer the variables here won’t be used after the scope. But there’s only one variable left now so this seems fine to remove.

  149. in src/util/system.cpp:211 in 422d3784d6 outdated
    219     size_t option_index = key.find('.');
    220-    if (option_index == std::string::npos) {
    221-        option_index = 1;
    222-    } else {
    223-        ++option_index;
    224+    if (option_index != std::string::npos) {
    


    ariard commented at 6:36 pm on November 7, 2019:
    nit: comment “Read section like testnet.foo or regtest.bar”

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    nit: comment “Read section like testnet.foo or regtest.bar”

    Will add this

  150. in src/util/settings.cpp:12 in 202e198308 outdated
     7+#include <univalue.h>
     8+
     9+namespace util {
    10+namespace {
    11+
    12+struct Source {
    


    jnewbery commented at 6:47 pm on November 7, 2019:
    For me, this struct doesn’t make things any clearer. An alternative is to pass an enum into the callback function to indicate where the SettingsSpan came from. Take a look at the change here, which also adds a few more comments: https://github.com/jnewbery/bitcoin/commit/fc3e1d4a2b8201cc694aee01a84b2b5e937b99cb

    ryanofsky commented at 10:20 pm on November 7, 2019:

    re: #15934 (review)

    For me, this struct doesn’t make things any clearer. An alternative is to pass an enum into the callback function to indicate where the SettingsSpan came from. Take a look at the change here, which also adds a few more comments: jnewbery@fc3e1d4

    You’re right. Sorry I doubted you! Struct was actually a holdover from an earlier iteration of this that had other properties like the section name. I’ll squash in your change.

  151. jnewbery commented at 6:49 pm on November 7, 2019: member

    utACK 202e19830

    I have a couple of suggestions for clarifying the code in some places. Take a look at https://github.com/jnewbery/bitcoin/commits/pr15934.2 and let me know what you think.

  152. in src/util/system.cpp:865 in 422d3784d6 outdated
    860+        return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str());
    861+    };
    862+
    863+    const bool fRegTest = get_net("-regtest");
    864+    const bool fTestNet = get_net("-testnet");
    865     const bool is_chain_arg_set = IsArgSet("-chain");
    


    ariard commented at 8:03 pm on November 7, 2019:
    I got why we need IsArgSet("chain") can’t CBaseChainParams::MAIN be returned by default in this function ? I tested chain=0 and it start anyway on main chain..

    ryanofsky commented at 10:22 pm on November 7, 2019:

    re: #15934 (review)

    I got why we need IsArgSet("chain") can’t CBaseChainParams::MAIN be returned by default in this function ?

    The result of IsArgSet isn’t needed to set the default, since that’s handled below. It’s only called to trigger an error if -regtest -testnet and -chain= options are used together because you’re only allowed to use one.

    For example bitcoind -chain=main -regtest triggers Invalid combination of -regtest, -testnet and -chain. Can use at most one.'

    I tested chain=0 and it start anyway on main chain..

    I don’t see this. Was it intentional to omit the leading dash before -chain? When I start with -chain=0 I see Error: CreateBaseChainParams: Unknown chain 0. When I start with chain=0 I see Error: Command line contains unexpected token 'chain=0', see bitcoind -h for a list of options.


    ariard commented at 4:26 pm on November 8, 2019:

    I don’t see this. Was it intentional to omit the leading dash before -chain?

    Ah sorry I tested with chain=0 in config file, but didn’t remember that bitcoind was pretty liberal with non-sense in config file..

    That’s said would it change anything to just return CBaseChainParams::MAIN at then of this function ? Assuming there is no conflict with other networks, if -chain isn’t set you’re going anyways to start on mainnet.


    ryanofsky commented at 4:53 pm on November 8, 2019:

    re: #15934 (review)

    That’s said would it change anything to just return CBaseChainParams::MAIN at then of this function ? Assuming there is no conflict with other networks, if -chain isn’t set you’re going anyways to start on mainnet.

    I might be misunderstanding the suggestion, but if the suggestion is to change return GetArg("-chain", CBaseChainParams::MAIN); to return CBaseChainParams::MAIN; then that will break usages bitcoind -chain=test or bitcoind -chain=regtest.

    If the question is why the overlapping options exist in the first place, I don’t know the answer, but this was done in #16680, and the discussion there probably has some justifications.


    ariard commented at 5:55 pm on November 8, 2019:
    Okay you’re right didn’t have the chain=test syntax in mind.
  153. in src/util/system.cpp:261 in 422d3784d6 outdated
    271-        found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg);
    272-        if (!found_result.first) continue;
    273-
    274-        // otherwise, issue a warning
    275-        unsuitables.insert(arg);
    276+        if (OnlyHasDefaultSectionSetting(m_settings, m_network, SettingName(arg))) {
    


    ariard commented at 8:43 pm on November 7, 2019:
    I’m wondering if there you could return after first match, in AppInitParameterInteraction unsuitables are processed as errors and not warnings

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    I’m wondering if there you could return after first match, in AppInitParameterInteraction unsuitables are processed as errors and not warnings

    Wow that is pretty funny. At one point I believe this was warning, and then it was changed into an error. I think it’s better to return the complete set here though, so we can improve the error message to mention all the unsuitables instead of just the first.


    jnewbery commented at 4:17 pm on November 8, 2019:

    I agree. The:

    0    for (const auto& arg : gArgs.GetUnsuitableSectionOnlyArgs()) {
    1        return InitError(strprintf(_("Config setting for %s only applied on %s network when in [%s] section.").translated, arg, network, network));
    2    }
    

    in AppInitParameterInteraction() clearly doesn’t make sense.


    ariard commented at 5:05 pm on November 8, 2019:
    Yes I’m fine too if we print all errors than return
  154. in src/util/settings.cpp:103 in b70e95b233 outdated
     98+    bool result_complete = false;
     99+    bool prev_negated_empty = false;
    100+    MergeSettings(settings, section, name, [&](Source source) {
    101+        // Weird behavior preserved for backwards compatibility: Apply config
    102+        // file settings even if negated on command line. Negating a setting on
    103+        // command line will discard earlier settings on the command line and
    


    ariard commented at 9:44 pm on November 7, 2019:
    You’re mentioning discard of earlier settings on the command line and settings in the config file but in MergeSettings command line is evaluated before config so it can’t discard something which hasn’t evaluated yet ? I think you’re thinking about the old code but it’s not really clear..

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    You’re mentioning discard of earlier settings on the command line and settings in the config file but in MergeSettings command line is evaluated before config so it can’t discard something which hasn’t evaluated yet ? I think you’re thinking about the old code but it’s not really clear..

    By “discard” here I just mean “ignore”. And “earlier” is just referring to earlier command line settings, not anything having to do with settings from different sources. I’ll go ahead and replace “discard” with “ignore” in this paragraph and maybe say “ignore earlier settings on the command line, and ignore settings in the config file” to be clearer what “earlier” refers to.

    To take an example, lets say you have wallet=w1 wallet=w2 in the config file and -wallet=w3 -wallet=w4 -nowallet on the command line. The final wallet list will be empty because the last -nowallet discards everything that came before (later values take precedence over earlier values, command line arguments take precedence over config arguments).

    The zombie case is wallet=w1 wallet=w2 in the config file and -wallet=w3 -wallet=w4 -nowallet -wallet=w5 -wallet=w6. In this case, I would want the wallet list to look like ["w5", "w6"]. But actually it looks like ["w5", "w6", "w1", "w2"] because just adding w5 to the previous example mysteriously brings back previously ignored w1 and w2.


    ariard commented at 4:34 pm on November 8, 2019:
    Thanks, gotcha, I find code easier to grasp moving to Source::CONFIG_FILE_DEFAULT_SECTION, etc
  155. in src/util/settings.cpp:65 in b70e95b233 outdated
    60+
    61+        // Weird behavior preserved for backwards compatibility: Take first
    62+        // assigned value instead of last. In general, later settings take
    63+        // precedence over early settings, but for backwards compatibility in
    64+        // the config file the precedence is reversed for most settings.
    65+        const bool reverse_precedence = source.config_file && !get_chain_name;
    


    ariard commented at 9:50 pm on November 7, 2019:
    Okay is this weird behavior due to the way GetConfigOptions and ReadConfigStream are parsing ? Also maybe you can update comment to explain most settings is “most settings except for chain namewhich would underscore why you care aboutget_chain_name`

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    Okay is this weird behavior due to the way GetConfigOptions and ReadConfigStream are parsing ?

    The code has changed a lot since this behavior was introduced, so I don’t recall what this behavior was due to initially. I’m pretty sure it was never intentional. But in the current master branch, this behavior is caused by passing getLast=false to GetArgHelper for config file settings, and getLast=true for command line settings. In this PR getLast is just replaced by reverse_precendence.

    Also maybe you can update comment to explain most settings is “most settings except for chain namewhich would underscore why you care aboutget_chain_name`

    Sure, will add.


    ariard commented at 4:38 pm on November 8, 2019:
    Ah no you’re right, nothing to do with ReadConfigStream parsing, that’s just a weird flag of GetArgHelper, without that much rational..
  156. in src/util/settings.cpp:56 in b70e95b233 outdated
    51+    bool get_chain_name)
    52+{
    53+    SettingsValue result;
    54+    MergeSettings(settings, section, name, [&](Source source) {
    55+        // Weird behavior preserved for backwards compatibility: Apply negated
    56+        // setting in otherwise ignored sections. A negated value in the
    


    ariard commented at 10:01 pm on November 7, 2019:
    So the behavior you’re describing is in case of negated value in default section, this result is going to take precedence on value in network section even it’s a non-negated one ? If yes saying that network specific options are overriden would be more correct than ignored.

    ryanofsky commented at 10:21 pm on November 7, 2019:

    re: #15934 (review)

    So the behavior you’re describing is in case of negated value in default section, this result is going to take precedence on value in network section even it’s a non-negated one ? If yes saying that network specific options are overriden would be more correct than ignored.

    Hmm, this isn’t referring to the network-specific options in network sections that are overridden by the weird behavior. It is referring to network-specific settings in the default section that are supposed to be ignored when -regtest or -testnet are used.

    Maybe I can change “Apply negated setting in otherwise ignored sections” to “Apply negated setting even if it is in supposed to be ignored.”


    ariard commented at 4:46 pm on November 8, 2019:
    Thanks, after your explanation, I think comment is good enough (or don’t suggestion in mind to make it better)
  157. ariard commented at 10:06 pm on November 7, 2019: member

    @ryanofsky I’m curious of more details on some weird behaviors you’re documented, it’s hard to grasp them or comment may be better. Otherwise my others comments are nits or follow-ups improvements, holding my ACK just on answers to weird behaviors.

    Advice to any other reviewer, it’s easier to understand PR starting in reverse topological order Deduplicate settings merge code and then Add util::Settings struct and helper functions

  158. ryanofsky commented at 11:59 pm on November 7, 2019: member
    Thanks John and Antoine for sticking with this and not giving up on this insanity! I’m adopting every suggestion here and making changes to address all comments.
  159. Clarify emptyIncludeConf logic
    Suggestion from John Newbery <john@johnnewbery.com> in
    https://github.com/bitcoin/bitcoin/pull/15934#discussion_r343795528
    dc8e1e7548
  160. Rename includeconf variables for clarity
    includeconf -> conf_file_names
    to_include -> conf_file_name
    include_config -> conf_file_stream
    
    Suggestion from John Newbery <john@johnnewbery.com> in
    https://github.com/bitcoin/bitcoin/pull/15934#discussion_r343905138
    5a84aa880f
  161. Remove includeconf nested scope
    Easier to review ignoring whitespace
    
    Suggestion from John Newbery <john@johnnewbery.com> in
    https://github.com/bitcoin/bitcoin/pull/15934#discussion_r343806860
    e2e37cfe8a
  162. Add util::Settings struct and helper functions.
    Implement merging of settings from different sources (command line and config
    file) separately from parsing code in system.cpp, so it is easier to add new
    sources.
    
    Document current inconsistent merging behavior without changing it.
    
    This commit only adds new settings code without using it. The next commit calls
    the new code to replace existing code in system.cpp.
    
    Co-authored-by: John Newbery <john@johnnewbery.com>
    9dcb952fe5
  163. Deduplicate settings merge code
    Get rid of settings merging code in util/system.cpp repeated 5 places,
    inconsistently:
    
    - ArgsManagerHelper::GetArg
    - ArgsManagerHelper::GetNetBoolArg
    - ArgsManager::GetArgs
    - ArgsManager::IsArgNegated
    - ArgsManager::GetUnsuitableSectionOnlyArgs
    
    Having settings merging code separated from parsing simplifies parsing somewhat
    (for example negated values can simply be represented as false values instead
    of partially cleared or emply placeholder lists).
    
    Having settings merge happen one place instead of 5 makes it easier to add new
    settings sources and harder to introduce new inconsistencies in the way
    settings are merged.
    
    This commit does not change behavior in any way.
    7f40528cd5
  164. Add settings_tests
    Co-authored-by: James O'Beirne <james.obeirne@gmail.com>
    083c954b02
  165. ryanofsky referenced this in commit da06fc0eb2 on Nov 8, 2019
  166. ryanofsky referenced this in commit a468529100 on Nov 8, 2019
  167. ryanofsky referenced this in commit 5c623d3c80 on Nov 8, 2019
  168. ryanofsky force-pushed on Nov 8, 2019
  169. ryanofsky commented at 3:55 am on November 8, 2019: member
    Updated 202e19830854550ee4eade53fe4fd3ae323ddbdd -> 20f74e2fba764299f48bd872c9dc2df86c21e747 (pr/mergeset.23 -> pr/mergeset.24, compare) with suggestions above. Rebased 20f74e2fba764299f48bd872c9dc2df86c21e747 -> 083c954b02a4e7d0708349eeaf3bac2b5947fb0e (pr/mergeset.24 -> pr/mergeset.25) to fix silent merge conflict with #17384 (fatal error: 'test/setup_common.h' file not found')
  170. ryanofsky force-pushed on Nov 8, 2019
  171. in src/util/system.cpp:898 in dc8e1e7548 outdated
    893@@ -894,14 +894,19 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
    894         if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) {
    895             return false;
    896         }
    897-        // if there is an -includeconf in the override args, but it is empty, that means the user
    898-        // passed '-noincludeconf' on the command line, in which case we should not include anything
    899-        bool emptyIncludeConf;
    900+        // `-includeconf` cannot be included in the command line arguments except
    901+        // as `-noincludeconf` (which indicates that no conf file should be used).
    


    ariard commented at 4:53 pm on November 8, 2019:
    I find comment a bit confusing, -noincludeconf means we are only going to discard includeconf in config file but not the main config itself (at least I tested that the current behavior with this patchset AFAII)

    ryanofsky commented at 5:00 pm on November 8, 2019:

    re: #15934 (review)

    I find comment a bit confusing, -noincludeconf means we are only going to discard includeconf in config file but not the main config itself (at least I’ve tested that the current behavior with this patchset)

    -noincludeconf just disables the -includeconf directive. I wouldn’t expect it to disable the whole config file. (I might expect -noconf to do that.) So this doesn’t seem that confusing to me, but I’m happy to take a suggestion if you have an idea to improve it.

    The current text just comes from John’s suggestion #15934 (review)


    ariard commented at 5:47 pm on November 8, 2019:
    nit-picking there, was just to change comment to (which indicates that no included conf file should be used)
  172. in src/util/system.cpp:348 in 083c954b02
    352-        if (it->second.size() > 0) {
    353-            for (const auto& ic : it->second) {
    354-                error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n";
    355-            }
    356-            return false;
    357+    bool success = true;
    


    jnewbery commented at 4:56 pm on November 8, 2019:

    nit (please don’t change in this PR. Leave for a follow-up, if at all): ‘we clear it’ in the comment above is inaccurate. If an -includeconf is found on the command line then we return false rather than silently removing the bad config. Change that comment to // Do not allow -includeconf from the command line (except for -noincludeconf)

    In fact, I don’t think we need the local success variable at all. You could just return false from the inner for loop. I don’t think the user needs to be told about the multiple -includeconf command line arguments they’ve used. Just alerting about the first should be enough.

  173. ariard commented at 5:51 pm on November 8, 2019: member

    ACK 083c954

    I’ve reviewed that’s new code is doing what it says to do. I think that weird behaviors are compatibility-maintained but at least if we have a regression they are well-documented and so we’ll know where to look, plus as said by James this part of code is low-risk so it’s worth moving forward.

    Great work Russ, and I’m concept ACK future PRs to remove/squeeze weird behaviors!

  174. jnewbery commented at 6:22 pm on November 8, 2019: member
    ACK 083c954b02a4e7d0708349eeaf3bac2b5947fb0e
  175. ryanofsky commented at 6:54 pm on November 8, 2019: member

    Thanks! Will leave PR at 083c954b02a4e7d0708349eeaf3bac2b5947fb0e. Some notes for future followup:

  176. jnewbery commented at 7:59 pm on November 8, 2019: member

    Other potential future follow-ups:

    • Don’t throw in GetChainName()

    (https://github.com/bitcoin/bitcoin/blob/8021392b825c74312173f15eb937ba6d4aec3841/src/util/system.cpp#L970). I think the calling code was originally supposed to catch this (eg https://github.com/bitcoin/bitcoin/blob/8021392b825c74312173f15eb937ba6d4aec3841/src/bitcoind.cpp#L88), but this is no longer the case because GetChainName() is called in ReadConfigFiles(), and so starting with -regtest -testnet results in an unhandled exception.

    • Remove ArgsManagerHelper()

    Here in master: https://github.com/bitcoin/bitcoin/blob/8021392b825c74312173f15eb937ba6d4aec3841/src/util/system.cpp#L165. After this PR there are only two functions in this class. Just move them into ArgsManager(). The class was only added to speed up rebuilding (https://github.com/bitcoin/bitcoin/pull/11862#discussion_r173870835), which doesn’t seem like a good reason to keep it.

    • Move the ArgsManager code into util/settings ?
  177. in src/util/settings.cpp:71 in 9dcb952fe5 outdated
    66+        // Weird behavior preserved for backwards compatibility: Take first
    67+        // assigned value instead of last. In general, later settings take
    68+        // precedence over early settings, but for backwards compatibility in
    69+        // the config file the precedence is reversed for all settings except
    70+        // chain name settings.
    71+        const bool reverse_precedence = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !get_chain_name;
    


    jamesob commented at 9:07 pm on November 8, 2019:
    These long lines are killin’ me but I’ll bite my tongue in the interest of a merge.

    ryanofsky commented at 9:41 pm on November 8, 2019:
    Will fix with the other followups, but if it in case its useful https://greasyfork.org/en/scripts/18789-github-toggle-code-wrap seems to work well. (Found through https://github.com/StylishThemes/GitHub-code-wrap)
  178. jamesob approved
  179. jamesob commented at 9:16 pm on November 8, 2019: member

    ACK 083c954b02a4e7d0708349eeaf3bac2b5947fb0e

    Reviewed diff-of-diffs both on Github and locally. Only changes since my last ACK are those discussed above. Nice job persevering, @ryanofsky, and thanks to @jnewbery @ariard for good feedback.

  180. laanwj referenced this in commit a7aec7ad97 on Nov 8, 2019
  181. laanwj merged this on Nov 8, 2019
  182. laanwj closed this on Nov 8, 2019

  183. sidhujag referenced this in commit dfc914880a on Nov 9, 2019
  184. sidhujag referenced this in commit 248b11fb0f on Nov 9, 2019
  185. sidhujag referenced this in commit 0bc925ddb6 on Nov 9, 2019
  186. sidhujag referenced this in commit 16a52029e7 on Nov 9, 2019
  187. fanquake removed this from the "Blockers" column in a project

  188. ryanofsky referenced this in commit fb4e02508e on Nov 14, 2019
  189. ryanofsky referenced this in commit 80b46ba8ca on Nov 14, 2019
  190. ryanofsky referenced this in commit 959096cbac on Nov 14, 2019
  191. ryanofsky referenced this in commit 6f018d3673 on Nov 14, 2019
  192. ryanofsky referenced this in commit 6974a2fecb on Nov 14, 2019
  193. ryanofsky referenced this in commit db458b3a0f on Nov 14, 2019
  194. ryanofsky commented at 4:40 pm on November 18, 2019: member

    I’ve been working on some followups for this PR since it’s been merged. #17473 has various refactoring cleanups suggested above, and #17508 is tracking cleanup of confusing settings behaviors.

    Going through all the comments in this issue, I think the only suggestions left not addressed by #17473 and #17508 are:

    I don’t have plans to work on these last few things

  195. ariard commented at 5:37 pm on November 18, 2019: member
    Maybe tag them good_first_issue or up_to_grabs to let people find them
  196. ryanofsky added the label Up for grabs on Nov 18, 2019
  197. ryanofsky referenced this in commit 3f7dc9b808 on Dec 18, 2019
  198. ryanofsky referenced this in commit 57e8b7a727 on Dec 18, 2019
  199. ryanofsky referenced this in commit dc0f148074 on Dec 18, 2019
  200. ryanofsky referenced this in commit 3e185522ac on Dec 18, 2019
  201. MarcoFalke referenced this in commit 6677be64f6 on Dec 19, 2019
  202. sidhujag referenced this in commit 33fe88ae5b on Dec 19, 2019
  203. HashUnlimited referenced this in commit d72048269a on Apr 17, 2020
  204. HashUnlimited referenced this in commit d558168a1c on Apr 17, 2020
  205. HashUnlimited referenced this in commit b4e4df9618 on Apr 17, 2020
  206. HashUnlimited referenced this in commit a31740dbc8 on Apr 17, 2020
  207. HashUnlimited referenced this in commit 2f174783e4 on Apr 17, 2020
  208. HashUnlimited referenced this in commit 7a97631cc5 on Apr 17, 2020
  209. HashUnlimited referenced this in commit 6c2e836e12 on Apr 17, 2020
  210. deadalnix referenced this in commit b60a395a83 on Apr 27, 2020
  211. deadalnix referenced this in commit 455cd82187 on Apr 29, 2020
  212. deadalnix referenced this in commit e594f5b1be on Apr 29, 2020
  213. deadalnix referenced this in commit 6a645371c0 on Apr 29, 2020
  214. deadalnix referenced this in commit 0a64462924 on Apr 30, 2020
  215. deadalnix referenced this in commit 380fef33ea on Apr 30, 2020
  216. deadalnix referenced this in commit 4943f9ea3a on Apr 30, 2020
  217. deadalnix referenced this in commit 619943a5c9 on Apr 30, 2020
  218. ftrader referenced this in commit 5a0b5039bd on Aug 17, 2020
  219. sidhujag referenced this in commit 0cbe87777d on Nov 10, 2020
  220. sidhujag referenced this in commit 33f7a4b53b on Nov 10, 2020
  221. sidhujag referenced this in commit c0c2bcddec on Nov 10, 2020
  222. sidhujag referenced this in commit c1677b430c on Nov 10, 2020
  223. sidhujag referenced this in commit 96245fcfb0 on Nov 10, 2020
  224. silence48 referenced this in commit d8e262268f on Nov 15, 2020
  225. MarcoFalke commented at 9:20 am on January 30, 2021: member
    Hidden by GitHub, but the “Up for grabs” refers to #15934 (comment)
  226. backpacker69 referenced this in commit 0839aa237e on Mar 28, 2021
  227. backpacker69 referenced this in commit e78d17f1c6 on Mar 28, 2021
  228. backpacker69 referenced this in commit 7f404e8d42 on Mar 28, 2021
  229. backpacker69 referenced this in commit 899f4ff4c4 on Mar 28, 2021
  230. MarcoFalke removed the label Up for grabs on Aug 18, 2021
  231. uvhw referenced this in commit 47d44ccc3e on Feb 14, 2022
  232. Stackout referenced this in commit 29b1f0a7fd on Feb 17, 2022
  233. Stackout referenced this in commit b999e46dd4 on Feb 17, 2022
  234. Stackout referenced this in commit 94a272bb9d on Feb 17, 2022
  235. Stackout referenced this in commit 3857614709 on Feb 17, 2022
  236. Stackout referenced this in commit cb85b99bd6 on Feb 17, 2022
  237. Stackout referenced this in commit caa30cc07d on Feb 17, 2022
  238. Stackout referenced this in commit deb9db2b95 on Feb 17, 2022
  239. kittywhiskers referenced this in commit 9ca6cd6cd9 on Apr 13, 2022
  240. kittywhiskers referenced this in commit c9b2afaced on Apr 13, 2022
  241. kittywhiskers referenced this in commit 6943e2c7bd on Apr 14, 2022
  242. kittywhiskers referenced this in commit 3b8223ee77 on Apr 16, 2022
  243. kittywhiskers referenced this in commit e7caa77cac on Apr 16, 2022
  244. kittywhiskers referenced this in commit 59c2c96f9d on Apr 20, 2022
  245. kittywhiskers referenced this in commit 9d343c9878 on May 7, 2022
  246. kittywhiskers referenced this in commit 03d3a64e6f on May 9, 2022
  247. kittywhiskers referenced this in commit cf92976c2b on May 13, 2022
  248. kittywhiskers referenced this in commit 00b0234191 on Jun 2, 2022
  249. kittywhiskers referenced this in commit fc34557327 on Jun 2, 2022
  250. kittywhiskers referenced this in commit cd14343a3a on Jun 2, 2022
  251. kittywhiskers referenced this in commit b37a692571 on Jun 3, 2022
  252. kittywhiskers referenced this in commit 34284011c3 on Jun 4, 2022
  253. PastaPastaPasta referenced this in commit 398c0bcb7f on Jun 7, 2022
  254. PastaPastaPasta referenced this in commit 7cedf84d50 on Jun 7, 2022
  255. PastaPastaPasta referenced this in commit e79d73f0fc on Jun 7, 2022
  256. PastaPastaPasta referenced this in commit 78ac0e9361 on Jun 7, 2022
  257. PastaPastaPasta referenced this in commit d1187d70d1 on Jun 10, 2022
  258. PastaPastaPasta referenced this in commit 67f95cb60b on Jun 14, 2022
  259. DrahtBot locked this on Aug 18, 2022

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: 2024-07-03 10:13 UTC

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