M-Chain's committee is the validator set, and RunKeygen refuses a policy
needing more parties than the committee has. mainnet's mchain.json asked
for 7-of-10 against five genesis validators, so a custody key could never
have been generated — the failure would have surfaced the first time
someone tried to bridge, not at deploy.
Adds the invariant as a test over every network's built genesis, and
picks up luxfi/genesis v1.16.4 where the policies are sized to fit
(3-of-5 on mainnet/testnet/devnet, 2-of-3 on localnet).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The build installed M-Chain's plugin binary as
tGVBwRxpmD2aFdg3iYjgRvrCe8Jcmq9UNKxyHMus2NZ8WcD8t — the CB58 of the
retired `thresholdvm` identifier — while the genesis builder declares the
chain with constants.MPCVMID (qCURact1n41FcoNBch8iMVBwc9AWie48D118ZNJ5tBdWrvryS).
The plugin registry resolves a CreateChainTx's vmID to an implementation
by filename, so the two never met: M-Chain was declared in genesis and no
node could start it. The Dockerfile comment had already been renamed to
"mpcvm" without the CB58 being recomputed, which is why it read as correct.
Fixes all five sites (plugin build target, the build-verify list, the
runtime COPY, the comment, and publish_plugin_set.sh) and adds
genesis/builder/mchain_test.go, which pins the vmID literally and asserts
M-Chain is present in the height-0 chain set for mainnet, testnet and
local. A vmID is an immutable one-way door once a chain is created with
it, so it is now covered by a test rather than by five copies of a string.
Bumps luxfi/genesis to v1.16.3, where mchain.json states its quorum as
"policy": "7-of-10" instead of a bare mpcThreshold that reads as the
signer count to an operator and as the polynomial degree to a library.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Propagates two just-published upstream fixes into the node (luxd) binary:
- consensus v1.36.7 → v1.36.9: the chain engine no longer os.Exit(1)-kills a
validator on a SetPreference orphan-refusal — it reconciles the VM to the
certified block when the diverged tip is provably uncertified, and halts
fail-closed only on a genuine double-finalization (v1.36.8). Also unifies
consensus's transitive geth pin to v1.20.1, matching node/evm (v1.36.9). The
new PreferenceReconciler VM interface is optional, so node's VMs are
unaffected (they take the non-fatal defer path); no code change required here.
- chains v1.7.7 → v1.7.9: every VM (aivm/keyvm/quantumvm/dexvm) now serializes
through native luxfi/zap struct-is-wire, with canonical-id hardening.
go.mod/go.sum only — dependency-graph resolution, no node code change. Transitive
indirects move forward within-major (bft, cockroachdb/errors, sentry-go,
prometheus/common, go-internal); no major-version bumps. geth stays v1.20.1
(already node's pin). Verified: GOWORK=off CGO_ENABLED=0 go build ./... clean.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Two changes, both node-local:
1. CADENCE — proposer.WindowDuration (the proposer-slot spacing, a validator's min build
delay = slot index x WindowDuration) was a hardcoded 5s const tuned for mainnet-scale
validator sets, flooring small/local block cadence at 5s per slot. It is now a
startup-configurable var (default 5s, unchanged for mainnet) set via the new
--proposervm-window-duration flag → node.Config → ManagerConfig → proposervm.Config →
proposer.SetWindowDuration at VM init. Read by both the windower delay math and
TimeToSlot, so they stay consistent. Measured on a 5-node strict-PQ net: 5s→1s took
sustained C-Chain from 44 TPS / 14s-per-block to 104 TPS / 3.8s-per-block, still 5/5
byte-identical finality.
2. SECURITY (cryptographer MEDIUM-1) — postForkCommonComponents.Verify now refuses a block
carrying a CLASSICAL secp256k1 proposer identity when the chain is strict-PQ
(StakingMLDSASigner set), UNCONDITIONALLY (before the consensusState==Ready gate, so it
also holds during bootstrap/state-sync). Fills the documented-but-unwired 'proposer'
SchemeGate site: the downgrade defense is now an explicit fail-closed in-perimeter gate,
not merely emergent from 20-byte NodeID collision-resistance + upstream enforcement.
Adds SignedBlock.HasClassicalProposer(). Mirrors contract.RefuseUnderStrictPQ.
Pins consensus v1.36.7 (consume-on-error build loop — kills the non-leader BuildBlock spin).
Block + proposervm VM tests green.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Under strict-PQ the canonical NodeID is ML-DSA-65-derived (config/node DeriveNodeID →
DeriveMLDSA), so the P-chain validator set and the proposervm windower are ML-DSA-keyed.
But proposervm signed post-fork blocks with the classical TLS leaf and derived the block
Proposer() via ids.NodeIDFromCert — a different value than DeriveMLDSA — so every signed
block (height ≥ 2) failed verifyPostDurangoBlockDelay with errUnexpectedProposer, was
dropped, and rebuilt in an unbounded storm (block 1 survived only because the unsigned
transition block skips the proposer comparison).
The block's offCert slot now carries a scheme-tagged proposer identity [scheme:1B|identity]:
0x90 classical (cert DER → NodeIDFromCert, ECDSA verify) or 0x42 strict-PQ (raw ML-DSA-65
pubkey → DeriveMLDSA(ids.Empty,pub), ML-DSA verify). ML-DSA signing/verification uses a
FIPS 204 §5.2 domain-separation context ('lux-proposervm-block-v1') so a proposer signature
can never be replayed as another ML-DSA message. proposervm.Config gains StakingMLDSASigner
/StakingMLDSAPub, plumbed from StakingConfig through ManagerConfig; exactly one scheme is
active per chain. K=1 nets are unaffected (transition + no-window blocks are unsigned).
Proven on a 5-node strict-PQ local net: sustained multi-block production, every block
built once, gossiped, and finalized byte-identical on all 5 (no automine). Pins consensus
v1.36.6 (engine logger + logged build-drops) which made this diagnosable.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
zap v1.2.2 (zero-copy SetBytes + Builder pool) + utxo v0.5.3 (pooled wire
builders + SetBytesFixed) cut X-chain tx wire composition ~1.9x (2551->1345ns,
37->19 allocs on the isolated composite; the X build path benefits proportionally
without touching P-chain parse, which stays at 705ns/3allocs). P/X/xvm/
components-lux suites all green against the new deps.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Supersedes v1.36.4's build recipe which used a build-time 'replace node => /build'
(forbidden: no local replace directives). Instead, chains v1.7.5 pins the real,
published node v1.36.4 tag, so the baked VM plugins resolve node the normal way —
Dockerfile just clones chains and builds; CHAINS_REF v1.7.4 -> v1.7.5; go.mod
chains v1.7.4 -> v1.7.5; genproto realigned. Zero replace directives anywhere.
tidy + -mod=readonly + cold 'go mod download all' clean.
Note: luxfi node/geth/utxo git tags are being periodically wiped by an external
tag-sync (root cause of the 'unknown revision' failures); all four pinned tags
(node v1.36.4, chains v1.7.5, utxo v0.5.1, geth v1.17.12) re-verified present
immediately before this build races the sync window.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The xvm codec kill depends on utxo/wire's TransferableOut/In + nftfx/propertyfx
envelopes, which were only in the local ~/work/lux/utxo working tree (pinned via
'replace github.com/luxfi/utxo => ../utxo'). That local-path replace works for a
local build but breaks the hermetic Docker/CI build ('open /utxo/go.mod: no such
file or directory'). Published those two additive wire commits as utxo v0.5.1
(clean ff on utxo main, +2 over v0.5.0) and pin it here — the exact code node was
built+tested against. Dropped the replace. Cold-cache 'go mod download all' is now
clean; xvm/components/lux/wallet tests green against v0.5.1.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The codec kill is complete. Every node VM (platformvm, xvm, warp, proposervm,
components/lux) and every chains app-chain VM (bridgevm, zkvm, mpcvm — now native
in chains v1.7.4) marshals via native ZAP struct-is-wire. Nothing imports
node/vms/pcodecs anymore, so the package — the last reflection/serialize-tag
dispatcher (a thin alias over proto/zap_codec's LinearCodec) — is deleted.
- rm vms/pcodecs + vms/pcodecs/pcodecsmock (zero consumers; verified whole tree).
- go.mod: chains v1.7.2 -> v1.7.4 (the pcodecs-free chains), precompile
v0.19.0 -> v0.19.1. Realigned genproto so the split googleapis/rpc module
resolves by longest-prefix (no monolith ambiguity); tidy clean, -mod=readonly
build clean.
- version -> v1.36.4 (constants.go defaultPatch, compatibility.json under the
same RPCChainVM protocol as v1.36.3 — no protocol change, only a codec rip).
There is one and only one way to put a struct on the wire: ZAP.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The non-codec main changes (Dockerfile EVM_VERSION v1.104.8, chains/manager
GetContext size-chunking for behind-validator resync + Quasar EXPORT frontier
bridge, warp/signature, rpcchainvm/zap client) that the branch lacked. The
codec-era main files (codec.go/parse.go/tx.go etc.) are intentionally
superseded by this branch's native-ZAP struct-is-wire — not reintroduced.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Node builds + full test sweep green (155 ok / 0 FAIL) against PUBLISHED
modules only: consensus v1.36.1, zap v1.2.0, utxo v0.3.7. The local zap/utxo
replaces are gone; the upstream nftfx/propertyfx wire (utxo e790d39) ships as
v0.3.8 with the xvm struct-is-wire patch, which is the only remaining pcodecs
consumer and lands in the next release.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Consumes the upstream fx-wire addition (utxo commit e790d39: TypeKindNFT/
Property + 6 shapes + NextEnvelope) needed for the xvm struct-is-wire
migration. Local replace like zap; publish as utxo v0.3.8 with the node
release.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
v1.36 upstreamed round-scoped view-change INTO the engine (internal prevote/POL
in attestation/reconcile/cert), dropping the external hooks config.Parameters.
ViewChange + Runtime.HandleIncomingPrevote. Ripped the node's now-redundant
external plumbing: the ViewChange enable block (LUX_CONSENSUS_VIEW_CHANGE gate),
the quorumKindPrevote gossip routing + HandleIncomingPrevote call, the dead
BroadcastPrevote gossiper method, and the quorumKindPrevote envelope kind. The
engine owns view-change natively now (fail-secure halt on 2a-n>f unchanged).
Whole node builds EXIT 0 on v1.36.1; luxd binary compiles (55M).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
vms/platformvm/... build + vet PASS with pcodecs/txs.Codec gone from the VM.
Executor semantics (standard_tx_executor):
- registerOwnSet(): shared primitive — per-validator state.L1Validator with
native owner blobs (txs.MarshalOwner/UnmarshalOwner, no codec), active-set
capacity check, EndAccumulatedFee=balance+accruedFees, SetNetToL1Conversion
manager-authority recording (byte-for-byte legacy tail).
- ConvertNetworkTx: promote endomorphism (owner-authorized, folds old
ConvertNetworkToL1Tx), gated by security.Mode.Manager.
- CreateNetworkTx: base (AddNet/SetNetOwner) + sovereign path registers own set.
- Deleted CreateSovereignL1Tx + ConvertNetworkToL1Tx.
- txs.UnmarshalOwner added: canonical owner marshal/unmarshal pair, no codec.
All ~13 txs.Visitor impls carry ConvertNetworkTx; gossip/block Parse(b) codec-free;
wallet/network/primary off txs.Codec.
KNOWN GAP (pending design decision, NOT silently green): CreateNetworkTx reads
tx.Chains() only to derive managerChainID — it does NOT create the genesis
chains (no AddChain). Atomic-spawn-with-chains vs decomplect-to-CreateChainTx is
the open call. 17 codec-era _test.go parked as .bak for follow-up rewrite.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Third-layer mainnet finality fix. v1.34.27 (consensus v1.35.36) proved the
dynamic committee (alpha=4 n=5) and topology.go cert-receive canonical resolve
work live — consensus climbed past 1085755 to round-height 1085761 — but hit the
next layer: a stale-alias prevote-lock. viewForLocked seeds lockBlock from a
pre-canonical-fix durable committedSlot (an OUTER proposervm-wrapper id); a
round-0 lock on an outer alias makes prevoteTarget compare the stale outer id
against the inner-canonical winner by raw id, mismatch forever, no POL, freeze.
v1.35.37 (742696baf): stepViewChange rebases a stale-alias lock onto the inner
canonical winner IFF it resolves (via the same vmCanonicalResolver the cert
rebase uses) to exactly winnerCanon — a genuinely different inner (real fork) or
unresolvable lock is left fail-closed frozen, never merged. lockRound preserved,
2a-n>f untouched, alpha inner-precommits were always inner (CanonicalVoteMessage
binds inner, excludes outer). Full engine/chain green (356s, 0 fail); -race
clean; RED safety gate proves divergent inners still refused.
Roll note: luxd-4 first releases its lock (log 'vc stale-alias lock REBASED');
finality past 1085755 needs >=4 nodes on this build, so height climbs as 3->2->
1->0 come up. EVM stays v1.104.7 (head-pin).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Unsticks mainnet C-Chain finality. Root cause (consensus-layer, proven): under
pChainHeight=0 anyone-can-propose, each validator wraps the same inner block in
its own outer proposervm envelope; votes are canonical-keyed so an α-of-K cert
forms network-wide, but HandleIncomingCert did a strict envelope-id lookup — a
node holding a different alias of the same inner block missed it and requested
catchup instead of finalizing the local wrapper it held, so VM.Accept was never
called and the EVM head froze at 1085755 while consensus logged 'finalized
voters=4'. v1.35.34 (66438a23b) resolves the local wrapper by canonical id and
rebases the verified cert (outer ids unsigned; α votes verify unchanged), no
fork (per-height gate keys on canonical id, idempotent).
Also: dynamic committee 1→N proven (v1.35.35, effectiveCommittee sizes cert +
view-change from live validator count; n=1→1/1,2→2/2,3→3/3,4→3/4,5→4/5 — the
BFT-safe ⌊2n/3⌋+1); view-change safety gate realigned to the effective
committee (RED#2); snowman comment-purge (Quasar/Nova); dynamic committee logs.
Node commits: 4e71814e58 (proposervm→EVM Accept-cascade test, RED#1),
c13681108f (purge), a02611413e (logs). EVM stays v1.104.7 (head-pin). Full
engine/chain suite green under -race (369s, 0 fail/0 race); RED adversarial
suite proves scale-invariance 1→1M + RLP seam byte-exact on real mainnet state.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Restores mainnet C-Chain finality. The prior stall: with 5 live validators but
MainnetParams K=21/alpha=15, BOTH finality gates (assembleCertLocked cert-alpha
AND view-change POL) required 15 distinct votes — impossible from 5 validators,
so the chain froze (safe, no fork). consensus v1.35.33 sizes both gates from the
live validator count via effectiveCommittee (alpha=4 for n=5), inheriting the
minBFTCommittee K=4/alpha=3 floor (1085013 self-finality guard preserved);
Snowman K=21 sample untouched. node chains/quorum.go adds
validatorStakeSource.ValidatorCount (height-indexed, deterministic). EVM stays
v1.104.7 (head-state pin). Test: 5-validator MainnetParams control freezes,
fix converges in 0.35s.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Same content as v1.34.24 (v1.104.3-headpin == v1.104.7, retagged per semver
policy: real monotonic patch versions, no suffix tags). go.mod has no replace
directives; removes the leftover comment from an already-completed cascade.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The COMPLETE mainnet reliability build. v1.35.29 adds FIX#1 (self-finality floor — a
transient bftCommittee count can never self-finalize without quorum; the 1085016 accept-
without-quorum safety hole) + FIX#3 (canonical vote aggregation — sibling wrappers of the
same inner block combine into ONE cert; the block-288 wrapper-split cert-termination stall)
+ the v4 clone-resign inner->outer fix (my v1.35.28 re-sign silently no-op'd on real wrapped
blocks). Matcher EVM v1.104.3-hotfix + Bug B UNCHANGED — execution-identical to mainnet.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
MAINNET-COMPATIBLE node-only patch: the v1.34.14 line + catch-up/preference fixes + vote-
guard, EVM PLUGIN UNCHANGED (EVM_VERSION=v1.104.3, precompile v0.19.0, chains v1.7.2, vm
v1.2.5 — byte-identical to mainnet; NO settle-only, NO state-transition change). go.mod delta
vs v1.34.14 = consensus ONLY. Consensus v1.35.28 touches vote/cert LIVENESS only (engine is
byte-identical across the lines); it carries #2 (GetBlock-guarded build-tip steer) + the
vote-guard clone-artifact re-sign fallback. Cherry-picked #1/#3/#5 (proposervm validate-
before-assign, EmptyNodeID->real peers, have-block!=not-behind cert-fetch) on the prior commit.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
v1.34.12 crash-looped on boot:
panic: threshold: scheme BLS already registered
consensus <= v1.35.22 blank-imported the OLD crypto/threshold/bls in
quasar.go while node imports the NEW threshold/scheme/bls; both register
BLS into the crypto/threshold registry -> panic. consensus v1.35.24
repoints quasar.go to threshold/scheme/bls.
Verified on the FULL node binary dep closure (go list -deps ./main, 1028
pkgs): luxfi/crypto/threshold/bls = 0, luxfi/threshold/scheme/bls = 1 —
exactly one BLS registration path, the panic cannot recur.
Pins: consensus v1.35.24, chains v1.7.2, evm v1.104.3.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Supersedes the v1.34.11 image build, which failed in the Docker plugin
stage: chains v1.7.1 pulled consensus v1.25.35, whose polaris.go imports
the removed pulsar/ref/go/pkg/pulsar path — the quantumvm + mpcvm plugins
could not build and their hard COPY failed.
- chains v1.7.1 -> v1.7.2 (consensus bumped to v1.35.22 there; consistent
pulsar graph; all 10 chain VM plugins verified building standalone).
- Dockerfile CHAINS_REF v1.7.1 -> v1.7.2.
- consensus v1.35.21 -> v1.35.22 (Pike-clean of the ProposalKey liveness
fix; logic identical to v1.35.21).
Pins: consensus v1.35.22, chains v1.7.2, evm v1.104.3. Off origin/main,
no WIP.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
IMAGE/BUILD change (packaging only — the consensus behavior change ships
separately as luxfi/consensus v1.35.21). Two edits make the deployable
node image build with the fixed consensus and the current chain plugins:
- consensus pin v1.35.20 -> v1.35.21 (ProposalKey decomplection, commit
776a5119c: outer block IDs are transport aliases, proposal identity is
ProposalKey{ParentID,Height} — proposervm envelope churn can no longer
spawn own sibling candidates that split votes; the mainnet 1082880 fix).
- Dockerfile ARG CHAINS_REF v1.7.0 -> v1.7.1. v1.7.0 still shipped the
pre-rename `thresholdvm` dir, so the Dockerfile's `cd /tmp/chains/mpcvm`
plugin build produced nothing (swallowed by `|| echo WARN`) and the hard
COPY of the mpcvm plugin (tGVBwRxp...) failed — the v1.34.10 Docker build
break. v1.7.1 carries the mpcvm rename; the mpcvm plugin builds clean
(verified standalone, 16MB) and all 10 chain plugins are present.
Pins verified for the canary image: consensus v1.35.21 (=776a5119c),
chains v1.7.1, evm v1.104.3, cevm v0.19.0, dex v1.5.15. Built off
origin/main — no WIP from any other worktree.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Pins node to the published tags carrying this session's LP-0130 work:
ids/constants add M/F chain identifiers (drop T-Chain), genesis adds
M/FChainGenesis, chains has the mpcvm rename, consensus has the
mempool-churn pre-build gate + threshold scheme/bls repoint.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Pairs the proposervm stale-parent slot-snap churn-fix (f840336167) with consensus
v1.35.17 (view-change abandonment must never silence a height). Together they close the
distributed VC liveness stall that keeps mainnet C-Chain safe-but-paused: slot-snap kills
the stale-parent rebuild sibling explosion at the source (one stable candidate per slot),
and v1.35.17 keeps the round machinery driving the height to convergence instead of going
silent after rePoll's 8-attempt cap. Liveness-only; decided-floor / no-double-finalize
safety invariant untouched. Gated on the live idle-inclusive devnet re-storm + RED before
any testnet/mainnet roll.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Harden the bootstrap checkpoint — the one path that bypasses the beacon quorum —
into a cryptographically SIGNED weak-subjectivity anchor.
- Checkpoint carries an authority Signature over a domain-separated canonical
(id,height); CheckpointVerifier (injected, backed by a proven primitive —
Ed25519/BLS, never custom crypto) authenticates it. The policy stays crypto-free
exactly like AncestrySource/heightOf.
- AcceptsFrontier trusts a pinned checkpoint ONLY when the configured authority
signed that exact (id,height). Present-but-unsigned, signed-by-a-non-authority,
signature-transplanted-to-another-(id,height), and no-verifier-wired all FAIL
CLOSED. A compromised flag cannot inject a false sync anchor without also forging
the authority's signature. It is a cryptographic vouch, NEVER a ⅔-live-stake tally.
- No production path pins a checkpoint yet (opt-in operator escape hatch), so the
invariant is enforced for when it is activated; nothing to wire now.
Folds consensus v1.35.16 (single ⅔ formula + leaf-lock doctrine) into the node.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Make the bootstrap-trust vs live-finality boundary a HARD API split so neither
can impersonate the other — the mass-recovery deadlock was bootstrap reusing the
finality quorum (>2/3 of CURRENT stake), which is unsatisfiable when the
recovery targets are themselves down validators.
- FinalityQuorum.HasFinality(weight,total): the live rule, strict >2/3 of current
stake (unchanged). Renamed from ConsensusQuorum to the reviewer's exact spec.
- BootstrapTrust.AcceptsFrontier(replies): selects a weak-subjective sync anchor
from AUTHENTICATED CONFIGURED beacons — a response FLOOR (MinResponses) plus a
supermajority over the RESPONDERS, NEVER over the whole set. Distinct type, so
bootstrap cannot call the finality predicate (INVARIANT 3: acceptance != finality;
the node re-executes every synced block before re-entering consensus).
- Contrast proven: 3-of-5 bootstrap ACCEPTS a frontier, yet HasFinality(3/5) is
false and STAYS false after sync — bootstrap is not a finality bypass.
Folds consensus v1.35.15 (the global unlock-before-call-out invariant + the
n=1..10 quorum acceptance matrix) into the node binary.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
consensus v1.35.13 -> v1.35.14 (decomplect: removed the selfVoter shortcut that
self-deadlocked on t.mu.RLock — the live n=1 freeze; K==1 now finalizes solely via the
inline finalizer, one path). EVM_VERSION v1.103.0 -> v1.104.0 (= v1.103.0 + accept-empty:
a consensus-finalized empty block Accepts, so VM.Accept can never fail-closed on an empty
first block — the stale-evm-pin gap). Unblocks single-validator Zoo mainnet/testnet +
Hanzo (all frozen at block 0).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The v1.34.1 n=1 fix passed unit tests but the LIVE single-validator chains still
froze (Zoo mainnet/testnet, Hanzo, Pars, all block-0). Root cause (neo's live repro,
confirmed through the full runtime path): a reentrant-RWMutex self-deadlock — the
single-node selfVoter called engine.ReceiveVote synchronously while buildBlocksLocked
held t.mu, so ReceiveVote's t.mu.RLock deadlocked on the same goroutine (node hangs at
'single-node mode: self-voting', inline finalize never runs, block never decides).
consensus v1.35.13 dispatches the selfVoter's ReceiveVote on a fresh goroutine (no
reentrant lock) — the block now DECIDES through the real NewRuntime path (proven by
TestBlue_SingleValidator_DecidesThroughFullRuntimePath: hangs pre-fix, passes post-fix).
The bump also carries the n=1 synthesize fix (v1.35.11) and the dormant 1→N
decentralization guard (v1.35.12). EVM_VERSION unchanged (v1.103.0) — single-validator
blocks are tx-driven / non-empty.
Gates Zoo mainnet + Zoo testnet + Hanzo (all single-validator).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Prod-line (v1.34.x finality-admission) bump carrying the consensus fix for the
single-validator (K=1) DECIDE stall that wedged all three sovereign
single-validator chains (Zoo 200200, Hanzo 36963, Pars 494949): a K==1 block now
finalizes even when its self-vote is unverifiable against a not-yet-resolvable
single-validator set (synthesized 1-of-1 cert; per-height FinalizeBranch gate is
the safety). n>1 C-Chains (mainnet/testnet) unaffected — they never hit the K()==1
branch.
The bump also carries the (dormant) bounded phantom-floor reconcile
(v1.35.9/v1.35.10) — additive engine methods that this prod-line node never calls
(manager.go on main has no ReconcilePhantomFloor wiring), so behavior is unchanged.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Adds the per-deployment opt-in flag: when LUX_CONSENSUS_VIEW_CHANGE=true and K>1, sets
consensusParams.ViewChange=true so devnet/testnet activate the round-scoped view-change
without a mainnet default (mainnet owner-gated). The engine fail-secure HALTS if the committee
fails 2a-n>f, so enabling never weakens safety. Bumps consensus v1.35.6->v1.35.7 (removes dead
safeConfig dup). This is what makes v1.32.13's prevote transport actually activatable.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
consensus v1.35.5 is a patch-only fix (go.mod byte-identical to v1.35.2..v1.35.4): the
decided-height sign-gate floor is now seeded DIRECTLY from vm.LastAccepted at engine Start
(before signing) and in SyncState, so a node upgrading IN PLACE from a legacy v1 vote-guard
file (floor 0) has a real decided floor from the first instant of boot — closing the
mainnet v1->v2 upgrade window where the floor was 0 until the first post-upgrade finalize.
Sign-gate-only (never enters byHeight/ledger.Height; PART-A intact; only refuses more).
Supersedes the un-rolled node v1.32.9 (consensus v1.35.4) for the mainnet-first roll. No
node code change (fix is internal to the consensus engine). go mod tidy drops an orphaned
luxfi/bft indirect (pre-existing). Full node builds clean on linux/amd64 (cgo off).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
RECOVERY BUILD for the frozen testnet C-Chain. Carries BOTH fresh-multi-node fixes:
- STATE halt (missing trie node) — evm v1.101.2 MaterializeState, from the v1.32.7 tree.
- CONSENSUS double-finalization + liveness stall — consensus v1.35.2 (epoch-blind
vote-once + per-height vote convergence + RED-round hardening).
Deliberately based on v1.32.7, NOT node v1.33.0: v1.33.0 is a mis-numbered DISPROVEN
build (older commit, MISSING the evm v1.101.2 MaterializeState commit, pins the buggy
consensus v1.33.3) — basing on it would regress the state-halt fix. This branch also
EXCLUDES node-main's /ext->/v1 route migration to keep the incident recovery minimal
and avoid breaking /ext/bc/C/rpc tooling mid-recovery.
Recommended tag: node v1.33.1 (forward past the v1.33.0 revert-magnet so the image
tooling cannot revert testnet to the disproven build). NOT tagged here — cutting the
tag may auto-trigger a live deploy; that is a live-incident action requiring explicit
owner authorization.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Pick up consensus v1.33.3 (durable + epoch-keyed + funnel-pruned per-height
vote-once guard) and ACTIVATE its durability in production: buildChain now opens a
per-chain VoteGuardStore (chain.OpenVoteGuard at <chainDataDir>/vote-guard) for
every K>1 signing chain and passes it to the engine via NetworkConfig.VoteGuard.
This closes Red's HIGH-1 on the wire that matters: a signing validator's
(height,epoch)→canonical bindings are fsync'd before it votes and reloaded on
startup, so a crash between casting a vote and finalizing the height cannot forget
the binding and let the restarted node sign a conflicting sibling — the cross-node
fork with zero Byzantine intent under a rolling upgrade / eviction / OOM. The store
is opened at the node boundary so the fail-closed-on-corrupt decision (a signer
refusing to start with equivocation memory it cannot trust) lives here, not in the
engine. luxd builds against pinned v1.33.3.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
consensus v1.33.2 closes the CRITICAL cross-node fork Red found in v1.33.1: the
finality layer now enforces per-height vote-once (an honest validator signs at most
one canonical per height, both signing sites through reserveSlotForSign), so two
conflicting siblings can no longer both gather a >=2/3-stake cert. All 3 Red fork
repros GREEN, emergent probe 15x no fork, engine/chain suite green under -race.
luxd builds clean. Ships with the proposervm liveness fix + luxd-3 ZAP block-id guard.
Adopts consensus v1.33.1: the build-path re-solicit of an undecided own
proposal (avalanche repoll alignment) plus the v1.33.0 QCv2 canonical-
commitment finality + incident-1082814 verify-before-slash. Pulls transitive
ids v1.3.0, warp v1.24.0, pulsar v1.9.0, threshold v1.12.0. luxd builds
(GOWORK=off, pinned deps). No node code changes required — consensus public
API (NewRuntime/NetworkConfig/Runtime) is stable across the bump.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Pins the incident-1082814 durable fix. consensus v1.32.1 supersedes both the
down-leader line (v1.31.1, what main pinned) and the QCv2-core-only line
(v1.31.2, what node v1.31.5 shipped). Finality keys on the canonical inner
execution commitment; equivocation evidence requires a VERIFIED alpha-of-K cert
over a DIFFERENT canonical, removing the false-slash fatal-exit crash that
destabilized mainnet under DEX-fill load on v1.31.5.
Adds indirect github.com/luxfi/bft v0.1.5 (cert verify gate). Full node module
compiles clean; consensus proof suite 377 subtests + race + vet + fuzz green.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
consensus v1.31.1 fixes the down-leader HALT: when a slot-leader proposer is
down, validators built competing siblings at the same height, the alpha-of-K
votes split, no cert assembled, and the chain stalled (devnet stuck at height
36 with 4/5 healthy). The fix converges all validators onto one deterministic
build tip (PreferredBuildTip) so blocks finalize through a validator going down.
Transitive: corona v0.8.0->v0.10.2, dkg v0.3.0->v0.3.5, pulsar v1.2.0->v1.7.1
(the dealerless PQ line consensus v1.31.1 is built against). luxd builds clean.
consensus v1.31.0 on top of the v1.30.0 sibling-tolerant finality already shipped in
node v1.31.1: the finality decomplect (pure Ledger,Cert,DAG->Ledger',Plan fold;
markFinalized unwriteable), topological.go preference layer, the RED HIGH-1 clone()
O(window) perf fix (was O(chain height) — 86.7ms/100MB per finalize at 10^6 heights),
and RED LOW-1/LOW-2 hardening. Backward-compatible: node compiles with zero API drift;
proposervm + chains green. No transitive crypto-dep bumps.
Consume the sibling-tolerant finality fix: cert-driven FinalizeBranch reorg
replaces the per-height admission gate that deadlocked the proposervm
pre-fork->post-fork transition (devnet stuck at finalized height 1 while
blocks produced past 10). Base = v1.31.0 (= v1.30.101 union quasar PQ-finality
+ frontier self-vote) + the repliedCovers eclipse-defense cherry-pick.
Verified: go build ./... clean; chains + proposervm tests green against v1.30.0.
On a FRESH net a validator whose own stake makes the PEER-ONLY responder weight
fall below the stake-majority NAMING floor (a heavy validator, a small beacon set
like the P-chain's CustomBeacons, or skewed stake: peers = total-self, so
peers < total/2+1 ⟺ self > total/2-1) could NEVER name a frontier even with the
WHOLE validator set connected — FrontierTip returned FrontierConnecting forever
("bootstrap waiting for beacon connectivity before naming the frontier") and the
node hung, blocking uniform block production.
The node is itself a beacon and knows its OWN accepted tip with certainty. Add the
SELF-VOTE: in the below-floor case, when the node has reached its ENTIRE beacon set
(fullyConnectedBeacons — no eclipse can hide an ahead-tip) and that full set PLUS
itself unanimously hold a tip it has ALREADY ACCEPTED with nobody ahead, return the
new chainbootstrap.FrontierCaughtUp -> the loop completes at the node's own tip.
Safety (proven by tests): self is counted ONLY under FULL connectivity, so an
eclipse suppressing any beacon breaks full-set and falls back to the
partition-capture floor (fail safe) — self can never tip a PARTIAL view into a
false caught-up (stale-go-live guard holds). AcceptsFrontier still tallies PEERS
ONLY, so a behind node's ahead-frontier is named undiluted and it still syncs; a
peer above our height defeats CaughtUp. self only vouches for its OWN accepted tip
so C1 (forged-frontier naming) is untouched.
- selfNodeID threaded from m.NodeID into the blockHandler.
- helpers fullyConnectedBeacons + withSelfVote.
- bump luxfi/consensus v1.29.0 -> v1.29.1 (FrontierCaughtUp status).
Tests: TestNodeBootstrap_FreshNet_SelfVoteUnderFullConnectivity_CaughtUp,
TestNodeBootstrap_SelfVote_PeerAheadDefeatsCaughtUp,
TestNodeBootstrap_SelfVote_PartialConnectivityFailsSafe. Full chains suite green.