fuzz: Return chrono point from ConsumeTime(), Add ConsumeDuration() #34337

pull maflcko wants to merge 2 commits into bitcoin:master from maflcko:2601-fuzz-chrono changing 5 files +15 −11
  1. maflcko commented at 11:38 am on January 19, 2026: member

    Returning a raw i64 is a bit confusing when it comes to chrono types. For example, in the addrman fuzz tests, the time_penalty is not a time point, but a duration.

    Also, all call-sites assume second resolution right now, so document that better by returning NodeSeconds from ConsumeTime(...) and std::chrono::seconds from ConsumeDuration(...).

  2. fuzz: Use min option in ConsumeTime
    This is less code and also required for the next commit.
    faa5a9ebad
  3. DrahtBot renamed this:
    fuzz: Return chrono point from ConsumeTime(), Add ConsumeDuration()
    fuzz: Return chrono point from ConsumeTime(), Add ConsumeDuration()
    on Jan 19, 2026
  4. DrahtBot added the label Fuzzing on Jan 19, 2026
  5. DrahtBot commented at 11:39 am on January 19, 2026: contributor

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

    Code Coverage & Benchmarks

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

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK l0rinc, Crypt-iQ

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #32430 (test: Add and use ElapseTime helper by maflcko)

    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. maflcko force-pushed on Jan 19, 2026
  7. DrahtBot added the label CI failed on Jan 19, 2026
  8. DrahtBot removed the label CI failed on Jan 19, 2026
  9. maflcko requested review from l0rinc on Jan 21, 2026
  10. in src/test/fuzz/headerssync.cpp:62 in faa5a9ebad outdated
    60     CBlockHeader genesis_header{Params().GenesisBlock()};
    61     CBlockIndex start_index(genesis_header);
    62 
    63-    if (mock_time < start_index.GetMedianTimePast()) return;
    64-    SetMockTime(mock_time);
    65+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
    


    l0rinc commented at 12:33 pm on January 23, 2026:
    nit: we could add @hodlinator as a coauthor here, see #32430 (review)

    maflcko commented at 2:59 pm on January 23, 2026:
    i think i wrote this without seeing the other pull/commit. Do you have a link to it? i don’t want to add someone as co-author without them asking for it.

    l0rinc commented at 3:43 pm on January 23, 2026:
    will let him comment on it

    hodlinator commented at 9:53 pm on January 26, 2026:
    Thanks for the mention @l0rinc, I’m fine without the mention regardless of parallel discovery or not. Good to see it improved. 👍
  11. in src/test/fuzz/util.cpp:38 in fa33ca66b2 outdated
    30@@ -31,12 +31,17 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option
    31     return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, max.value_or(MAX_MONEY));
    32 }
    33 
    34-int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
    35+NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
    36 {
    37     // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime.
    38     static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z").value()};
    39     static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z").value()};
    


    l0rinc commented at 12:39 pm on January 23, 2026:

    We could avoid specifying the max in https://github.com/bitcoin/bitcoin/blob/fa5f29774872d18febc0df38831a6e45f3de69cc/src/test/fuzz/tx_pool.cpp#L145 and https://github.com/bitcoin/bitcoin/blob/fa5f29774872d18febc0df38831a6e45f3de69cc/src/test/fuzz/package_eval.cpp#L113 (which is bigger than this max which may be a bit confusing)

    0    const auto time = ConsumeTime(fuzzed_data_provider, /*min=*/(chainstate.m_chain.Tip()->GetMedianTimePast() + 1) * 1s);
    

    (to avoid the the duplication of MockTime in both cases we could move it into mempool.h here or in a follow-up)


    maflcko commented at 2:59 pm on January 23, 2026:
    i think it is fine for tests to set a higher max, if they want.

    l0rinc commented at 3:43 pm on January 23, 2026:
    We can also do the deduplication of MockTime in a follow-up
  12. in src/test/fuzz/util.cpp:34 in fa33ca66b2 outdated
    30@@ -31,12 +31,17 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option
    31     return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, max.value_or(MAX_MONEY));
    32 }
    33 
    34-int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
    35+NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
    


    l0rinc commented at 12:43 pm on January 23, 2026:

    Since the extra min/max parameters are rarely used, we could transition these to native chrono types, which would enable us using the new duration consumer here:

    0return NodeSeconds{ConsumeDuration(fuzzed_data_provider, min.value_or(time_min), max.value_or(time_max))};
    

    or if we keep the types:

    0return NodeSeconds{ConsumeDuration(fuzzed_data_provider, min.value_or(time_min) * 1s, max.value_or(time_max) * 1s)};
    

    maflcko commented at 2:59 pm on January 23, 2026:
    thx, done

    l0rinc commented at 3:42 pm on January 23, 2026:
    we can do the const std::optional<std::chrono::seconds>& min, const std::optional<std::chrono::seconds>& max in a follow-up
  13. in src/test/fuzz/addrman.cpp:147 in fa33ca66b2 outdated
    143@@ -144,23 +144,23 @@ FUZZ_TARGET(addrman, .init = initialize_addrman)
    144                     addresses.push_back(ConsumeAddress(fuzzed_data_provider));
    145                 }
    146                 auto net_addr = ConsumeNetAddr(fuzzed_data_provider);
    147-                auto time_penalty = std::chrono::seconds{ConsumeTime(fuzzed_data_provider, 0, 100000000)};
    148+                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/100000000s);
    


    l0rinc commented at 12:48 pm on January 23, 2026:

    This is a lot less noisy than before! 👍 Please consider formatting the huge number a bit better:

    0                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/100'000'000s);
    

    or expressing it in a different unit that roughly the same:

    0                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/1000 * 24h);
    

    (turns out the 1000d is not a duration)


    maflcko commented at 2:59 pm on January 23, 2026:
    i think the numbers don’t matter and do not need to have ' in it. i think someone was just holding the 0 key for a short time.
  14. in src/test/fuzz/p2p_headers_presync.cpp:124 in fa33ca66b2 outdated
    120@@ -121,7 +121,7 @@ CBlockHeader ConsumeHeader(FuzzedDataProvider& fuzzed_data_provider, const uint2
    121         arith_uint256 target = ConsumeArithUInt256InRange(fuzzed_data_provider, lower_target, upper_target);
    122         header.nBits = target.GetCompact();
    123     }
    124-    header.nTime = ConsumeTime(fuzzed_data_provider);
    125+    header.nTime = TicksSinceEpoch<std::chrono::seconds>(ConsumeTime(fuzzed_data_provider));
    


    l0rinc commented at 12:51 pm on January 23, 2026:
    While this is more verbose, it documents it clearly that CBlockHeader::nTime is “seconds since epoch” and not just “Time”
  15. in src/test/fuzz/util.cpp:44 in fa33ca66b2 outdated
    41+    return NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max))}};
    42+}
    43+
    44+std::chrono::seconds ConsumeDuration(FuzzedDataProvider& fuzzed_data_provider, std::chrono::seconds min, std::chrono::seconds max) noexcept
    45+{
    46+    return std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count())};
    


    l0rinc commented at 12:52 pm on January 23, 2026:

    These chrono type conversions are ridiculously verbose for such an small functionality, please consider using something like this in the PR:

    0    return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count()) * 1s;
    

    maflcko commented at 2:59 pm on January 23, 2026:
    thx, done
  16. l0rinc approved
  17. l0rinc commented at 1:33 pm on January 23, 2026: contributor

    Concept ACK, fixes primitive obsession with the new std::chrono helpers for better type safety. I know that we have to stop refactoring somewhere - and I don’t want to endlessly extend it either -, but I think we can take another step and make the old method more type safe not just in its return value but the args as well - which would clean up a few remaining call sites as a bonus.

      0diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp
      1index 88260b272f..57799ba9ec 100644
      2--- a/src/test/fuzz/addrman.cpp
      3+++ b/src/test/fuzz/addrman.cpp
      4@@ -144,7 +144,7 @@ FUZZ_TARGET(addrman, .init = initialize_addrman)
      5                     addresses.push_back(ConsumeAddress(fuzzed_data_provider));
      6                 }
      7                 auto net_addr = ConsumeNetAddr(fuzzed_data_provider);
      8-                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/100000000s);
      9+                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/1000 * 24h);
     10                 addr_man.Add(addresses, net_addr, time_penalty);
     11             },
     12             [&] {
     13diff --git a/src/test/fuzz/headerssync.cpp b/src/test/fuzz/headerssync.cpp
     14index f6e574f404..201a94cbeb 100644
     15--- a/src/test/fuzz/headerssync.cpp
     16+++ b/src/test/fuzz/headerssync.cpp
     17@@ -59,7 +59,7 @@ FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
     18     CBlockHeader genesis_header{Params().GenesisBlock()};
     19     CBlockIndex start_index(genesis_header);
     20 
     21-    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
     22+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast() * 1s));
     23 
     24     const uint256 genesis_hash = genesis_header.GetHash();
     25     start_index.phashBlock = &genesis_hash;
     26diff --git a/src/test/fuzz/package_eval.cpp b/src/test/fuzz/package_eval.cpp
     27index 1cc2caa396..7ed729365e 100644
     28--- a/src/test/fuzz/package_eval.cpp
     29+++ b/src/test/fuzz/package_eval.cpp
     30@@ -106,14 +106,6 @@ struct TransactionsDelta final : public CValidationInterface {
     31     }
     32 };
     33 
     34-void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
     35-{
     36-    const auto time = ConsumeTime(fuzzed_data_provider,
     37-                                  chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
     38-                                  std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
     39-    SetMockTime(time);
     40-}
     41-
     42 std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
     43 {
     44     // Take the default options for tests...
     45diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
     46index f70dd710b3..82a77461a7 100644
     47--- a/src/test/fuzz/tx_pool.cpp
     48+++ b/src/test/fuzz/tx_pool.cpp
     49@@ -138,14 +138,6 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
     50     g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
     51 }
     52 
     53-void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
     54-{
     55-    const auto time = ConsumeTime(fuzzed_data_provider,
     56-                                  chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
     57-                                  std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
     58-    SetMockTime(time);
     59-}
     60-
     61 std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
     62 {
     63     // Take the default options for tests...
     64diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
     65index 3b3af16115..fe8f37bbf7 100644
     66--- a/src/test/fuzz/util.cpp
     67+++ b/src/test/fuzz/util.cpp
     68@@ -31,17 +31,17 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option
     69     return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, max.value_or(MAX_MONEY));
     70 }
     71 
     72-NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
     73+NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::chrono::seconds>& min, const std::optional<std::chrono::seconds>& max) noexcept
     74 {
     75     // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime.
     76-    static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z").value()};
     77-    static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z").value()};
     78-    return NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max))}};
     79+    static constexpr auto time_min{(std::chrono::sys_days{2000y / 1 / 1} + 1s).time_since_epoch()};
     80+    static constexpr auto time_max{(std::chrono::sys_days{2101y / 1 / 1} - 1s).time_since_epoch()};
     81+    return NodeSeconds{ConsumeDuration(fuzzed_data_provider, min.value_or(time_min), max.value_or(time_max))};
     82 }
     83 
     84 std::chrono::seconds ConsumeDuration(FuzzedDataProvider& fuzzed_data_provider, std::chrono::seconds min, std::chrono::seconds max) noexcept
     85 {
     86-    return std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count())};
     87+    return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count()) * 1s;
     88 }
     89 
     90 CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<Txid>>& prevout_txids, const int max_num_in, const int max_num_out) noexcept
     91diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
     92index 3947ee12a6..0f87d1d57d 100644
     93--- a/src/test/fuzz/util.h
     94+++ b/src/test/fuzz/util.h
     95@@ -20,6 +20,7 @@
     96 #include <test/fuzz/FuzzedDataProvider.h>
     97 #include <test/fuzz/fuzz.h>
     98 #include <uint256.h>
     99+#include <util/time.h>
    100 
    101 #include <algorithm>
    102 #include <array>
    103@@ -144,7 +145,7 @@ template <typename WeakEnumType, size_t size>
    104 
    105 [[nodiscard]] CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::optional<CAmount>& max = std::nullopt) noexcept;
    106 
    107-[[nodiscard]] NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min = std::nullopt, const std::optional<int64_t>& max = std::nullopt) noexcept;
    108+[[nodiscard]] NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::chrono::seconds>& min = std::nullopt, const std::optional<std::chrono::seconds>& max = std::nullopt) noexcept;
    109 [[nodiscard]] std::chrono::seconds ConsumeDuration(FuzzedDataProvider& fuzzed_data_provider, std::chrono::seconds min, std::chrono::seconds max) noexcept;
    110 
    111 [[nodiscard]] CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<Txid>>& prevout_txids, int max_num_in = 10, int max_num_out = 10) noexcept;
    112diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp
    113index 241a4d559c..3ab4314615 100644
    114--- a/src/test/fuzz/util/mempool.cpp
    115+++ b/src/test/fuzz/util/mempool.cpp
    116@@ -14,6 +14,11 @@
    117 #include <cstdint>
    118 #include <limits>
    119 
    120+void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) noexcept
    121+{
    122+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/(chainstate.m_chain.Tip()->GetMedianTimePast() + 1) * 1s));
    123+}
    124+
    125 CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx, uint32_t max_height) noexcept
    126 {
    127     // Avoid:
    128diff --git a/src/test/fuzz/util/mempool.h b/src/test/fuzz/util/mempool.h
    129index 948e936c75..308a38251d 100644
    130--- a/src/test/fuzz/util/mempool.h
    131+++ b/src/test/fuzz/util/mempool.h
    132@@ -21,6 +21,8 @@ public:
    133     }
    134 };
    135 
    136+void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) noexcept;
    137+
    138 [[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx, uint32_t max_height=std::numeric_limits<uint32_t>::max()) noexcept;
    139 
    140 #endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H
    141diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
    142index dddc67a41b..3a87a45f4f 100644
    143--- a/src/test/fuzz/utxo_snapshot.cpp
    144+++ b/src/test/fuzz/utxo_snapshot.cpp
    145@@ -103,7 +103,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
    146 {
    147     SeedRandomStateForTest(SeedRand::ZEROS);
    148     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
    149-    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
    150+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1'296'688'602s)); // regtest genesis block timestamp
    151     auto& setup{*g_setup};
    152     bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
    153     auto& chainman{*setup.m_node.chainman};
    154diff --git a/src/test/fuzz/utxo_total_supply.cpp b/src/test/fuzz/utxo_total_supply.cpp
    155index d27ca3470b..e7929f4571 100644
    156--- a/src/test/fuzz/utxo_total_supply.cpp
    157+++ b/src/test/fuzz/utxo_total_supply.cpp
    158@@ -24,7 +24,7 @@ FUZZ_TARGET(utxo_total_supply)
    159 {
    160     SeedRandomStateForTest(SeedRand::ZEROS);
    161     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
    162-    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
    163+    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1'296'688'602s)); // regtest genesis block timestamp
    164     /** The testing setup that creates a chainman only (no chainstate) */
    165     ChainTestingSetup test_setup{
    166         ChainType::REGTEST,
    
  18. maflcko force-pushed on Jan 23, 2026
  19. fuzz: Return chrono point from ConsumeTime(), Add ConsumeDuration()
    A chrono time point is a bit more type-safe than a raw i64.
    
    Also, add a dedicated helper for plain chrono durations.
    eeee3755f8
  20. maflcko force-pushed on Jan 23, 2026
  21. DrahtBot added the label CI failed on Jan 23, 2026
  22. l0rinc commented at 3:43 pm on January 23, 2026: contributor
    ACK eeee3755f8c415b227820479b5492261f3a8aa08
  23. DrahtBot removed the label CI failed on Jan 23, 2026
  24. Crypt-iQ commented at 5:08 pm on January 23, 2026: contributor
    crACK eeee3755f8c415b227820479b5492261f3a8aa08
  25. maflcko commented at 11:08 am on January 26, 2026: member
    simple fuzz-only change with two acks, rfm?
  26. fanquake merged this on Jan 26, 2026
  27. fanquake closed this on Jan 26, 2026

  28. maflcko deleted the branch on Jan 26, 2026

github-metadata-mirror

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

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