#35557 kernel, validation: Add btck_chainstate_manager_set_clock_time

ReviewabilityReviewsAgreementSize
#35557 ryanofsky kernel, validation: Add btck_chainstate_manager_set_clock_timeReady0Mild171 + 86 tests
  • Allow kernel library callers and tests to set clock time per instance rather than through global state
  • Eliminate intermittent test flakes caused by race conditions across second boundaries
  • Ready to review
  • No review verdicts recorded
  • Unaddressed objection: PR may be obsolete after #35496 and conflicts with approach in #35906 (maflcko)
  • Prefers setter on chainstate over options, but questions whether it is worth the effort (sedited)
  • Ergonomics concern about managing clocks per object was answered with an alternative prototype (stickies-v)
  • 171 lines added or modified outside tests
  • 86 lines added or modified in tests
  • 99 lines removed in total
  • 31 files, 6 commits
Kernel (libbitcoinkernel)#9 of 18
P2 · new feature
  • P2 because it lets concurrent kernel instances set mock time independently without shared global state
  • Enforces kernel boundaries by catching accidental nondeterministic clock usage at link time
Validation#45 of 53
P3 · cleanup
  • P3 because scoped time checks prevent second-boundary race conditions in isolated validation tests
  • The impact is limited to testing safety rather than fixing production consensus issues
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

Introduces `ChainstateManager::Now()` and a C/C++ API `btck_chainstate_manager_set_clock_time` / `SetClockTime` to allow kernel consumers and tests to set and query clock time on a per-chainstate-manager instance. Time-sensitive validation operations—including header future-time validation, IBD status evaluation (`IsTipRecent`), header pre-sync logging, and verification progress—are updated to read time through `ChainstateManager`. The PR also standardizes mempool time on `NodeClock::time_point` (`MempoolTime`) and segregates `NodeClock::now()` into a separate translation unit omitted from `libbitcoinkernel` to catch accidental nondeterministic clock usage at link time.

Problem: Validation logic in `libbitcoinkernel` directly queried system time via process-global clock functions, preventing kernel library callers and tests from driving validation deterministically across multiple instances. Furthermore, uncoordinated queries to `NodeClock::now()` caused race conditions across second boundaries in tests like `chainstatemanager_ibd_exit_after_loading_blocks`.

Evidence of importance:

  • Commit 79c304eacd explains an intermittent test failure: "The test set up the tip time using Now<NodeSeconds>() and then called UpdateIBDStatus(), which calls IsTipRecent(), which also calls Now<NodeSeconds>(). If a second boundary was crossed between the two calls, the tip_recent=true case would fail spuriously."
  • The PR description notes this provides an instance-scoped alternative to #35496 (which added global `btck_set_mock_time`), eliminating global state sharing between concurrent kernel instances.

Scope: The diff includes refactoring mempool time types from integers to `NodeClock::time_point` and adjusting link targets to disallow `NodeClock::now()` in `libbitcoinkernel`, which spans beyond `ChainstateManager` itself.

Risk of a mistake: medium (validation, consensus). A flaw in clock routing or time casting could alter block header future-time consensus checks or disrupt IBD latch transitions, potentially causing valid blocks to be rejected or nodes to stay in IBD indefinitely.

Changed since the previous assessment: Adapted the `utxo_total_supply` fuzz harness and `chainstatemanager_ibd_exit_after_loading_blocks` unit test to directly exercise the per-chainman clock instead of relying on global mock clocks.

Reviewability

Ready

Nothing known that would invalidate a review now.

Waiting on (from the thread read):

  • author: clarify relevance and approach following merge of #35496 and feedback from maflcko and sedited 2026-08-17

Agreement

Mild

objection that needs an answer has none yet (maflcko)

Thread notes: Statement r:4588310492 was edited with additional rebase announcements up to pr/kclock.11, but does not answer the open questions on relevance or approach from maflcko and sedited.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
maflcko (member)suggestion, styleUse tip->Time() to avoid casting and let the compiler do chrono type checksfixed · fix pushed · applied by authoransweredyes2026-06-18: 'You can use `tip->Time()`, to avoid the cast here.'
Status from: 2026-06-18: ryanofsky: 'Thanks! That is so much better and I applied this many places'
maflcko (member)suggestion, maintenanceAvoid casting time_point to duration and drop helper promoting DurationSinceEpochfixed · fix pushed · helper dropped by authoransweredyes2026-06-18: 'At call sites you can just replace block->GetBlockTime() with block->Time() to avoid any casts, and this helper.'
Status from: 2026-06-18: ryanofsky: 'You're right, dropped this now'
maflcko (member)suggestion, maintenanceRemove the cast in ChainstateManager::Now() and let call sites upgrade to sub-second precisionfixed · fix pushed · cast dropped by authoransweredyes2026-06-18: 'An alternative could be to remove the cast here and only add the cast in call sites that need it'
Status from: 2026-06-18: ryanofsky: 'It's definitely simpler not to have this and it is dropped now.'
maflcko (member)objection, correctnessdirect GetMockTime call in tx_pool test accesses a test-only global that may be 0, potentially causing silent bugsfixed · fix pushed · author switched from GetMockTime() to Now()changedyes2026-06-18: 'It directly access the test-only global of the mock time, which may be 0, and thus may lead to silent bugs if mocktime was not used here?'
Status from: 2026-06-18: ryanofsky: 'Makes sense, switched GetMockTime() -> Now()'
maflcko (member)objection, safetycalling GetMockTime directly sidesteps the g_used_system_time runtime sanitizerfixed · fix pushed · author switched from GetMockTime() to Now()changedyes2026-06-18: 'It side-steps the g_used_system_time runtime sanitizer.'
Status from: 2026-06-18: ryanofsky: 'Makes sense, switched GetMockTime() -> Now()'
maflcko (member)objection, correctnessGetMockTime returns a duration instead of a time pointfixed · fix pushed · author switched from GetMockTime() to Now()changedyes2026-06-18: 'It returns the wrong type (it should be a time point, not a duration)'
Status from: 2026-06-18: ryanofsky: 'Makes sense, switched GetMockTime() -> Now()'
sedited (member)suggestion, correctnessexercise the new chainman-scoped clock time in testsfixed · fix pushed · author added test coverage in pr/kclock.6changedyes2026-06-21: 'Can we use this new clock in one of the tests (maybe one of the utxo* fuzz tests, haven't looked too closely yet).'
Status from: 2026-06-29: ryanofsky: 'Thanks, kept all commits for now and added new tests as suggested.'
stickies-v (member)objection, interfacerequiring users to manage clock time across multiple separate places is cumbersome and error-proneanswered · author replied that independent objects may not want shared time and tested callback alternative in a commitansweredyes2026-06-23: 'This could get a bit annoying and potentially dangerous when users need to start managing clocks across multiple places.'
Status from: 2026-06-29: ryanofsky: 'My instinct is that it doesn't because if you're creating multiple chainstate managers or other objects that accept time'
stickies-v (member)suggestion, interfaceAdd a btck_Clock callback abstraction that can be passed to multiple objects and updated in one placeanswered · author implemented a prototype commit and discussed tradeoffsansweredyes2026-06-23: 'Perhaps a more ergonomic alternative could be to add a `btck_Clock` type which basically is just a callback that returns the current time?'
Status from: 2026-06-29: ryanofsky: 'Implemented in 3d513c5d03ceb8d33dc8e19c07149e3337c64ac6 to see what it would look like.'
seduless (contributor)suggestion, maintenanceuse the FakeNodeClock RAII helper instead of bare SetMockTime callsfixed · fix pushed · author applied the patch using FakeNodeClockchangedyes2026-06-25: 'Instead of bare SetMockTime calls, this is a good candidate for the RAII FakeNodeClock.'
Status from: 2026-06-29: ryanofsky: 'Thanks! Applied your patch and switched to FakeNodeClock'
maflcko (member)suggestion, usefulnessclarify whether the PR is still relevant following the merge of PR 35496openansweredno2026-08-17: 'That one was merged, so is this here still relevant?'
maflcko (member)objection, approachthe PR conflicts conceptually with the approach discussed in PR 35906openansweredno2026-08-17: 'Also, this conflicts with https://github.com/bitcoin/bitcoin/pull/35906#discussion_r3726539086'
sedited (member)suggestion, usefulnessevaluate whether the PR is worth pursuing or if stringintech should weigh inopenansweredno2026-10-26: 'not sure if this is worth the squeeze. Maybe @stringintech can chime in?'

Support:

  • sedited (member) (2026-06-21) [read]: prefers setting clock time on chainman over setting it in options because it can be updated repeatedly and matches current global mocktime mechanics

Participants:

  • maflcko (member, objection, 5 statements 2026-06-18 to 2026-08-17): objected to GetMockTime in fuzz tests, questioned PR relevance after #35496 merged, and cited conflict with #35906
  • sedited (member, support, 2 statements 2026-06-21 to 2026-10-26): supported setter approach over options approach, but later questioned whether the PR is worth the squeeze
  • stickies-v (member, objection, 1 statements 2026-06-23 to 2026-06-23): raised ergonomics concerns with per-object clock setting and suggested a callback clock abstraction
  • seduless (contributor, neutral, 1 statements 2026-06-25 to 2026-06-25): suggested using FakeNodeClock RAII helper in chainstatemanager tests

Files

126 lines under test/bench/ci.

  • src/validation.cpp +29/-29
  • src/test/kernel/test_kernel.cpp +35/-0
  • src/test/rbf_tests.cpp +17/-17
  • src/kernel/bitcoinkernel.h +28/-0
  • src/txmempool.h +19/-5
  • src/validation.h +21/-1
  • src/txmempool.cpp +8/-8
  • src/util/time.h +13/-3
  • src/util/time_nondet.cpp +14/-0
  • src/node/mempool_persist.cpp +9/-2
  • src/test/fuzz/tx_pool.cpp +5/-5
  • src/test/validation_chainstatemanager_tests.cpp +9/-1
  • src/kernel/bitcoinkernel.cpp +9/-0
  • src/test/fuzz/utxo_total_supply.cpp +6/-3
  • src/kernel/mempool_entry.h +5/-3
  • src/kernel/bitcoinkernel_wrapper.h +7/-0
  • src/chain.h +4/-2
  • src/test/fuzz/package_eval.cpp +3/-3
  • src/test/util/txmempool.cpp +3/-3
  • src/util/time.cpp +2/-4
  • src/bench/blockencodings.cpp +1/-1
  • src/bench/mempool_ephemeral_spends.cpp +1/-1
  • src/bench/mempool_eviction.cpp +1/-1
  • src/bench/mempool_stress.cpp +1/-1
  • src/bench/rpc_mempool.cpp +1/-1
  • src/node/chainstate.cpp +1/-1
  • src/rpc/mempool.cpp +1/-1
  • src/test/fuzz/rbf.cpp +1/-1
  • src/test/fuzz/util/mempool.cpp +1/-1
  • src/test/util/setup_common.cpp +1/-1
  • src/util/CMakeLists.txt +1/-0

Card

The short text the ranking pass compares across PRs.

This pull request adds an instance-scoped clock override API (`btck_chainstate_manager_set_clock_time`) and routes validation time checks through `ChainstateManager::Now()`. This eliminates the kernel's reliance on host system clocks and process-global mock time, enabling independent, deterministic multi-instance validation while fixing an intermittent unit test race across second boundaries. It also converts mempool time representations to type-safe time points and enforces clock determinism by preventing `libbitcoinkernel` from linking against `NodeClock::now()`. No dependencies are required.

Data

dossier JSON · extract JSON · model ledger, generated 2026-11-01T01:16, confidence high, input hash 6c871eb58e8751fc
thread read through 2026-10-26 (18 statements), head 0e777d8b30
labels: Validation, Kernel

Categories

Kernel (libbitcoinkernel)

P2 · new feature

Adds per-instance clock time querying and injection to the C/C++ kernel API, replacing global mocktime to support deterministic concurrent kernel instances. This advances the kernel boundary by removing direct node clock dependencies and catching nondeterministic clock usage at link time.

Membership: Has the Kernel label and adds `btck_chainstate_manager_set_clock_time` and `SetClockTime` to `src/kernel/bitcoinkernel.h` and `bitcoinkernel.cpp`.

Factors: security/stability 0, bug 1, performance 0, user value 0, leverage 1

Validation

P3 · cleanup

Routes header future-time validation, IBD evaluation, and progress calculation through `ChainstateManager::Now()`, eliminating global system time queries. This makes validation logic safer to test in isolation and resolves a spurious second-boundary race condition in `chainstatemanager_ibd_exit_after_loading_blocks`.

Membership: Has the Validation label and updates validation time checks across `src/validation.cpp` and `src/validation.h` to query clock time via `ChainstateManager`.

Factors: security/stability 0, bug 1, performance 0, user value 0, leverage 1