This is the exhaustive test specification for the attestation source planner. Codex implements both the tests (table-driven, all scenarios below) and the implementation, with this document as the authoritative behavior contract.
No test in this document is optional. All must be present and passing. Any deviation needs documented justification.
package attplan
type Mode int
const (
ModeNextNonMissed Mode = iota // legacy-next-non-missed, parameterized by cap
ModeStrictKMinus1 // strict-source-block-k-minus-1
ModeGreedyLookahead // multi-source lookahead; representative SourceBlockSlot is next non-missed
ModeFirstSeenGossip // direct first-seen parquet source; no source block
)
// Plan is the per-sim-slot attestation source mapping.
// SourceBlockSlot is nil when no source is found within the window or per mode rules.
type Entry struct {
SimSlot uint64
SourceBlockSlot *uint64
}
// Inputs:
// blockExists: map[slot]bool. True iff a block exists at that slot.
// Callers must populate for at least [simStart, simEnd+lookaheadCap].
// simStart, simEnd: range of sim_slots to plan for. Half-open [simStart, simEnd).
// mode: which heuristic to apply.
// lookaheadCap: meaningful for ModeNextNonMissed and ModeGreedyLookahead. Must be >= 1.
// Ignored for ModeStrictKMinus1 and ModeFirstSeenGossip.
//
// Output: one Entry per sim_slot in [simStart, simEnd), in ascending order.
//
// Errors:
// - mode == ModeNextNonMissed or ModeGreedyLookahead && lookaheadCap < 1: error
// - simStart > simEnd: error
// - simStart == simEnd: empty result (not an error)
func Plan(blockExists map[uint64]bool, simStart, simEnd uint64, mode Mode, lookaheadCap uint64) ([]Entry, error)For each sim_slot N, the source is the first K in [N+1, N+lookaheadCap] such that blockExists[K] == true. If no such K, source is nil.
def source(N, cap):
for K in range(N+1, N+1+cap):
if blockExists[K]:
return K
return NoneFor each sim_slot N, source is N+1 if blockExists[N+1] == true, else nil. lookaheadCap is ignored.
def source(N):
return N+1 if blockExists[N+1] else NoneFor planner compatibility, Plan returns the same representative source as ModeNextNonMissed. The HTTP slot-instruction backend expands this mode into every non-missed source block in the lookahead window.
For each sim_slot N, SourceBlockSlot is always nil. The HTTP slot-instruction backend serves inline indexed attestations grouped from first-seen parquet rows, so no source block is selected by the planner.
Use Go table-driven tests. Each scenario gets a row in a cases slice with name, blockExists, simStart, simEnd, mode, cap, expected (slice of expected sources, indexed by simSlot - simStart).
expected[i] == nil means sim_slot simStart+i has no source. expected[i] == &v means it has source v.
A1. TestA_NoMisses_Cap4
- blockExists: {100,101,102,103,104,105} all true
- simRange: [100,104)
- mode: ModeNextNonMissed, cap=4
- expected: 100→101, 101→102, 102→103, 103→104
A2. TestA_NoMisses_Cap32
- Same as A1 but cap=32.
- Same expected: cap doesn't matter when no misses.
A3. TestA_OneMiss_Cap4
- blockExists: 100,101,103,104 true; 102 false
- simRange: [100,104)
- cap=4
- expected: 100→101, 101→103 (102 missed, next is 103), 102→103, 103→104
A4. TestA_OneMiss_AtStart_Cap4
- blockExists: 100,102,103,104,105 true; 101 false
- simRange: [100,104)
- cap=4
- expected: 100→102, 101→102, 102→103, 103→104
A5. TestA_OneMiss_AtEnd_Cap4
- blockExists: 100,101,102,103,104,106 true; 105 false
- simRange: [100,104)
- cap=4
- expected: 100→101, 101→102, 102→103, 103→104 (sim_slot 103 looks at 104,105,106,107: 104 exists, source=104)
B1. TestA_TwoConsecutiveMisses_Cap4
- blockExists: 100,101,104,105 true; 102,103 false
- simRange: [100,104)
- cap=4
- expected: 100→101, 101→104 (next after gap), 102→104, 103→104
B2. TestA_ThreeConsecutiveMisses_Cap4
- blockExists: 100,101,105 true; 102,103,104 false
- simRange: [100,104)
- cap=4
- expected: 100→101, 101→105 (window 102..105 includes 105), 102→105 (window 103..106 includes 105), 103→105 (window 104..107 includes 105), 104→105 (window 105..108 includes 105 at boundary)
B3. TestA_FourConsecutiveMisses_Cap4
- blockExists: 100,101,106 true; 102..105 false
- simRange: [100,105)
- cap=4
- expected: 100→101, 101→nil (window 102..105 all missed), 102→106, 103→106, 104→106
These directly test the cap=4 default and the +N boundary semantics. The exact value of range(N+1, N+1+cap) is the contract — off-by-one here is the most likely bug.
C1. TestA_CapBoundary_Cap4_BlockAtPlus4
- blockExists: 100, 104 true; 101,102,103 false
- simRange: [100,101)
- cap=4
- expected: 100→104 (sim_slot 100 scans slots 101,102,103,104 — that's 4 slots, all in cap. Block at 104 found at offset +4.)
C2. TestA_CapBoundary_Cap4_BlockAtPlus5_ShouldBeNil
- blockExists: 100, 105 true; 101,102,103,104 false
- simRange: [100,101)
- cap=4
- expected: 100→nil (sim_slot 100 scans 101..104. Block at 105 is at offset +5, OUTSIDE cap. Source = nil.)
C3. TestA_CapBoundary_Cap32_BlockAtPlus32
- blockExists: 100, 132 true; everything between false
- simRange: [100,101)
- cap=32
- expected: 100→132 (offset +32 is the boundary, inclusive.)
C4. TestA_CapBoundary_Cap32_BlockAtPlus33_ShouldBeNil
- blockExists: 100, 133 true; everything between false
- simRange: [100,101)
- cap=32
- expected: 100→nil (offset +33 is outside cap=32.)
C5. TestA_CapBoundary_Cap1_Equivalent_to_StrictB
- For any input, ModeNextNonMissed with cap=1 must produce identical output to ModeStrictKMinus1.
- Property test: random missed-slot pattern, assert outputs equal.
D1. TestA_AllMissedInRange
- blockExists: only 100 true; 101..200 all false
- simRange: [100,150)
- cap=4
- expected: all nil
D2. TestA_ChainStall_Cap32_NoBlockInWindow
- blockExists: 100 true; 101..140 false
- simRange: [100,109)
- cap=32
- expected: all nil (sim_slot 100 scans 101..132, sim_slot 108 scans 109..140 — still nothing.)
D3. TestA_ChainStall_Cap32_BlockJustOutsideWindow
- blockExists: 100, 141 true; everything between false
- simRange: [100,109)
- cap=32
- expected: 100→nil, 101→nil, 102→nil, 103→nil, 104→nil, 105→nil, 106→nil, 107→nil, 108→nil (sim_slot 108 scans 109..140 — 141 is at +33, just outside cap.)
- Then add sim_slot 109 to the range:
- simRange: [100,110), cap=32
- expected[9] (sim_slot 109): 141
- (sim_slot 109 scans 110..141 — block at 141 is at +32, inclusive.)
E1. TestA_FinalSimSlot_ScansBeyondEnd
- blockExists: 100..104 true, 105..110 true (so blocks exist past end_slot)
- simRange: [100,105)
- cap=4
- expected: 100→101, 101→102, 102→103, 103→104, 104→105 (Final sim_slot 104 sources block at 105, which is BEYOND simEnd. Planner must look past simEnd.)
E2. TestA_FinalSimSlot_BlocksNotProvidedPastEnd
- blockExists: 100..104 true; 105..136 NOT IN MAP (treated as false)
- simRange: [100,105)
- cap=4
- expected: 100→101, 101→102, 102→103, 103→104, 104→nil (Final sim_slot has no block in its scan window. Returns nil. Caller is responsible for providing blockExists data past simEnd if they want the tail covered.)
E3. TestA_EmptyRange
- simRange: [100,100)
- expected: empty slice
- Not an error.
E4. TestA_SingleSlotRange
- blockExists: 100,101 true
- simRange: [100,101)
- expected: 100→101
F1. TestB_NoMisses
- blockExists: 100..105 true
- simRange: [100,104)
- mode: ModeStrictKMinus1
- expected: 100→101, 101→102, 102→103, 103→104
F2. TestB_OneMiss
- blockExists: 100,101,103,104 true; 102 false
- simRange: [100,104)
- expected: 100→101, 101→nil (block 102 missed), 102→103, 103→104
F3. TestB_OneMiss_AtStart
- blockExists: 100,102,103 true; 101 false
- simRange: [100,103)
- expected: 100→nil, 101→102, 102→103
F4. TestB_ConsecutiveMisses
- blockExists: 100,105 true; 101..104 false
- simRange: [100,105)
- expected: 100→nil, 101→nil, 102→nil, 103→nil, 104→105
F5. TestB_AllMissed
- blockExists: 100 true; rest false
- simRange: [100,110)
- expected: all nil
F6. TestB_CapParameterIgnored
- For any input, ModeStrictKMinus1 must produce same output regardless of
lookaheadCapvalue. - Pass cap=1, cap=4, cap=32, cap=1000 — all must produce identical results.
G1. TestInv_NoMissesAgreement
- For any input WITHOUT missed slots in [simStart+1, simEnd+1]:
- ModeNextNonMissed (any cap >= 1) == ModeStrictKMinus1 == "source = sim_slot+1 always"
- Use a fuzz-style or random-input property test.
G2. TestInv_ModeBIsSubsetOfModeA
- For any input, every non-nil entry in ModeStrictKMinus1 output is also present (and equal) in ModeNextNonMissed (cap >= 1) output at the same sim_slot index.
- Conversely, every nil in ModeStrictKMinus1 either matches nil OR a later source in ModeNextNonMissed.
G3. TestInv_ModeASourceInWindow
- For any input, in ModeNextNonMissed output: if
source[i]is non-nil, thensimStart+i+1 <= source[i] <= simStart+i+cap. - Source is never <= sim_slot or > sim_slot+cap.
G4. TestInv_DeterministicGivenSameInput
- Call Plan twice with same inputs. Outputs must be equal.
H1. TestErr_Cap0_ModeA
- simRange: [100,104), mode=ModeNextNonMissed, cap=0
- expected: error (no useful semantics for cap=0)
H2. TestErr_SimStartAfterSimEnd
- simRange: [105,100), mode=ModeNextNonMissed, cap=4
- expected: error
H3. TestErr_Cap0_ModeB_NoError
- simRange: [100,104), mode=ModeStrictKMinus1, cap=0
- expected: NO error (cap is ignored in ModeB)
I1. TestNum_SlotZero
- blockExists: 0,1,2,3,4 true
- simRange: [0,4)
- mode: ModeNextNonMissed, cap=4
- expected: 0→1, 1→2, 2→3, 3→4
- (Genesis-adjacent ranges. In practice we won't simulate this, but the planner shouldn't reject slot 0.)
I2. TestNum_LargeSlotNumbers
- blockExists: 10_000_000, 10_000_001 true
- simRange: [10_000_000, 10_000_001)
- expected: 10_000_000 → 10_000_001
- (Far-future slot numbers, no overflow.)
I3. TestNum_NoUInt64Overflow_AtMaxSlot
- simRange: [math.MaxUint64-100, math.MaxUint64-90), cap=32
- This shouldn't be a real use case, but the planner shouldn't panic.
- expected: implementation choice — either error on overflow or return nil for sources that would overflow. Document and test.
- Recommended: return nil (sim_slot+cap would overflow → no valid source possible).
J1. TestA_Cap4_ReproducesCurrentLighthouseBehavior
- This is the most important test. Sample 5-10 real ranges from mainnet with known missed slots, fork transitions, and chain density.
- For each range, compute the planner output with ModeNextNonMissed, cap=4.
- Assert it matches a golden file generated from the current simulator's behavior — specifically, the attestation source pattern observed in current
inject_next_block_attestations(engine.rs:328-403).
Implementation note: we don't have a direct hook for "what source did current Lighthouse pick" in CSV output. Add one as part of the slim-Lighthouse work (source_block_slot column in JSONL v3 — emit it from both old and new code paths during the parity test). Then this test reads the old simulator's output and asserts the new planner agrees.
If we can't add that column to the old simulator easily, fallback: extract the "would-have-been-source" logic into a separate Rust function in the current codebase, run it offline, dump a CSV of (sim_slot, source_block_slot) pairs, use that as the golden file.
K1. TestPlan_OutputIsAscendingBySimSlot
- Property: for any input, output
[]Entryis ordered bySimSlotascending with no gaps.
K2. TestPlan_OutputCoversFullRange
- Property: len(output) == simEnd - simStart (assuming simStart <= simEnd).
- Every sim_slot in [simStart, simEnd) appears exactly once.
Plan should complete in O((simEnd - simStart) × cap) time. For typical V1 inputs (~100k sim_slots, cap=4 or 32), this is fast (millions of map lookups in a few ms).
Add a benchmark:
func BenchmarkPlan_100k_Cap4(b *testing.B) { ... }
func BenchmarkPlan_100k_Cap32(b *testing.B) { ... }No specific budget — just ensure it's not pathologically slow.
pkg/attplan/
├── attplan.go # implementation
├── attplan_test.go # all unit tests above, table-driven
├── attplan_fuzz_test.go # property tests (G-series)
├── attplan_bench_test.go # benchmarks
└── doc.go # package-level docstring referencing this spec
Use testify/require for assertions (concise diffs). Use quick.Check or testing/fuzz.Fuzz for property tests.
go test ./pkg/attplan -cover must report >= 95% line coverage. Anything lower means there's a code path without a test. Codex should add tests until the threshold is met or document why the uncovered lines are unreachable.
Before declaring done:
- All table-driven test cases listed above have a corresponding
case{...}entry in the test slice. Names match. - All property tests (G-series) are implemented as standalone test functions.
- All error cases (H-series) return non-nil errors and are tested via
require.Error. - Benchmark exists and runs without error.
-
go test ./pkg/attplan/... -racepasses. -
go vet ./pkg/attplan/...clean. - Coverage >= 95%.
- J1 fixture parity test is wired even if the golden file is a TODO (place a
t.Skipwith a clear reason if the fixture can't be generated yet).