Skip to content

tests: pin the pm_lifecycle clock instead of starting at now() - #212

Merged
On1x merged 1 commit into
masterfrom
fix/pm-lifecycle-hf16-vote
Oct 30, 2026
Merged

On1x merged 1 commit into
masterfrom
fix/pm-lifecycle-hf16-vote

Conversation

@On1x

@On1x On1x commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

What and why

pm_lifecycle_suite starts every case at fc::time_point::now(), and the harness takes genesis from the same clock (init_genesis in database.cpp). So these 72 cases silently change the rules they run under on the day a date-gated hardfork activates.

HF16 (CHAIN_HARDFORK_16_TIME, 2026-10-31T00:00:00Z) requires a QC over the parent on every block, exempting only the activation block's child (block_needs_parent_qc, round-anchor clause). A chain whose first block is already past the fork date therefore dies at block 3 with No QC for head #2, before any case tests what it is about:

12:34:29  apply_hardfork ] HARDFORK 16 at block 13712
fatal error: pm_lifecycle_suite/leverage_profit_close_never_mints:
  cert.valid(): No QC for head #13713 yet: cannot hand over the turn

That failure was run 41054140379 (PR #211, which only touches share/vizd/vizd.sh). The long leverage cases were the first to cross the date inside one run — the same master commit was green at 08:21 UTC and red at 12:34 UTC. From 00:00 UTC tonight it is not one case and not two jobs: all 72 cases, in all three consensus_sim jobs.

Change

Pin the case clock two days before HF16 — after HF15, before HF16, i.e. exactly the rules that were live on 2026-10-30. Two days of headroom for the longest case (~11 h 26 min of virtual time); both bounds are compile-time constants, so the pair stays ordered forever. One helper plus a mechanical replacement of the 72 start lines.

This is the cure test_pm_audit_fixes.cpp already applied to its own time bomb (its header says so). test_agent_access.cpp pins the same way.

Why not "vote for head"

Voting, as the HF16-aware scenarios do, is not enough here: after the fork the slot shortens to CHAIN_BLOCK_INTERVAL_HF16 (1 s) and _generate_block asserts the producer is the validator scheduled for the slot of when. This file's block-count arithmetic (blocks = (grace + 240) / CHAIN_BLOCK_INTERVAL + 80 and friends) is written against 3 s blocks, so a run that legitimately crossed the fork would under-cover its deadlines. Pinning keeps the file honest: it tests PM lifecycle under pre-HF16 rules, and HF16 itself is covered by test_hf16_boundary.cpp / test_hf16_production_safety.cpp.

Scope check

Four scenario files take the real wall clock; the other three vote for head already or pin (see above), and the rest start at 2026-01-01 or epoch. This file was the only one that both starts at now() and never votes.

Verification

  • single-TU -fsyntax-only (testnet defines, nice -n 19 ionice -c3): clean.
  • CI on this PR is the positive control: the same hour that reproduced the failure must be green here. Note that before 00:00 UTC CI exercises the pre-fork path this fix pins to — the decisive check is the first post-fork run on master, where No QC for head must not appear.

Merge is the owner's, as usual.

The harness takes genesis from fc::time_point::now(), so every case in
pm_lifecycle_suite silently changed the rules it ran under on the day a
date-gated hardfork activated. HF16 (2026-10-31T00:00:00Z) requires a QC over
the parent on every block, exempting only the activation block's child: a
chain whose first block is past the fork date dies at block 3 with
"No QC for head #2", before any of the 72 cases tests what it is about.

Yesterday's failure in run 41054140379 was this class: the long leverage cases
crossed the date inside one run (HARDFORK 16 at block 13712), so the same
master commit was green in the morning and red at noon.

Pin the case clock two days before HF16 — after HF15, before HF16, i.e. the
rules that were live on 2026-10-30. Two days leave room for the longest case
(~11 h 26 min of virtual time), and both bounds are compile-time constants, so
the pair stays ordered forever. This is the same cure test_pm_audit_fixes.cpp
already applied to its own time bomb.

Voting for head, as the HF16-aware scenarios do, would not be enough here:
after the fork the slot shortens to CHAIN_BLOCK_INTERVAL_HF16 (1 s), while this
file's block-count arithmetic is written against 3 s blocks.

Single-TU -fsyntax-only (testnet defines, nice/ionice): clean.
@On1x
On1x force-pushed the fix/pm-lifecycle-hf16-vote branch from a647cba to 14cc5b5 Compare October 30, 2026 13:46
@On1x On1x changed the title tests: vote for head in pm_lifecycle so HF16 can hand over the turn tests: pin the pm_lifecycle clock instead of starting at now() Oct 30, 2026
@On1x
On1x merged commit 8af748a into master Oct 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant