Picks up the 2026-06-06 relicensing:
- luxfi/vm v1.2.0 → Lux Ecosystem License v1.2 (was Lux Research v1.0).
License-only retag of the extracted runtime; patent reservation
preserved for runtime optimization surfaces, royalty-free for
Descending Chains.
- luxfi/sampler v1.1.0 → BSD-3-Clause (was Lux Research v1.0).
License restored to match the luxfi/node provenance (originally
extracted from node/utils/sampler).
- luxfi/staking v1.5.0 → BSD-3-Clause (was Lux Research v1.0).
License restored to match the luxfi/node provenance.
No code changes in this commit — go.mod/go.sum only.
Drop codec/wrappers import in trie_segments + statesynctest helpers.
luxfi/utils/wrappers has identical API (Packer, ByteLen/ShortLen/
LongLen, Errs) — it has been the canonical home since utils v1.1.x.
Bumps utils to v1.1.5 for the new json subpackage now used elsewhere
in the codec rip; statesync only uses /wrappers.
predicate/predicate_results.go:
Hand-rolled big-endian wire for map[Hash]map[Address][]byte:
u16 version | u32 outer | (32B hash, u32 inner, (20B addr, u32 len, bytes)*)*
Sorted by hash then address for deterministic output — block-hash
load-bearing across validators. Test suite (golden-byte multi-tx
cases) passes.
warp/messages/{codec,payload}.go:
Drops codec.Manager polymorphic registry — single payload family
(ValidatorUptime) doesn't need reflection. Fixed 44-byte layout:
u16 version | u16 type | 32B validation_id | u64 total_uptime
Parse() dispatches on type discriminator (u16) — extensible without
the codec dependency. initialize() / Bytes() / Parse() API unchanged.
Both Bytes()/Parse() public APIs preserved — consumers don't change.
Previous ExportChain returned (bool, error) and would re-export from
scratch every call, leaving partial files with no marker on Ctrl-C.
Now returns *ExportChainResult with first/last block hashes and a Status
field ("ok" | "noop" | "interrupted"). Behavior:
* Atomic JSON sentinel at <file>.sentinel (tmp+rename) updated every
1024 blocks and on terminal states.
* Idempotency: if file exists with a "done" sentinel matching
{first, last, firstHash, lastHash}, returns Status=noop without
re-exporting. Safe to call from a cron CronJob.
* Interrupt: ctx.Done() at every block boundary writes a sentinel
with Status=interrupted + HighestExported so the caller can resume.
* Block iterator still streams (no whole-chain buffering); gzip path
unchanged.
Symmetric with admin_importChain which returns *ImportChainResult, and
with the cmd/rlp-export CLI shipping alongside.
Server-side test coverage: writeSentinel atomicity, readSentinel
absent + corrupt fall-through, JSON shape pinning for both the
sentinel and ExportChainResult, exportProgressInterval stability.
vm v1.1.6 drops GetNetworkUpgrades() (removed in runtime decomplect
3eea32ad). Required for v0.19.0's Etna -> Quasar rename: vm v1.1.5
still expected upgrade.Config to implement runtime.NetworkUpgrades
with the old IsEtnaActivated method, which no longer exists post-rename.
Transitive: luxfi/protocol v0.0.5 -> luxfi/proto v1.0.0 (vm v1.1.6
dep tree).
precompile v0.5.35 (commit 794912f) fixes a Key() collision in the 7
EIP-2537 bls12381 sub-configs: G1Add, G1Mul, G1MSM, G2Add, G2Mul,
G2MSM, Pairing each now return their own ConfigKey rather than all
returning G1AddConfigKey. Without this, only one of the 7 entries
could land in the registry's ordered upgrade map — DisallowUnknownFields
parse would reject any upgrade.json with the other six.
Lux Neo had to drop the 7 bls12381 entries from mainnet upgrade.json
(78f2ecb) to ship #114 because of this bug. With v0.5.35 they can be
re-added forward-dated to Quasar (Unix 1766708400).
Bundles into the next evm release tag (v0.19.1).
Quasar Edition rip: there is one and only one upgrade-key namespace.
The strict decoder forces parse-time failure on any unknown field —
which catches typos and stale aliases (e.g. etnaTimestamp) at config
roll instead of silently muting forks at activation.
Why this matters: encoding/json by default ignores unknown JSON keys
and zero-initializes the corresponding struct fields. After the
EtnaTimestamp → QuasarTimestamp rename, any deployed upgrade.json
still carrying etnaTimestamp would parse cleanly with the new field
left nil, and the fork would never activate. The strict decoder
turns that silent-disable failure mode into a loud boot-time error.
Pairs with: luxfi/genesis kill-etna sweep + node v1.28.22 plugin bump.
A genesis header with Cancun active (CancunTime=0) populated the Cancun fields
(ParentBeaconRoot/ExcessBlobGas/BlobGasUsed) but left WithdrawalsHash nil,
because the assignment was guarded by num.Sign()!=0 (skipped at block 0). RLP
encodes post-merge fields positionally, so a nil WithdrawalsHash followed by
present Cancun fields yields a header ReadHeader cannot decode:
rlp: input string too short for common.Hash, decoding into ...WithdrawalsHash
NewHeaderChain then fabricates a fake genesis header, which has the wrong state
root, so block-building on a fresh L1 fails and the chain produces zero blocks.
Cancun implies Shanghai, so a Cancun header MUST carry WithdrawalsHash. Set it
(EmptyWithdrawalsHash) whenever Cancun is active and it's nil. Chains preserving
a pre-withdrawals genesis hash opt out via SkipPostMergeFields (short-circuits
the whole post-merge block), so they're unaffected.
Verified on lqd devnet (chainId 8675312): genesis header round-trips, balance
readable at 0x0, self-transfer mines block 1.
publish_antithesis_images.yml pushed to molten-verve-216720/avalanche-repository
(ava-labs GAR). Per workspace policy, lux containers go to GHCR not GAR; the
trigger-antithesis.yml that remains is already rebranded to tenant=lux. Also
fix stale 'Avalanche linters' comment in .golangci.yml.
Mirrors the mainnet UnmarshalJSON regression test for the lux-testnet
primary C-Chain. Embeds testnet_upgrade_v47.json (47 entries) and
asserts every key round-trips through extras.UpgradeConfig.UnmarshalJSON
against the live precompile registry.
If a future luxfi/precompile bump adds a new ConfigKey without the
corresponding RegisterModule(), or if the canonical
~/work/lux/genesis/configs/testnet/upgrade.json re-introduces an
unregistered EIP key (kzg4844Config / secp256r1Config / ed25519Config),
this test trips before the JSON ships to the cluster — closing the same
brick-on-boot footgun Red flagged on mainnet (vector 8 CRITICAL).
Sync rule: re-vendor testnet_upgrade_v47.json whenever the genesis
canonical changes — same pattern as mainnet_upgrade_v46.json.
params/extras_e2e_test/: new hermetic e2e regression-proof test that
unmarshals the canonical luxfi/genesis configs/mainnet/upgrade.json
(embedded via //go:embed) through extras.UpgradeConfig and asserts:
- precompileUpgrades count = 46
- all known config types resolve via the registry
- no unregistered EIP entries leak through
- networkUpgradeOverrides round-trip stable
Closes Red round-2 vector V12.
.github/workflows/ci.yml: drop `continue-on-error: true` from the
"Run short tests" step (kept on dependency-install and ci_setup
steps that legitimately have flaky external network deps).
CGO_ENABLED=0 still dodges the duplicate _randombytes symbol
collision from luxfi/accel cgo deps, but TEST FAILURES in that mode
must now gate CI per Red round-2 vector V19. Without this gate the
new e2e test could silently regress.
On lux-mainnet 2026-06-02 every newly-created L2 EVM chain (hanzo, zoo,
spc, pars) panicked at chain create with:
panic in eth.New: unable to commit genesis block to statedb:
triedb parent [0x56e81f17171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421] layer missing
Root cause: the operator does not specify state-scheme in chain config, so
vm.ethConfig.StateScheme = "" enters eth.New. backend.go then calls
customrawdb.ParseStateSchemeExt("", chainDb) which delegates to
geth/rawdb.ParseStateScheme — upstream returns PathScheme as the implicit
default for any database with no stored scheme (every fresh L2). That
silently re-enables path mode even though vm.go's preflight at the top
of Initialize hard-refuses it. Genesis commit then runs through pathdb's
Update with parentRoot=EmptyRootHash and dies the moment the layer tree
is consulted.
Fix: extract the scheme normalisation into resolveStateScheme() and lock
the empty-string default to HashScheme here, before backend.go can inherit
geth's path-by-default. Path explicit is still refused as before, so this
is purely an operator-default fix.
Adds:
- plugin/evm/vm_statescheme_test.go: TestResolveStateScheme covering all
four input cases (empty→hash, hash passthrough, path refused, custom
passthrough).
- core/genesis_pathdb_test.go: TestGenesisCommit_PathDB_{FreshDB,
SetupGenesisBlock_FreshDB} document the upstream pathdb genesis-commit
invariant on a fresh in-memory DB so future drift here is caught
immediately.
Also bumps compatibility.json to register v0.18.4-v0.18.18 with the
existing RPC chain VM protocol (42); restores TestCompatibility (which
was failing on main since v0.18.4 because the table was never updated).
Version: v0.18.17 -> v0.18.18.
luxfi/genesis is now indirect (no direct EVM imports), but tidy pulled in
fresher minimum versions of its transitive deps:
- constants v1.5.2 -> v1.5.7
- crypto v1.19.6 -> v1.19.15
- metric v1.5.1 -> v1.5.5
- precompile v0.5.23 -> v0.5.27
- otel v1.42.0 -> v1.43.0
Plus drops StephenButtolph/canoto, fatih/structtag, grpc-prometheus from
indirect block (no longer transitively required).
No functional changes; this is purely the dependency cleanup that follows
the luxfi/genesis v1.13.8 bump.
Bug fix (closure-swarm v1.28.15 boot blocker):
PQ gate at plugin/evm/vm.go was setting vm.chainConfig.PQ BEFORE
vm.chainConfig was assigned from g.Config — chainConfig was nil at
that point, panic on dereference. Moved the gate AFTER assignment.
Repro: fresh-PVC bootstrap with chain-config.json pq:true caused
"runtime error: invalid memory address or nil pointer dereference"
in the EVM ZAP plugin → C-Chain never bootstraps. Discovered during
localnet 1337 bring-up against v1.28.15.
Fix: move the gate to AFTER `vm.chainConfig = g.Config`. PQ profile
is now correctly installed on a non-nil chainConfig.
Added:
- cmd/rlp-verify/main.go — decodes C-Chain RLP export, verifies
genesis hash matches genesis JSON, each block's ParentHash chains
cleanly. What admin.importChain does sans state execution; sufficient
proof that an RLP+genesis pair is wireable to a luxd boot.
Indirect dep bumps:
- github.com/ChainSafe/go-schnorrkel v1.1.0 (sr25519Verify precompile)
- github.com/cosmos/go-bip39 v1.0.0 (bridgeRegistrarConfig precompile)
- github.com/luxfi/ai v0.1.0 (aiMiningConfig precompile)
Build tag: drop `gpu` tag on darwin gpu_bridge.go (parallel/gpu is one
concept; the darwin gpu bridge always compiles when cgo+darwin).
Two changes for the Quasar Edition rollout:
1. precompile/registry/registry.go side-effect imports the 16 modules
that ship in the canonical upgrade.json but were not yet wired into
the registry: attestation, babyjubjub, bls12381, bridge, compute,
curve25519, ed25519, math, pasta, pedersen, poseidon, sr25519,
stableswap, vrf, x25519, xwing. Without these imports each module's
init() never runs, so modules.RegisteredModules omits them and
UpgradeConfig.UnmarshalJSON rejects the activation with
"unknown precompile config: <key>", bricking luxd boot.
2. params/extras/precompile_upgrade_rollout_test.go (NEW) is the
forward-compat regression gate against the live lux-mainnet config.
Five subtests:
- IsForwardCompatibleWithLiveActivations: every precompile
activation currently in the lux-mainnet StatefulSet inline
upgrade.json (18 entries at blockTimestamp:0 including the
dead* family) MUST be preserved at the same blockTimestamp in
the canonical upgrade.json, else checkPrecompileCompatible
refuses boot.
- PrecompileTimestampsAreMonotonic: verifyPrecompileUpgrades
rejects non-monotonic timestamps; we verify the canonical
config respects this.
- WarpRequiresPrimaryNetworkSigners: warpConfig MUST pin
requirePrimaryNetworkSigners=true (red-review finding #8).
- HasStrictPQActivation: networkUpgradeOverrides MUST set
strictPQTimestamp=1766708400 (Quasar timestamp) so
contract.RefuseUnderStrictPQ fires.
- HasStrictPQTrue: ~/work/lux/state/chain-configs/lux-mainnet/
config.json MUST set "pq": true so the EVM plugin boots into
strict-PQ posture.
The test uses raw JSON parsing (not typed UpgradeConfig.Unmarshal) so
it does not depend on the registry side-effect imports being loaded
in the test binary — avoiding cgo linker conflicts between sr25519's
NaCl-compat randombytes and the HQC PQClean randombytes shim.
Red review findings #2, #7, #8.
Lux mainnet C-Chain canonical genesis hash is
0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e,
matching the 1,082,781-block historic RLP export at
lux-mainnet-96369.rlp. The genesis JSON activates Cancun at
genesis time (cancunTime=0) for MCOPY etc., but the genesis BLOCK
itself predates Cancun and must use the 16-field pre-Shanghai
header format to preserve hash continuity with the historic chain.
luxfi/geth's core.Genesis exposes SkipPostMergeFields for exactly
this case. luxfi/evm shipped the JSON unmarshaling but ignored the
flag in toBlock(), so any post-merge field that fork-activates at
genesis time (ParentBeaconRoot, ExcessBlobGas, BlobGasUsed,
WithdrawalsHash) gets baked into the header and shifts the
computed genesis hash, e.g. to 0x1ade42ec…. luxd then panics
during EnsureGenesisState because the resulting state root has no
parent in pathdb's layertree ("triedb parent
[0x56e81f17…] layer missing"), which blocks mainnet C-Chain
bootstrap and admin_importChain.
Mirror the upstream geth pattern: gate the entire Shanghai+
post-merge field assignment block with `if !g.SkipPostMergeFields`,
and round-trip the flag through MarshalJSON/UnmarshalJSON in
gen_genesis.go.
Verification (TestSkipPostMergeFieldsGenesisHash pins both):
- `skipPostMergeFields: true` → 0x3f4fa2a0… (canonical, RLP block 0)
- flag absent / false → different hash (gate is active)