diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp index 39b5f3ad3e5ff..b86d0b2991325 100644 --- a/src/node/chainstatemanager_args.cpp +++ b/src/node/chainstatemanager_args.cpp @@ -32,13 +32,20 @@ util::Result ApplyArgsManOptions(const ArgsManager& args, ChainstateManage if (auto value{args.GetBoolArg("-checkpoints")}) opts.checkpoints_enabled = *value; if (auto value{args.GetArg("-minimumchainwork")}) { - if (!IsHexNumber(*value)) { - return util::Error{strprintf(Untranslated("Invalid non-hex (%s) minimum chain work value specified"), *value)}; + if (auto min_work{uint256::FromUserHex(*value)}) { + opts.minimum_chain_work = UintToArith256(*min_work); + } else { + return util::Error{strprintf(Untranslated("Invalid minimum work specified (%s), must be up to %d hex digits"), *value, uint256::size() * 2)}; } - opts.minimum_chain_work = UintToArith256(uint256S(*value)); } - if (auto value{args.GetArg("-assumevalid")}) opts.assumed_valid_block = uint256S(*value); + if (auto value{args.GetArg("-assumevalid")}) { + if (auto block_hash{uint256::FromUserHex(*value)}) { + opts.assumed_valid_block = *block_hash; + } else { + return util::Error{strprintf(Untranslated("Invalid assumevalid block hash specified (%s), must be up to %d hex digits (or 0 to disable)"), *value, uint256::size() * 2)}; + } + } if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value}; diff --git a/src/test/fuzz/hex.cpp b/src/test/fuzz/hex.cpp index ebe30c3c1aa8b..0140c5947ed49 100644 --- a/src/test/fuzz/hex.cpp +++ b/src/test/fuzz/hex.cpp @@ -27,11 +27,14 @@ FUZZ_TARGET(hex) if (IsHex(random_hex_string)) { assert(ToLower(random_hex_string) == hex_data); } - (void)IsHexNumber(random_hex_string); if (uint256::FromHex(random_hex_string)) { assert(random_hex_string.length() == 64); assert(Txid::FromHex(random_hex_string)); assert(Wtxid::FromHex(random_hex_string)); + assert(uint256::FromUserHex(random_hex_string)); + } + if (const auto result{uint256::FromUserHex(random_hex_string)}) { + assert(uint256::FromHex(result->ToString())); } (void)uint256S(random_hex_string); try { diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index 2ba322717481d..4777f7ed4730e 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -393,6 +393,35 @@ BOOST_AUTO_TEST_CASE(from_hex) TestFromHex(); } +BOOST_AUTO_TEST_CASE(from_user_hex) +{ + BOOST_CHECK_EQUAL(uint256::FromUserHex("").value(), uint256::ZERO); + BOOST_CHECK_EQUAL(uint256::FromUserHex("0x").value(), uint256::ZERO); + BOOST_CHECK_EQUAL(uint256::FromUserHex("0").value(), uint256::ZERO); + BOOST_CHECK_EQUAL(uint256::FromUserHex("00").value(), uint256::ZERO); + BOOST_CHECK_EQUAL(uint256::FromUserHex("1").value(), uint256::ONE); + BOOST_CHECK_EQUAL(uint256::FromUserHex("0x10").value(), uint256{0x10}); + BOOST_CHECK_EQUAL(uint256::FromUserHex("10").value(), uint256{0x10}); + BOOST_CHECK_EQUAL(uint256::FromUserHex("0xFf").value(), uint256{0xff}); + BOOST_CHECK_EQUAL(uint256::FromUserHex("Ff").value(), uint256{0xff}); + const std::string valid_hex_64{"0x0123456789abcdef0123456789abcdef0123456789ABDCEF0123456789ABCDEF"}; + BOOST_REQUIRE_EQUAL(valid_hex_64.size(), 2 + 64); // 0x prefix and 64 hex digits + BOOST_CHECK_EQUAL(uint256::FromUserHex(valid_hex_64.substr(2)).value().ToString(), ToLower(valid_hex_64.substr(2))); + BOOST_CHECK_EQUAL(uint256::FromUserHex(valid_hex_64.substr(0)).value().ToString(), ToLower(valid_hex_64.substr(2))); + + BOOST_CHECK(!uint256::FromUserHex("0x0 ")); // no spaces at end, + BOOST_CHECK(!uint256::FromUserHex(" 0x0")); // or beginning, + BOOST_CHECK(!uint256::FromUserHex("0x 0")); // or middle, + BOOST_CHECK(!uint256::FromUserHex(" ")); // etc. + BOOST_CHECK(!uint256::FromUserHex("0x0ga")); // invalid character + BOOST_CHECK(!uint256::FromUserHex("x0")); // broken prefix + BOOST_CHECK(!uint256::FromUserHex("0x0x00")); // two prefixes not allowed + BOOST_CHECK(!uint256::FromUserHex(valid_hex_64.substr(2) + "0")); // 1 hex digit too many + BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + "a")); // 1 hex digit too many + BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + " ")); // whitespace after max length + BOOST_CHECK(!uint256::FromUserHex(valid_hex_64 + "z")); // invalid character after max length +} + BOOST_AUTO_TEST_CASE( check_ONE ) { uint256 one = uint256{"0000000000000000000000000000000000000000000000000000000000000001"}; diff --git a/src/test/util/random.cpp b/src/test/util/random.cpp index 47d03055e2817..8e0623ea0641c 100644 --- a/src/test/util/random.cpp +++ b/src/test/util/random.cpp @@ -7,9 +7,10 @@ #include #include #include +#include #include -#include +#include FastRandomContext g_insecure_rand_ctx; @@ -17,7 +18,7 @@ extern void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept; void SeedRandomForTest(SeedRand seedtype) { - static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; + constexpr auto RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; // Do this once, on the first call, regardless of seedtype, because once // MakeRandDeterministicDANGEROUS is called, the output of GetRandHash is @@ -25,14 +26,20 @@ void SeedRandomForTest(SeedRand seedtype) // process. static const uint256 ctx_seed = []() { // If RANDOM_CTX_SEED is set, use that as seed. - const char* num = std::getenv(RANDOM_CTX_SEED.c_str()); - if (num) return uint256S(num); + if (const char* num{std::getenv(RANDOM_CTX_SEED)}) { + if (auto num_parsed{uint256::FromUserHex(num)}) { + return *num_parsed; + } else { + std::cerr << RANDOM_CTX_SEED << " must consist of up to " << uint256::size() * 2 << " hex digits (\"0x\" prefix allowed), it was set to: '" << num << "'.\n"; + std::abort(); + } + } // Otherwise use a (truly) random value. return GetRandHash(); }(); const uint256& seed{seedtype == SeedRand::SEED ? ctx_seed : uint256::ZERO}; - LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); + LogInfo("Setting random seed for current tests to %s=%s\n", RANDOM_CTX_SEED, seed.GetHex()); MakeRandDeterministicDANGEROUS(seed); g_insecure_rand_ctx.Reseed(GetRandHash()); } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index bf1fc1ea0af8d..c2c725d676988 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -432,31 +432,6 @@ BOOST_AUTO_TEST_CASE(util_IsHex) BOOST_CHECK(!IsHex("0x0000")); } -BOOST_AUTO_TEST_CASE(util_IsHexNumber) -{ - BOOST_CHECK(IsHexNumber("0x0")); - BOOST_CHECK(IsHexNumber("0")); - BOOST_CHECK(IsHexNumber("0x10")); - BOOST_CHECK(IsHexNumber("10")); - BOOST_CHECK(IsHexNumber("0xff")); - BOOST_CHECK(IsHexNumber("ff")); - BOOST_CHECK(IsHexNumber("0xFfa")); - BOOST_CHECK(IsHexNumber("Ffa")); - BOOST_CHECK(IsHexNumber("0x00112233445566778899aabbccddeeffAABBCCDDEEFF")); - BOOST_CHECK(IsHexNumber("00112233445566778899aabbccddeeffAABBCCDDEEFF")); - - BOOST_CHECK(!IsHexNumber("")); // empty string not allowed - BOOST_CHECK(!IsHexNumber("0x")); // empty string after prefix not allowed - BOOST_CHECK(!IsHexNumber("0x0 ")); // no spaces at end, - BOOST_CHECK(!IsHexNumber(" 0x0")); // or beginning, - BOOST_CHECK(!IsHexNumber("0x 0")); // or middle, - BOOST_CHECK(!IsHexNumber(" ")); // etc. - BOOST_CHECK(!IsHexNumber("0x0ga")); // invalid character - BOOST_CHECK(!IsHexNumber("x0")); // broken prefix - BOOST_CHECK(!IsHexNumber("0x0x00")); // two prefixes not allowed - -} - BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { SeedRandomForTest(SeedRand::ZEROS); diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index cf819f8751dd8..2ed59ab2d34a1 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -769,4 +772,59 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna } } +/** Helper function to parse args into args_man and return the result of applying them to opts */ +template +util::Result SetOptsFromArgs(ArgsManager& args_man, Options opts, + const std::vector& args) +{ + const auto argv{Cat({"ignore"}, args)}; + std::string error{}; + if (!args_man.ParseParameters(argv.size(), argv.data(), error)) { + return util::Error{Untranslated("ParseParameters failed with error: " + error)}; + } + const auto result{node::ApplyArgsManOptions(args_man, opts)}; + if (!result) return util::Error{util::ErrorString(result)}; + return opts; +} + +BOOST_FIXTURE_TEST_CASE(chainstatemanager_args, BasicTestingSetup) +{ + //! Try to apply the provided args to a ChainstateManager::Options + auto get_opts = [&](const std::vector& args) { + static kernel::Notifications notifications{}; + static const ChainstateManager::Options options{ + .chainparams = ::Params(), + .datadir = {}, + .notifications = notifications}; + return SetOptsFromArgs(*this->m_node.args, options, args); + }; + //! Like get_opts, but requires the provided args to be valid and unwraps the result + auto get_valid_opts = [&](const std::vector& args) { + const auto result{get_opts(args)}; + BOOST_REQUIRE_MESSAGE(result, util::ErrorString(result).original); + return *result; + }; + + // test -assumevalid + BOOST_CHECK(!get_valid_opts({}).assumed_valid_block.has_value()); + BOOST_CHECK(get_valid_opts({"-assumevalid="}).assumed_valid_block.value().IsNull()); + BOOST_CHECK(get_valid_opts({"-assumevalid=0"}).assumed_valid_block.value().IsNull()); + BOOST_CHECK(get_valid_opts({"-noassumevalid"}).assumed_valid_block.value().IsNull()); + BOOST_CHECK_EQUAL(get_valid_opts({"-assumevalid=0x1234"}).assumed_valid_block.value().ToString(), std::string(60, '0') + "1234"); + const std::string cmd{"-assumevalid=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}; + BOOST_CHECK_EQUAL(get_valid_opts({cmd.c_str()}).assumed_valid_block.value().ToString(), cmd.substr(13, cmd.size())); + + BOOST_CHECK(!get_opts({"-assumevalid=xyz"})); // invalid hex characters + BOOST_CHECK(!get_opts({"-assumevalid=01234567890123456789012345678901234567890123456789012345678901234"})); // > 64 hex chars + + // test -minimumchainwork + BOOST_CHECK(!get_valid_opts({}).minimum_chain_work.has_value()); + BOOST_CHECK_EQUAL(get_valid_opts({"-minimumchainwork=0"}).minimum_chain_work.value().GetCompact(), 0U); + BOOST_CHECK_EQUAL(get_valid_opts({"-nominimumchainwork"}).minimum_chain_work.value().GetCompact(), 0U); + BOOST_CHECK_EQUAL(get_valid_opts({"-minimumchainwork=0x1234"}).minimum_chain_work.value().GetCompact(), 0x02123400U); + + BOOST_CHECK(!get_opts({"-minimumchainwork=xyz"})); // invalid hex characters + BOOST_CHECK(!get_opts({"-minimumchainwork=01234567890123456789012345678901234567890123456789012345678901234"})); // > 64 hex chars +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/uint256.h b/src/uint256.h index cf008765bdca8..db37adfe6df20 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include /** Template base class for fixed-sized opaque blobs. */ template @@ -157,6 +159,25 @@ std::optional FromHex(std::string_view str) rv.SetHexDeprecated(str); return rv; } +/** + * @brief Like FromHex(std::string_view str), but allows an "0x" prefix + * and pads the input with leading zeroes if it is shorter than + * the expected length of uintN_t::size()*2. + * + * Designed to be used when dealing with user input. + */ +template +std::optional FromUserHex(std::string_view input) +{ + input = util::RemovePrefixView(input, "0x"); + constexpr auto expected_size{uintN_t::size() * 2}; + if (input.size() < expected_size) { + auto padded = std::string(expected_size, '0'); + std::copy(input.begin(), input.end(), padded.begin() + expected_size - input.size()); + return FromHex(padded); + } + return FromHex(input); +} } // namespace detail /** 160-bit opaque blob. @@ -178,6 +199,7 @@ class uint160 : public base_blob<160> { class uint256 : public base_blob<256> { public: static std::optional FromHex(std::string_view str) { return detail::FromHex(str); } + static std::optional FromUserHex(std::string_view str) { return detail::FromUserHex(str); } constexpr uint256() = default; consteval explicit uint256(std::string_view hex_str) : base_blob<256>(hex_str) {} constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {} diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index e030262a32eb6..15cb40aba1322 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -46,16 +46,6 @@ bool IsHex(std::string_view str) return (str.size() > 0) && (str.size()%2 == 0); } -bool IsHexNumber(std::string_view str) -{ - if (str.substr(0, 2) == "0x") str.remove_prefix(2); - for (char c : str) { - if (HexDigit(c) < 0) return false; - } - // Return false for empty string or "0x". - return str.size() > 0; -} - template std::optional> TryParseHex(std::string_view str) { diff --git a/src/util/strencodings.h b/src/util/strencodings.h index e5c2d3ddf2ddf..91ac35b13213c 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -70,10 +70,6 @@ std::vector ParseHex(std::string_view hex_str) /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ bool IsHex(std::string_view str); -/** -* Return true if the string is a hex number, optionally prefixed with "0x" -*/ -bool IsHexNumber(std::string_view str); std::optional> DecodeBase64(std::string_view str); std::string EncodeBase64(Span input); inline std::string EncodeBase64(Span input) { return EncodeBase64(MakeUCharSpan(input)); } diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 03a9f666b2a71..32ad3ad271e0e 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -139,8 +139,8 @@ def run_test(self): height += 1 # Start node1 and node2 with assumevalid so they accept a block with a bad signature. - self.start_node(1, extra_args=["-assumevalid=" + hex(block102.sha256)]) - self.start_node(2, extra_args=["-assumevalid=" + hex(block102.sha256)]) + self.start_node(1, extra_args=["-assumevalid=" + block102.hash]) + self.start_node(2, extra_args=["-assumevalid=" + block102.hash]) p2p0 = self.nodes[0].add_p2p_connection(BaseNode()) p2p0.send_header_for_blocks(self.blocks[0:2000]) diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py index 8327a0477b38c..34228f6f380d6 100755 --- a/test/functional/feature_minchainwork.py +++ b/test/functional/feature_minchainwork.py @@ -110,7 +110,7 @@ def run_test(self): self.stop_node(0) self.nodes[0].assert_start_raises_init_error( ["-minimumchainwork=test"], - expected_msg='Error: Invalid non-hex (test) minimum chain work value specified', + expected_msg='Error: Invalid minimum work specified (test), must be up to 64 hex digits', )