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

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    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 <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  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)

        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:

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

    or if we keep the types:

    return 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:

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

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

                    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:

        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.

    <details><summary>changes done during code review</summary>

    diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp
    index 88260b272f..57799ba9ec 100644
    --- a/src/test/fuzz/addrman.cpp
    +++ b/src/test/fuzz/addrman.cpp
    @@ -144,7 +144,7 @@ FUZZ_TARGET(addrman, .init = initialize_addrman)
                         addresses.push_back(ConsumeAddress(fuzzed_data_provider));
                     }
                     auto net_addr = ConsumeNetAddr(fuzzed_data_provider);
    -                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/100000000s);
    +                auto time_penalty = ConsumeDuration(fuzzed_data_provider, /*min=*/0s, /*max=*/1000 * 24h);
                     addr_man.Add(addresses, net_addr, time_penalty);
                 },
                 [&] {
    diff --git a/src/test/fuzz/headerssync.cpp b/src/test/fuzz/headerssync.cpp
    index f6e574f404..201a94cbeb 100644
    --- a/src/test/fuzz/headerssync.cpp
    +++ b/src/test/fuzz/headerssync.cpp
    @@ -59,7 +59,7 @@ FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz)
         CBlockHeader genesis_header{Params().GenesisBlock()};
         CBlockIndex start_index(genesis_header);
     
    -    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast()));
    +    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast() * 1s));
     
         const uint256 genesis_hash = genesis_header.GetHash();
         start_index.phashBlock = &genesis_hash;
    diff --git a/src/test/fuzz/package_eval.cpp b/src/test/fuzz/package_eval.cpp
    index 1cc2caa396..7ed729365e 100644
    --- a/src/test/fuzz/package_eval.cpp
    +++ b/src/test/fuzz/package_eval.cpp
    @@ -106,14 +106,6 @@ struct TransactionsDelta final : public CValidationInterface {
         }
     };
     
    -void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
    -{
    -    const auto time = ConsumeTime(fuzzed_data_provider,
    -                                  chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
    -                                  std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
    -    SetMockTime(time);
    -}
    -
     std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
     {
         // Take the default options for tests...
    diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
    index f70dd710b3..82a77461a7 100644
    --- a/src/test/fuzz/tx_pool.cpp
    +++ b/src/test/fuzz/tx_pool.cpp
    @@ -138,14 +138,6 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
         g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
     }
     
    -void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)
    -{
    -    const auto time = ConsumeTime(fuzzed_data_provider,
    -                                  chainstate.m_chain.Tip()->GetMedianTimePast() + 1,
    -                                  std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());
    -    SetMockTime(time);
    -}
    -
     std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)
     {
         // Take the default options for tests...
    diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
    index 3b3af16115..fe8f37bbf7 100644
    --- a/src/test/fuzz/util.cpp
    +++ b/src/test/fuzz/util.cpp
    @@ -31,17 +31,17 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option
         return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, max.value_or(MAX_MONEY));
     }
     
    -NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept
    +NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::chrono::seconds>& min, const std::optional<std::chrono::seconds>& max) noexcept
     {
         // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime.
    -    static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z").value()};
    -    static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z").value()};
    -    return NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max))}};
    +    static constexpr auto time_min{(std::chrono::sys_days{2000y / 1 / 1} + 1s).time_since_epoch()};
    +    static constexpr auto time_max{(std::chrono::sys_days{2101y / 1 / 1} - 1s).time_since_epoch()};
    +    return NodeSeconds{ConsumeDuration(fuzzed_data_provider, min.value_or(time_min), max.value_or(time_max))};
     }
     
     std::chrono::seconds ConsumeDuration(FuzzedDataProvider& fuzzed_data_provider, std::chrono::seconds min, std::chrono::seconds max) noexcept
     {
    -    return std::chrono::seconds{fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count())};
    +    return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.count(), max.count()) * 1s;
     }
     
     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
    diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
    index 3947ee12a6..0f87d1d57d 100644
    --- a/src/test/fuzz/util.h
    +++ b/src/test/fuzz/util.h
    @@ -20,6 +20,7 @@
     #include <test/fuzz/FuzzedDataProvider.h>
     #include <test/fuzz/fuzz.h>
     #include <uint256.h>
    +#include <util/time.h>
     
     #include <algorithm>
     #include <array>
    @@ -144,7 +145,7 @@ template <typename WeakEnumType, size_t size>
     
     [[nodiscard]] CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::optional<CAmount>& max = std::nullopt) noexcept;
     
    -[[nodiscard]] NodeSeconds ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min = std::nullopt, const std::optional<int64_t>& max = std::nullopt) noexcept;
    +[[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;
     [[nodiscard]] std::chrono::seconds ConsumeDuration(FuzzedDataProvider& fuzzed_data_provider, std::chrono::seconds min, std::chrono::seconds max) noexcept;
     
     [[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;
    diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp
    index 241a4d559c..3ab4314615 100644
    --- a/src/test/fuzz/util/mempool.cpp
    +++ b/src/test/fuzz/util/mempool.cpp
    @@ -14,6 +14,11 @@
     #include <cstdint>
     #include <limits>
     
    +void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) noexcept
    +{
    +    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/(chainstate.m_chain.Tip()->GetMedianTimePast() + 1) * 1s));
    +}
    +
     CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx, uint32_t max_height) noexcept
     {
         // Avoid:
    diff --git a/src/test/fuzz/util/mempool.h b/src/test/fuzz/util/mempool.h
    index 948e936c75..308a38251d 100644
    --- a/src/test/fuzz/util/mempool.h
    +++ b/src/test/fuzz/util/mempool.h
    @@ -21,6 +21,8 @@ public:
         }
     };
     
    +void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) noexcept;
    +
     [[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx, uint32_t max_height=std::numeric_limits<uint32_t>::max()) noexcept;
     
     #endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H
    diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
    index dddc67a41b..3a87a45f4f 100644
    --- a/src/test/fuzz/utxo_snapshot.cpp
    +++ b/src/test/fuzz/utxo_snapshot.cpp
    @@ -103,7 +103,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
     {
         SeedRandomStateForTest(SeedRand::ZEROS);
         FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
    -    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
    +    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1'296'688'602s)); // regtest genesis block timestamp
         auto& setup{*g_setup};
         bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
         auto& chainman{*setup.m_node.chainman};
    diff --git a/src/test/fuzz/utxo_total_supply.cpp b/src/test/fuzz/utxo_total_supply.cpp
    index d27ca3470b..e7929f4571 100644
    --- a/src/test/fuzz/utxo_total_supply.cpp
    +++ b/src/test/fuzz/utxo_total_supply.cpp
    @@ -24,7 +24,7 @@ FUZZ_TARGET(utxo_total_supply)
     {
         SeedRandomStateForTest(SeedRand::ZEROS);
         FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
    -    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)); // regtest genesis block timestamp
    +    SetMockTime(ConsumeTime(fuzzed_data_provider, /*min=*/1'296'688'602s)); // regtest genesis block timestamp
         /** The testing setup that creates a chainman only (no chainstate) */
         ChainTestingSetup test_setup{
             ChainType::REGTEST,
    

    </details

  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-05-15 03:12 UTC

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