`go build ./...` failed outright — the v1.16.x line was DELETED from
github.com/luxfi/geth (newest is v1.20.1) while proxy.golang.org still serves
it, and GOPRIVATE forces a direct fetch for luxfi/*, so resolution fails with
"unknown revision v1.16.98".
Not fixable by tidying here: the requirement is baked into PUBLISHED modules,
which pin the dead tag and in some cases explicitly `exclude` the alternative.
The first versions that moved off it:
luxfi/crypto v1.20.2 (every version through v1.20.0 pins the dead tag)
luxfi/constants v1.6.2
luxfi/warp v1.24.1
Requiring those is what escapes it; geth then resolves to v1.20.1. Companion
luxfi modules moved with them at minor level within v1.x / v0.x — no major jumps.
Verified: go build ./... goes from exit 1 to exit 0. go test ./... is 1 ok / 3 fail. Those tests were NOT reachable before —
the packages could not compile — so this exposes pre-existing failures rather
than causing them.
constants v1.6.1 references ids.MChainID/ids.FChainID (M-Chain=MPC,
F-Chain=FHE); ids v1.3.1 is the first tag that defines them. Unblocks
the module build (was: undefined: ids.MChainID). Builds green: go build
./... exit 0, pkg/genesis tests pass.
genesis.LightMnemonic becomes a backward-compat re-export of
light.Mnemonic (github.com/luxfi/light v1.0.0) — one source of truth for
the public dev seed, no local replace directive, published semver.
Orthogonal to the pre-existing constants v1.6.1 <-> ids MChainID/FChainID
skew that blocks this module's full build (verified: ids resolves to
v1.2.10 with and without this change; identical errors either way).
Co-authored-by: Hanzo AI <ai@hanzo.ai>
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.
This is the third decomplect layer:
1. builder/ now a nested module (github.com/luxfi/genesis/builder)
— uses luxfi/utxo, vm, database, proto
2. pkg/genesis/security/ now a nested module
— uses luxfi/consensus for ChainSecurityProfile verification
3. do_transfer_test.go moved from pkg/genesis/ → builder/
— it imports luxfi/utxo/secp256k1fx which was forcing the entire
v1.27.x-era dep cascade onto downstream consumers via test-deps
in the module graph
Result on the root module's MVS-visible graph:
- consensus v1.25.0 → v1.22.84 (matches luxd v1.23.42 family)
- database v1.18.3 → v1.17.44 (matches luxd v1.23.42 exactly)
- validators v1.2.0 → v1.0.0 (matches luxd v1.23.42 exactly)
- threshold v1.6.17 → GONE (no longer in graph at all)
- node v1.27.24 → v1.20.3 (transitive, harmless)
Closes the v1.23.x backport blocker for canonical evmAddr/utxoAddr.
luxd v1.23.43 (genesis bump) can now adopt the canonical genesis
schema without dragging the v1.27.x post-quantum threshold refactor
into a v1.23.x production line.
One-direction module dep:
- github.com/luxfi/genesis (data types — minimal deps)
- github.com/luxfi/genesis/security (verification — uses consensus)
- github.com/luxfi/genesis/builder (tx-building — uses utxo/vm/db)
- github.com/luxfi/genesis/cmd (tools — uses node)
Never the reverse.
* decomplect: move SecurityProfile Resolve to pkg/genesis/security
pkg/genesis core types (SecurityProfile struct) stay free of
luxfi/consensus dep. The verification gate (ResolveProfile + hash
comparison) moves to pkg/genesis/security which imports both.
Result: downstream consumers that only need to parse canonical
genesis data (luxd v1.23.x line, tools, indexers) can do so without
dragging luxfi/consensus + luxfi/threshold + luxfi/validators
(Corona→Corona refactor) into their go.mod.
go list -deps ./pkg/genesis → no luxfi/consensus.
go test ./pkg/genesis/security/ → all 5 F102 verification tests pass.
Closes the dep cascade that blocked the v1.23.x backport of
evmAddr/utxoAddr canonical genesis. One and one way only:
data lives in pkg/genesis, verification lives in pkg/genesis/security,
consensus consumes both — never the reverse.
Closes#93 Phase 1.
* decomplect: split cmd/ into nested module (no luxfi/node in root go.mod)
The Phase 1 split (SecurityProfile struct in core, Resolve in
pkg/genesis/security subpackage) addressed package-level dep cycles,
but the genesis root go.mod still required luxfi/node v1.27.24
because cmd/bootstrap-l2 + cmd/derive100 import node directly for
wallet/tx-builder bits.
This commit moves cmd/ to a nested module
(github.com/luxfi/genesis/cmd) with its own go.mod that holds the
luxfi/node dep. Root go.mod no longer requires luxfi/node, breaking
the MVS cascade that was forcing luxd v1.23.x consumers to upgrade
28 packages just to bump genesis.
Result:
- go list -m all on root no longer surfaces luxfi/node directly
(still transitively visible at v1.23.x-compatible versions, not the
v1.27.x bloat)
- cmd/ retains the same source layout; tools still build via cmd/go.mod
- pkg/genesis core API unchanged
- pkg/genesis/security gate unchanged
Two-module separation:
- github.com/luxfi/genesis — data types + verification primitives
- github.com/luxfi/genesis/cmd — tools that need luxfi/node
One direction, no reverse import.
Closes the structural blocker for v1.23.x backport of canonical
evmAddr/utxoAddr.
* feat(genesis): bootstrap-l2 — durable replacement for /tmp script
Recreates the v1.27.x ConvertSubnetToL1 / CreateChain pipeline previously
maintained as /tmp/bootstrap-l2-v127. New tool at cmd/bootstrap-l2/:
- Reads MNEMONIC from env, derives BIP44 m/44'/9000'/0'/0/<idx>
- For each chain in --chains: IssueCreateNetworkTx → wallet re-sync →
IssueCreateChainTx (vmID nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr)
→ IssueAddChainValidatorTx for every primary validator
- Probes info.isBootstrapped(chain=<bcID>) + eth_blockNumber>0 per chain;
if either misses the deadline the whole tool exits non-zero (no partial
state propagated to YAML/CMs)
- Emits a single JSON document {chains:[{name, subnetId, blockchainId,
evmChainId, firstBlockHex, bootstrappedAt}, ...]} for downstream
YAML/CM updates
- --print-addr-only mode to verify funding before spending
go.mod: bump luxfi/node v1.23.36 → v1.27.21 to match cluster runtime
(devnet runs luxd v1.27.21; pre-rename wallet codecs would produce txs the
nodes can't decode). Only existing genesis use of luxfi/node is the
address formatter — no behavior change for other cmds.
Designed for the four canonical Lux L2 EVMs (hanzo, zoo, pars, spc)
but the chain list is data-driven via --chains and --configs-dir.
* feat(genesis): bootstrap-l2 default VM ID → mgj786NP... (native subnet-evm)
Devnet luxd-0 /data/plugins contains exactly two subnet-evm plugin IDs
(mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 and
ag3GReYPNuSR17rUP8acMdZipQBikdXNRKDyFszAysmy3vDXE), neither matching the
brand-namespaced alias nyGCobireNhxFB7iM5bxV74hAY6j9nQX6wizxfWomnMMtztkr
recorded in the 2026-05-27 chain-aliases ConfigMap annotation.
That alias required a runtime symlink (created post-startup, lost on PVC
remount) — the same fragile path that's been the historical source of
L2-EVM bootstrap failures. Mainnet's lux-mainnet/luxd-startup.yaml uses
mgj786NP... directly and `cp`'s it as the ag3GR... duplicate; we mirror
that pattern here so the plugin is *always* present without a startup
script writing into a PVC.
The chain-aliases CM annotation about nyGCobir... is now stale and will be
rewritten in the post-bootstrap update.
* feat(genesis): bootstrap-l2 — --existing-subnet-ids to resume after partial bootstrap
When CreateNetworkTx succeeds but the subsequent CreateChainTx fails (or
the tool gets killed mid-flight), the subnet still exists on-chain and
the fee was burned. Re-running the tool without --existing-subnet-ids
would burn another CreateNetwork fee.
Format: --existing-subnet-ids=hanzo:2PkWqv...,zoo:abc...
For each named chain, skip IssueCreateNetworkTx and pass the supplied
subnet ID straight into IssueCreateChainTx. Chains not in the map
continue to create a fresh subnet.
Also surfaces a real operational pattern: load-balanced services like
svc/luxd-headless can pin a kubectl port-forward to a lagging pod whose
state is behind the chain head; the tool must still complete by
re-using the subnet visible on the leader pod.
* feat(genesis): bootstrap-l2 — EVM heartbeat tx + decoupled bootstrap probe
Acceptance criterion 'eth_blockNumber > 0' on a fresh subnet-evm chain
requires injecting the first tx (subnet-evm only seals blocks when a tx
arrives). Adding two new flags:
--evm-heartbeat-key=<hex> LUX_PRIVATE_KEY hex (0x9011E888...).
Sends a 0-value self-tx after the chain
reports info.isBootstrapped=true; the first
tx rolls block 1.
--probe-bootstrap-only Skip the eth_blockNumber>0 wait. Used when
an out-of-band heartbeat (chain-heartbeat
CronJob) will roll block 1 later.
The probe is also split into waitBootstrap + probeChain so the heartbeat
fires after isBootstrapped but before eth_blockNumber checks (otherwise
eth_sendRawTransaction returns 'chain not bootstrapped').
Signing path: luxfi/crypto/secp256k1 → ecdsa.PrivateKey → geth/core/types
SignTx with EIP-155 signer scoped to each chain's evmChainID. EVM address
derivation goes through luxfi/crypto/secp256k1.PubkeyToAddress so we never
import an upstream go-ethereum crypto package.
* feat(genesis): bootstrap-l2 — preset chain ID (chain:subnetID:chainID) for resume
Extends --existing-subnet-ids parser to accept an optional third field
<chainID>. When present, both IssueCreateNetworkTx and IssueCreateChainTx
are skipped; the tool only waits for isBootstrapped, sends the heartbeat
(if --evm-heartbeat-key is set), and probes eth_blockNumber.
This is the post-partial-failure resume pattern: if hanzo's subnet+chain
were created successfully in a prior run but zoo onwards failed (or were
never attempted), pass
--existing-subnet-ids=hanzo:2PkWqv...:rHiiTB...,zoo:abc...
and the tool will skip the hanzo P-chain spend, treat hanzo as already
created, and continue with the next chain.
Also gates the AddChainValidatorTx and post-chain wallet re-sync on
presetChainID being unset, since both are unnecessary in resume mode.
* fix(genesis): canonical 0x prefix on all devnet alloc keys + fail-loud bootstrap-l2 (#72)
Two-part fix for the alloc-key shape that bootstrap-l2 consumes:
## 1. Genesis configs patched at source (4 files)
Every alloc key in:
- configs/hanzo-devnet/genesis.json
- configs/pars-devnet/genesis.json
- configs/spc-devnet/genesis.json
- configs/zoo-devnet/genesis.json
now matches the canonical /^0x[0-9a-fA-F]{40}$/ shape. The EVM
genesis loader rejects unprefixed keys; previously bootstrap-l2's
in-memory repair was masking the issue. Fixing at source makes the
configs self-describing and stops the "works for me but won't load
on a fresh node" failure mode.
## 2. bootstrap-l2 normalization is now fail-loud (cmd/bootstrap-l2/main.go)
Pre-flight repair upgrades from silent fix to:
- Normalize 0X → 0x (preserve body case for EIP-55 checksum signal)
- Add missing 0x prefix (repair, log count)
- Reject anything that doesn't match /^0x[0-9a-fA-F]{40}$/ AFTER
repair via log.Fatalf with up to 5 sample keys
Per the user's directive:
> "normalize alloc keys on load/write or fail loudly before chain
> creation. I prefer normalization plus a validation log, not
> silent mutation with no signal."
Log line now shows both healthy and repaired counts:
[devnet] hanzo: alloc keys ok=2 0x-repaired=0 (in-memory only, file unchanged)
A genuinely malformed key (non-hex, wrong length) aborts the entire
bootstrap before any CreateNetworkTx burns LUX:
[devnet] hanzo: 1 malformed alloc keys (require canonical
/^0x[0-9a-fA-F]{40}$/); sample: ["0xZZZ..."]
## 3. Single source of truth: normalizeAllocKey() helper
New pure function returns `(canonical, repaired, valid)`. Every
consumer (the in-memory repair, future validation tooling, any
on-disk rewriter) routes through this one function. Routes:
- body-case PRESERVED (EIP-55 checksum)
- prefix-case NORMALIZED (`0X` → `0x`)
- shape-validated (length 40, hex only)
- idempotent
## Tests
9 new tests in cmd/bootstrap-l2/main_test.go:
- already-canonical roundtrip
- missing-prefix repair flag
- 0X-prefix normalization
- body-case preservation (EIP-55 invariant)
- short body rejection (39/38/empty)
- long body rejection (41)
- non-hex rejection (g, _, space)
- empty string rejection
- idempotency under repeated normalize calls
All pass. Build clean.
Closes the v1.9.14 CI build failure: pkg/genesis/security_profile_test.go
references consensusconfig.StrictPQ()/ProfileStrictPQ which were only
introduced when the Lux prefix was dropped from profile identifiers.
That symbol first ships in consensus v1.23.25.
Also fixes the gofmt drift on builder/builder.go, cmd/genesis/main.go,
and pkg/genesis/keys.go that was failing the Test job.
Adds SecurityProfile to genesis.Config and ConfigOutput as an optional
pin-by-ID + pin-by-hash for the chain-wide ChainSecurityProfile. JSON
shape:
"securityProfile": {
"profileID": 1,
"profileHashHex": "<96 hex chars of SHA3-384 ComputeHash>"
}
SecurityProfile.Resolve() loads the canonical profile via
consensus/config.ProfileByID, runs Validate(), recomputes ComputeHash,
and refuses on any mismatch — a forked binary that swaps in a different
canonical profile content fails genesis boot.
This closes the genesis half of red-team F102: previously this package
never imported consensus/config.ChainSecurityProfile so no genesis
file could enforce the locked profile. The node-side wiring lands in
the follow-up commit.
Bumps luxfi/consensus v1.22.63 → v1.23.5 (the version that ships
ProfileByID, ProfileHash binding, and the closures for F96/F100/F101/
F107/F109).
Adds 6 regression tests for the load + JSON round-trip path.
luxfi/crypto v1.18.4 exposes the canonical NewKeyFromSeed entry point for
ML-DSA-65 (and mldsa44 / mldsa87) that the genesis HIP-0077 derivation
needed. Closes the TODO at the prior CIRCL stop-gap import.
The HIP-0077 SHAKE-256(label || child_seed) expansion still happens at
mldsaKeygenFromChildSeed; the resulting 32-byte xi is passed verbatim to
mldsa65.NewKeyFromSeed. At len == 32 the canonical package wires the
seed straight into FIPS 204 5.1 KeyGen, so the keypair is byte-for-byte
reproducible against the prior CIRCL call (which this package wraps
unchanged).
go.mod: luxfi/crypto v1.17.44 -> v1.18.4
go.mod: cloudflare/circl moves from direct -> indirect
Patch-bump.
The repo had no .github/workflows/ at all, so tag pushes never produced
release binaries.
Added:
.github/workflows/ci.yml go vet, gofmt -s -d, go test (push/PR to main)
.github/workflows/release.yml build genesis for linux|darwin|windows
amd64/arm64 + sha256, attach to GH release
CI runs `go test -skip TestGetGenesisLocalnet ./...` until the embedded
localnet genesis file is regenerated (expected 5e17 wei / 0.5 LUX, got
5e14). All other tests are green.
While here:
* go.mod bumped Go directive 1.26.1 → 1.26.2 (matches new toolchain
used by ci.yml / release.yml).
* luxfi/metric v1.4.11 → v1.4.12. v1.4.11 had a build-tag bug where
both process_metrics_other.go and process_metrics_windows.go used
//go:build windows, causing duplicate symbols on GOOS=windows. v1.4.12
fixes the tag to //go:build !unix && !windows. Required for the
windows/amd64 leg of the release matrix.
* `gofmt -s -w` on four pre-existing unformatted files so the new
gofmt CI gate is green from day one.
Adds an opt-out for the embedded C-Chain that gets baked into every
primary network genesis. With LUX_DISABLE_CCHAIN=1 the cchainData byte
slice stays empty, ConfigOutput.CChainGenesis is "", and downstream
builder.FromConfig's `if config.CChainGenesis != ""` guard skips the
C-Chain entry entirely — no more chainId 31337 silently mounted at
/ext/bc/C/rpc on networks that don't want it.
Why this knob exists: forks that run their own EVM blockchain via
CreateChainTx (<tenant>, etc.) don't use the C-Chain at all — but the
embedded localnet/cchain.json was being read unconditionally, so an
SRE running `lqd --network-id=1337` with no C-Chain env vars set
still got one. That's a footgun for any service that hard-codes the C
alias and a confusion source when you grep for /bc/C and it's there.
Three tests pin the behavior:
• TestGetGenesis_DisableCChain — set knob → cChainGenesis is empty
in the marshalled primary genesis, networkID still 1337.
• TestGetGenesis_DisableCChainOverridesFile — disable wins over the
LUX_CCHAIN_GENESIS_FILE override; an SRE setting both during a
misconfig probe gets the no-C-Chain outcome rather than silently
landing the file's content on the primary.
• TestGetGenesis_DisableCChainDefault — unset knob still embeds
C-Chain (regression guard so a careless precedence edit doesn't
silently strip C-Chain from mainnet/testnet/devnet).
go.mod / go.sum churn is `go mod tidy` resolving previously-stale
entries — unrelated to the C-Chain change but required for the test
suite to build.