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',
)