Skip to content
Security
Skill

/fork-choice-audit

L1 trigger - audits fork-choice rule implementation (LMD-GHOST, Tendermint locking, Nakamoto longest-chain) for equivocation handling, slot-vs-block reasoning, duplicate block handling, and chain reorg correctness.

From plugin
plamen
276160 skills12 agents4 commands
Install
$ npx -y skills add PlamenTSV/plamen --skill fork-choice-audit --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/fork-choice-audit

Context preview

The summary Claude sees to decide when to auto-load this skill.

L1 trigger - audits fork-choice rule implementation (LMD-GHOST, Tendermint locking, Nakamoto longest-chain) for equivocation handling, slot-vs-block reasoning, duplicate block handling, and chain reorg correctness.

SKILL.md

fork-choice-audit.SKILL.md
name: "fork-choice-audit"
description: "L1 trigger - audits fork-choice rule implementation (LMD-GHOST, Tendermint locking, Nakamoto longest-chain) for equivocation handling, slot-vs-block reasoning, duplicate block handling, and chain reorg correctness."

Injectable Skill: Fork Choice Audit

> **L1 trigger**: `L1_PATTERN=true` AND (`fork_choice/` OR `ghost/` OR `lmd/` OR `consensus/tendermint/` OR `fork.rs` OR `choice.rs` detected in recon subsystem map) > **Inject Into**: `depth-consensus-invariant` > **Language**: Go and Rust > **Finding prefix**: `[FC-N]` > **Status**: v0.1 draft, Round 4 exemplars pending

Orchestrator Decomposition Guide

  • Section 1, 2: depth-consensus-invariant (rule correctness)
  • Section 3: depth-edge-case (equivocation + duplicate handling)
  • Section 4: depth-state-trace (reorg state consistency)

When This Skill Activates

Recon identifies a fork-choice module. This skill applies whether the protocol uses LMD-GHOST (Ethereum beacon chain), longest-chain (Bitcoin, pre-merge Ethereum), Tendermint locking (Cosmos), or a custom fork-choice rule.

1. Identify the Rule

Before auditing, determine which rule is implemented. Extract from spec docs and code:

| Rule | Signature | Key invariants | |---|---|---| | **LMD-GHOST** | "latest message driven — greedy heaviest observed subtree" | Each validator's latest attestation contributes to a subtree weight; choose heaviest | | **Casper FFG + LMD-GHOST** (Ethereum) | LMD-GHOST bounded by finalized checkpoints | Never revert past justified/finalized checkpoint | | **Tendermint BFT** | Round-based, locking on 2/3+ prevotes | Locked validator cannot vote for conflicting proposal in same round | | **Nakamoto longest-chain** | Heaviest accumulated work | Chain with most work wins; stale blocks discarded | | **HotStuff / Aptos / Sui** | 3-phase: prepare, precommit, commit | No two conflicting QCs at the same view |

Write the identified rule into the finding header so reviewers know which invariants apply.

2. Rule-Specific Invariants

2a. LMD-GHOST

  • **Heaviest subtree monotonicity**: adding an attestation can only increase (never decrease) the subtree weight
  • **Latest-message tie-break**: if two children have equal weight, tie-break is deterministic (typically lower root hash)
  • **Boundary with finality**: fork choice never selects a block that would require reverting a finalized block
  • **Slot vs block**: a slot can have zero, one, or multiple proposed blocks; fork choice must handle the missing-block case

**Check**: Enumerate every call site of `find_head()` / `get_head()`. For each, verify the return value is bounded by the latest finalized checkpoint.

2b. Tendermint BFT locking

  • **Lock only on 2/3+ prevotes**: validator cannot lock without proof of 2/3 prevotes
  • **Lock carries across rounds**: once locked in round R, validator votes locked block in rounds R+1, R+2, ... unless unlocked by a newer 2/3+ prevote
  • **Unlock on newer PoLC** (Proof-of-Lock-Change): only unlock if a strictly newer round's 2/3+ prevotes are seen
  • **No equivocation across rounds**: locked validator cannot vote for conflicting block in later round of same height

**Check**: Follow the `lock_value`, `lock_round`, `valid_value`, `valid_round` state variables. Verify every `set_lock` call is guarded by the 2/3 check.

2c. Nakamoto longest-chain

  • **Cumulative difficulty, not length**: chain selection uses total work, not block count
  • **Stale block eviction**: blocks building on non-canonical ancestors are discarded cleanly
  • **Reorg depth bound**: some clients impose a max reorg depth (e.g., 64 blocks); verify the bound is documented and enforced

3. Equivocation and Duplicate Block Handling

Equivocation = a validator signing two conflicting attestations. Fork choice must:

1. **Detect** equivocation via slashing conditions (double vote, surround vote, double proposal) 2. **Preserve** both conflicting signatures as evidence 3. **Not crash** or hang when two conflicting messages arrive in close proximity 4. **Not double-count** the equivocator's weight — their vote should NOT contribute to fork-choice weight after equivocation is detected

**Check**:

  • Ast-grep for "equivocation" / "double_vote" / "slashing" in fork-choice code
  • Trace the weight computation: does it exclude equivocators' contributions?
  • Duplicate block handling: if two blocks for the same slot arrive, does the fork-choice data structure gracefully hold both, or does the second overwrite the first? Overwrites are bug-prone.

**Known exemplar class**: Cosmos / Tendermint "Denial of Validators" attacks — multiple historical bugs where equivocation detection could itself be exploited to halt the chain.

Tag: `[EQUIVOC:{loc}:{handling}]`

4. Reorg State Consistency

When fork choice selects a new head, the node must reorg: revert state from the old head back to the common ancestor, then apply blocks from the common ancestor to the new head.

**Check**: 1. Enumerate all state that must be rolled back on reorg: account balances, nonces, receipts, logs, fork-choice's own caches 2. For each state category, verify the reorg path touches it 3. Look for **side-channel state** that does NOT roll back: metrics, logs flushed to disk, cached RPC responses, indexer state 4. On reorg, is any background task (indexer, RPC subscription, metrics) left in an inconsistent state?

Tag: `[REORG-GAP:{state-category}]`

4a. Fork block ordering / replay order

For every helper that returns blocks to apply during a reorg (`get_fork_blocks`, `fork_blocks`, `blocks_to_apply`, `ancestor_path`, `rollback_path`):

1. Determine the expected order: ancestor → child → ... → new head for forward application, or old head → ... → ancestor for rollback. 2. Inspect whether the implementation collects by walking parent pointers from head to ancestor and forgets to reverse the list. 3. Check all consumers: if a function expects forward order but receives reverse-chro

Read more
Ships withplamen

Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.

Get the whole plugin