Skip to content

fix(nonce): correct TICKN candidate freeze and η_ph hash#88

Merged
wcatz merged 1 commit intomasterfrom
fix/nonce-tickn-bugs
Feb 26, 2026
Merged

fix(nonce): correct TICKN candidate freeze and η_ph hash#88
wcatz merged 1 commit intomasterfrom
fix/nonce-tickn-bugs

Conversation

@wcatz
Copy link
Copy Markdown
Owner

@wcatz wcatz commented Feb 26, 2026

Summary

  • Bug 1: FreezeCandidate off-by-one — candidate nonce included one extra block's contribution because ProcessBlock evolved before freeze. Moved freeze inside ProcessBlock before evolution.
  • Bug 2: TICKN η_ph used last block's own hash instead of prevHash (= second-to-last block hash). Added GetPrevHashOfLastBlock store method.
  • Bug 3: RecomputeCurrentEpochNonce trusted stored nonce_value instead of recomputing from raw VRF output. Added GetVrfOutputsForEpoch.

Bugs 1+2 compound to produce completely wrong nonces on the auto-leaderlog trigger at the stability window. CLI/manual commands were unaffected (fall through to Koios).

Post-deploy

After deploying, clear cached wrong TICKN nonces:

DELETE FROM epoch_nonces WHERE source = 'computed';
DELETE FROM leader_schedules WHERE epoch >= (SELECT MAX(epoch)-2 FROM leader_schedules);

Test plan

  • Deploy to goduckbot-test (latest tag)
  • Wait for next epoch stability window trigger
  • Compare auto-triggered leaderlog nonce against Koios
  • Run /nonce <epoch> and verify match

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Restructured nonce computation to derive epoch values directly from VRF outputs, replacing prior storage-based approach for enhanced consistency across epoch transitions.
    • Nonce freezing now automatically occurs at the epoch stability window, eliminating manual operations and improving epoch synchronization efficiency.

Three bugs in the live nonce pipeline that compound to produce wrong
leader schedules from the auto-trigger at the stability window:

1. FreezeCandidate off-by-one: ProcessBlock evolved the nonce BEFORE
   FreezeCandidate was called externally, so the candidate included
   one extra block's contribution. Moved freeze logic inside
   ProcessBlock before evolution, matching ComputeEpochNonce.

2. TICKN η_ph used wrong hash: GetLastBlockHashForEpoch returns the
   last block's own hash, but η_ph = prevHash of last block = hash
   of the second-to-last block. Added GetPrevHashOfLastBlock method.

3. RecomputeCurrentEpochNonce trusted stored nonce_value instead of
   recomputing from raw VRF output. Added GetVrfOutputsForEpoch and
   aligned with ComputeEpochNonce's recomputation approach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wcatz wcatz merged commit 2ff04b5 into master Feb 26, 2026
3 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02502a7 and 59f29b8.

📒 Files selected for processing (4)
  • db.go
  • main.go
  • nonce.go
  • store.go

📝 Walkthrough

Walkthrough

This change refactors nonce computation to rely on raw VRF outputs instead of stored nonce values. New database methods retrieve VRF outputs per epoch and the previous block's hash. Nonce candidate freezing moves from an explicit API call into ProcessBlock at the stability window. The NonceTracker's public FreezeCandidate method is removed in favor of internal epoch-driven logic.

Changes

Cohort / File(s) Summary
Store & Database Layer
store.go, db.go
Introduced VrfBlock type and extended Store interface with GetVrfOutputsForEpoch and GetPrevHashOfLastBlock methods; added corresponding PgStore and SqliteStore implementations using raw SQL queries ordered by slot and leveraging LIMIT/OFFSET for previous block retrieval.
Nonce Computation Rewrite
nonce.go
Switched from stored nonce_value sequences to recomputation from raw VRF outputs via GetVrfOutputsForEpoch; integrated candidate nonce freezing into ProcessBlock at stability window; updated GetNonceForEpoch to compute η_ph using GetPrevHashOfLastBlock; removed public FreezeCandidate method; updated RecomputeCurrentEpochNonce and ComputeEpochNonce to derive nonces directly from VRF data with revised logging and state management.
Block Processing Flow
main.go
Removed explicit nonceTracker.FreezeCandidate(epoch) call from checkLeaderlogTrigger, relocating freeze behavior into ProcessBlock logic; trigger activation and leaderlog calculation flows remain unchanged.

Sequence Diagram(s)

sequenceDiagram
    actor Client as Client
    participant PB as ProcessBlock
    participant NT as NonceTracker
    participant Store as Store/Database
    
    Client->>PB: ProcessBlock(slot, epoch, blockHash, vrfOutput)
    activate PB
    
    alt At Stability Window
        PB->>NT: Freeze candidate nonce internally
        activate NT
        NT->>NT: Log & persist η_c = evolving nonce
        deactivate NT
    end
    
    PB->>PB: Continue with normal processing
    deactivate PB
    
    Client->>NT: RecomputeCurrentEpochNonce(ctx, epoch)
    activate NT
    
    NT->>Store: GetVrfOutputsForEpoch(ctx, epoch)
    activate Store
    Store-->>NT: []VrfBlock (ordered by slot)
    deactivate Store
    
    loop For each VrfBlock
        NT->>NT: Recompute nonce from VRF output
    end
    
    NT->>Store: GetPrevHashOfLastBlock(ctx, epoch-1)
    activate Store
    Store-->>NT: Previous epoch's second-to-last block hash (η_ph)
    deactivate Store
    
    NT->>NT: Persist recomputed nonce & update in-memory state
    deactivate NT
    
    Client->>NT: GetNonceForEpoch(epoch)
    activate NT
    NT->>Store: GetPrevHashOfLastBlock(ctx, epoch-1)
    activate Store
    Store-->>NT: η_ph for epoch transition
    deactivate Store
    NT->>NT: Compute η = H(η_c || η_ph)
    NT-->>Client: []byte (derived nonce)
    deactivate NT
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Freeze the nonce when stability blooms,
Recompute from VRF's truthful rooms,
Hash the last block, derive the new way,
No stored values lead us astray!

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/nonce-tickn-bugs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@wcatz wcatz deleted the fix/nonce-tickn-bugs branch March 20, 2026 14:23
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