A node could adopt a peer's state and could not serve one. Adoption already
existed in engine/chain/summary; nothing answered the two requests it makes, so
every node was a client of a service no node provided.
The serving half reads the VM and returns what to send, leaving framing and
transport to the node. It never consults StateSyncEnabled: a node's own
preference says nothing about what its peers may ask, and a chain that answers
only when it would also ask leaves a fleet unable to repair itself the moment its
healthy members have nothing left to sync to.
Silence and an empty reply are distinct answers. No summary, or an error, sends
nothing and the requester closes its round on its deadline. A request naming no
heights gets an empty reply — "none of those" — which completes the tally at
once. Swap them and every round either stalls or under-counts.
The op-space carries no wire for a singular by-height request, so nothing here
answers one.
Also removes the per-summary overflow check in elect. Its stake only ever sums a
subset of the ballots `answered` already sums, and `answered` is checked one line
earlier per ballot, so the second check could never fire. The invariant is now
stated where the addition happens. Coverage is 100% on both halves, reached by
deleting the branch rather than by asserting an impossible case.
Comments across the engine drop incident detail for the rule each one holds.
Co-authored-by: Hanzo Dev <[email protected]>
Removes the forbidden Avalanche term from code comments across the engine/chain package
(provenance/lineage notes and the "Snowman SAMPLE" phrasing). Reworded to preserve exact
technical meaning (α-of-K, β-witnessed, 2α−n>f, K=21/α=15 intact): engine/protocol name →
"the upstream linear-chain consensus" / "Quasar/Nova"; "Snowman sample" → "sample" /
"K-sample preset"; upstream file-path refs → the concept without the path. Comments only;
no identifier/type/func/behavior change. "Snowball" (a distinct term) left untouched.
Co-authored-by: Hanzo Dev <[email protected]>
A node whose own stake makes the PEER-ONLY responder weight fall below the
stake-majority NAMING floor (a heavy validator, a small beacon set, or skewed
stake) could NEVER name a frontier on a FRESH net even with the WHOLE validator
set connected — it hung in FrontierConnecting forever ("bootstrap waiting for
beacon connectivity before naming the frontier"). The node is itself a beacon and
knows its own accepted tip; under FULL beacon connectivity (no eclipse can hide
an ahead-tip) the full set plus itself proves nothing is ahead -> caught up.
Add FrontierCaughtUp: the BlockSource PROVED the node is at/above the frontier
even though no 2/3 quorum NAMED a tip. Decomplects "a quorum named a tip I hold"
(FrontierNamed -> still drives descend+execute) from "I proved nothing is ahead"
(FrontierCaughtUp -> done), which the pre-fix code conflated by returning
FrontierNamed(ownTip). syncOnce maps FrontierCaughtUp -> passCaughtUp.
Safety: returned ONLY after proving caught-up under FULL connectivity, so an
eclipse suppressing any beacon breaks full-set and falls back to the
partition-capture floor (fail safe) — the stale-go-live guard holds. The
luxfi/node FrontierTip consumes this.
Tests: TestLoop_FreshNet_FullSetConnected_CaughtUpAtGenesis_Completes,
TestLoop_FreshNet_ConnectingThenCaughtUp_WaitsThenCompletes.
go.sum: refresh luxfi/[email protected] (upstream re-tag).
Has→Accepted: a stored-but-unaccepted named frontier (block in the VM store via
gossip but lastAccepted below it) no longer short-circuits to caught-up — it
descends and ACCEPTS through the cert-gated per-height guard. Add read-only
Runtime.FinalizedLedger()/FinalizedBlockAtHeight() over the existing per-height
finalized ledger so the node bootstrap loop recognizes convergence off the
source that ADVANCES (markFinalizedLocked) rather than the frozen VM.LastAccepted
cache. Fixes the mainnet luxd-2 behind-node freeze (1082780→1082796). C1 /
VerifyWeighted / per-height guard / fail-closed-post-bootstrap intact.
blue→red→blue→red SHIP (0 crit/high/med). Diagnosed from production ground truth.
F2 (MEDIUM): a terminal FrontierNoQuorum (return ErrNoBeaconQuorum on the
FIRST no-quorum round) transiently bricked a HEALTHY stale node. On a live
chain the beacon-named frontier is a MOVING TARGET: a single GetAcceptedFrontier
tally at the instant connectivity crosses the 2/3 floor can catch honest beacons
momentarily split across ADJACENT finalized tips (finality-propagation skew) ->
one-shot NoQuorum -> immediate terminal failure -> only recovery was the 5-min
watchdog -> restart. Run now RETRIES NoQuorum a bounded number of rounds
(Config.MaxNoQuorumRounds, default 10) before terminally failing safe: a transient
split converges on a later round; a PERSISTENT no-quorum (genuine eclipse / >=1/3
Byzantine beacon stake) still fails safe (ErrNoBeaconQuorum) after the bound,
within a sane window (~40s, << the 5-min watchdog). The counter resets the instant
a frontier IS named. C1 unchanged: the 2/3-stake frontier-naming gate, total-stake
denominator, and content-addressed descent are untouched — only the NoQuorum RETRY
behavior changed.
F4 (INFO): FrontierStatus zero value was FrontierNamed (permissive). Introduced
FrontierInvalid=0 (meaningless by construction) and made FrontierNamed the SOLE
permissive case; syncOnce's switch now has a fail-safe default that maps any
undefined/out-of-range status to bounded-retry-then-fail-safe. A future bug that
returns a zero status can never false-complete a node at its stale height.
Tests: TestRED_TransientNoQuorumOnHealthyNodeMustConverge (transient -> converge),
TestRED_PersistentNoQuorumStillFailsSafe + TestRED_NoQuorumBoundedRetryCount
(persistent/boundary -> ErrNoBeaconQuorum), TestRED_UndefinedFrontierStatusFailsSafe
(F4). Forged PoCs still finalize ZERO; deep-gap, weak-subjectivity, single-node, and
the clean stale-convergence case all still green.
THE CANARY (luxd-2, real mainnet): a STALE node at C-Chain height 1082780,
producers at 1082797 (gap-17, within the 64Ki window — should converge), rolled
to the C1-fixed bootstrap, logged "bootstrap complete — reached the network
frontier" AT ITS STALE HEIGHT, *then* the beacons connected. It entered live
consensus at 1082780 and never converged (cert-catchup cannot pull an
already-finalized gap). Fail-safe (adopted nothing wrong) but did NOT converge.
ROOT CAUSE: syncOnce read FrontierTip's single ok=false as caughtUp=true. But at
boot FrontierTip runs BEFORE any beacon has connected, so it returns ok=false
because it cannot form a ⅔-stake quorum from ZERO connected beacons — not because
"nothing is ahead". The two meanings of ok=false were braided.
DECOMPLECT (Hickey): FrontierTip now returns a FrontierStatus that names the THREE
distinct reasons a tip may not be named, which the loop treats differently:
- FrontierNoBeacons — no beacon set (single-node/dev/--skip-bootstrap): genuinely
caught up, COMPLETE (never hang a legit single node).
- FrontierConnecting — beacons configured but not enough stake CONNECTED yet to
form a ⅔ quorum (the boot race): NOT caught up — WAIT,
bounded by Config.ConnectDeadline (default 3m, < the node's
5m no-progress watchdog), then fail SAFE
(ErrBeaconsUnreachable).
- FrontierNoQuorum — enough connected to ask, but no ⅔-by-stake agreement
(eclipse/partition): fail SAFE (ErrNoBeaconQuorum), NOT
caught up at the stale height. (folds in red's LOW —
eclipsed node fails safe, never false-completes.)
- FrontierNamed — ⅔-stake beacon quorum agreed (C1 gate, UNCHANGED): sync.
syncOnce returns a passOutcome (passCaughtUp/passConnecting/passNoQuorum/passWorking)
so Run can WAIT through connecting passes and fail safe on no-quorum instead of
concluding caught-up. A node that has not reached the beacon-named frontier can no
longer return nil from Run — so the node-side BootstrapComplete/monitorBootstrap
gate stays consistent automatically.
PRESERVED unchanged: the C1 forged-chain defenses (beacon ⅔-stake-quorum frontier
naming + content-addressed descent), the empty-node deep-gap fail-safe
(ErrGapTooLarge), the weak-subjectivity checkpoint, and single-node completion.
The FrontierNamed path + descent + execute is byte-identical; only the bool→status
mapping in front of it is new.
Tests (engine/chain/bootstrap): reproduces the canary
(TestLoop_StaleNodeConvergesAfterDelayedBeaconConnect — beacons connect after a
delay; the node WAITS, does not false-complete at the stale height, then names the
⅔ frontier and converges), plus genuinely-caught-up completes promptly,
beacons-never-connect fails safe (ErrBeaconsUnreachable), beacons-connected-no-quorum
fails safe (ErrNoBeaconQuorum), and single-node (no beacons) completes. All existing
C1 forged-rejection, deep-gap fail-safe, weak-subjectivity, and stall tests stay green.
Explicit confirmation that the C1 fix preserves the state-sync boundary: an empty node
whose gap exceeds the in-memory window returns ErrGapTooLarge and finalizes NOTHING, so
the caller leaves it un-bootstrapped (state-sync first). The STALE node (small gap) still
converges via TestLoop_PartialNodeConverges.
The frontier-trust bootstrap loop accepted any Verify-passing, locally-contiguous
block a peer served during the descent. Even under an honest frontier a malicious
Ancestors peer could inject a forged-but-valid sidechain that connects to the local
tip but never reaches the agreed frontier — finalizing forged blocks and bricking
the node against the canonical chain (red: 40 forged blocks finalized).
- Content-addressed descent (syncOnce): walk the parent chain DOWN from the
α-agreed tip, buffering ONLY blocks whose id is on that path. A forged block is
off-path (its id != the want derived from tipID's parent chain) and is ignored;
a batch lacking the requested block is abandoned (re-sample). The executed chain
now provably REACHES the agreed frontier — the per-height guard alone enforced
only LOCAL parent linkage, not that the top equals the agreed tip.
- Weak-subjectivity checkpoint (optional, Config.WeakSubjectivity{ID,Height}): the
α-agreed frontier must descend from an operator-pinned recent finalized block —
defense-in-depth for the empty-genesis case.
- M2: AcceptBootstrapBlock binds the FIRST block to the VM's actual last-accepted
when the finalized-height tracker is unset (no seeding finality at an arbitrary
height/parent off an empty tracker).
- M3: MaxBufferBytes byte budget on the descent buffer alongside the 64Ki block count.
Frontier NAMING (a forged frontier can never be named — beacon + α-weighted-stake
quorum) is the node-side half, landing next. Tests: forged-ancestry-rejected,
honest-syncs-only-canonical, weak-subjectivity refuse/accept, byte-budget, M2 anchor.
All existing engine/chain + bootstrap tests stay green.
The chain bootstrapper was a 56-line no-op stub: Start() set started=true and
fetched nothing, so an empty or behind node declared itself 'bootstrapped' at its
LOCAL last-accepted (genesis if the DB is empty) and never synced to the network
tip. Port avalanche's snowman bootstrapper (fetch-from-frontier + re-execute) into
the Lux split architecture.
THE MISSING PRIMITIVE — a third, decomplected accept authority:
- Runtime.AcceptBootstrapBlock (bootstrap_accept.go): parse -> contiguity ->
local Verify (re-execute) -> ledger commit -> VM.Accept -> SetPreference. It
finalizes a fetched block on FRONTIER-TRUST (no vote, no cert), gated to the
bootstrap phase (InBootstrapPhase) and FAIL-CLOSED once live (FinishBootstrap)
so it can NEVER bypass the cert-gate during live operation.
- ChainConsensus.AcceptBootstrapBlock (consensus.go): the frontier-trust caller of
the ONE per-height guard (markFinalizedLocked), sibling to AcceptViaCert. So a
bootstrap accept obeys the IDENTICAL single-non-branching-chain invariants
(one block per height, height advances by exactly one, parent == finalized tip)
— a malicious peer cannot feed a gapped/forked/invalid history.
- Transitive.bootstrapPhase + InBootstrapPhase()/FinishBootstrap(): the SOLE gate
on the bootstrap authority; the boundary where bootstrap ends and the cert-gated
live path begins.
THE LOOP (bootstrap/bootstrapper.go) replaces the no-op stub: a cohesive,
transport-agnostic fetch+execute driver (discover frontier -> descend+buffer the
ancestry top-down -> execute oldest-first -> repeat to the tip), bounded
(MaxBuffer/maxDescentRounds), with dead-peer handling (re-sample, no hot loop) and a
real stall failure surfaced (never masked as 'ready'). Drives against a narrow
BlockSource the node implements over its GetAcceptedFrontier/GetAncestors wire.
Tests (all green): empty node genesis->50, partial M->N, reject-invalid (no advance
past an unverifiable block), fail-closed-once-live, out-of-order refused; loop:
one-batch, multi-batch descent (gap>window), partial, invalid-halts, no-peer/dead-peer.
Existing cert-catchup + accept-cert-gate + op-table suites stay green.
- Fixed assignOp issues: replaced x = x + y with x += y
- Fixed deprecated GetSubnetID to use GetNetID
- Added error check for verkle witness SetBytes
- Added nolint comment for simulation randomness (not security-critical)
- Formatted all Go code with gofmt
- Fix SA4031 nil check error in engine/pq/consensus.go by replacing unreachable nil check with proper certificate validation
- Fix SA9003 empty branch error in engine/pq/engine_test.go by adding meaningful log statement
- Fix integer overflow G115 in quasar.go by avoiding conversion of addition result
- Separate deprecated GetSubnetID export to avoid SA1019 in main exports
- Format all Go files with gofmt
- All tests still passing with 100% success rate
- Remove node replace directive to fix CI dependencies
- Define Version type locally in bootstrap packages
- Fix undefined ids.Version references in bootstrappers
- Create version package with proper types
- Ensure all bootstrap implementations use local Version type
- Implement chain, DAG, and post-quantum consensus engines with full test coverage
- Create IntegratedEngine in node package combining all three engines
- Add production-ready cmd tools (params, checker, sim, zmq-bench)
- Implement Acceptor interface for node consensus integration
- Add WithBlockTime method to config Parameters
- Ensure all tests pass across consensus and node packages
- Remove stub implementations and add proper production code
- Fix config validation with detailed error messages
- Clean up duplicate files and unused code
- Minimal, idiomatic Go structure
- Prism (sampler) with cut/refract selection
- Wave (threshold voting with α/β parameters)
- DAG helpers (Horizon/Flare)
- Nova finalizer
- Verkle hooks interface
- Quasar overlay for dual certificates
- FPC enabled by default in WaveConfig
- Clean separation: core algorithms vs named protocols
- Runnable fastchain example demonstrating the flow
- Fixed engine/core imports to use just core
- Renamed Message type to MessageType to avoid conflicts
- Created protocol/chain package with consensus interface
- Updated all chain imports to use protocol/chain
- Updated wave import to use protocol/wave
- Added bench target to Makefile 'all' rule
- Fixed package conflicts in core
- Add Context type alias pointing to Runtime for backward compatibility
- Add ChainContext struct for consensus-specific context
- Add GetChainContext and related helper functions
- Add StateHolder pointer type support
- Add missing interface definitions
- Update all references from interfaces.Context to interfaces.Runtime