Files
evm/LLM.md
zeekayandHanzo Dev c67047cc8d docs: LLM.md documents the software, not our operation of it
This is a tracked file in a public repository. It carried a deployment analysis
naming a cluster, namespace and statefulset, a recovery narrative, transaction
hashes and balances from a running network, an address described as one the
author controls, and a run of dated Problem/Status-FIXED postmortems. That is
operational record, and it belongs to the private one.

The RewardManager section keeps the mechanism, the config that activates it, the
in-process proof, and the per-chain DAO addresses a reader needs to configure it;
what it exercised on a running network is now stated as what was verified rather
than as where and on which pods. The durable facts inside the postmortems are
kept as properties: no beacon chain, so the Cancun blob fields are not part of a
Lux EVM block; a genesis file has to reproduce the chain's block 0; importing
history moves the database under anything reading it.

Also drops "subnet" from two lines describing our own chains.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-26 13:32:49 -07:00

39 KiB
Raw Permalink Blame History

Lux EVM Module

Project Overview

Lux EVM (formerly EVM) is the Ethereum Virtual Machine implementation for Lux L2 chains. This module provides EVM compatibility for the Lux network.

CRITICAL VERSION REQUIREMENTS

ALWAYS use these Lux-specific versions:

  • github.com/luxfi/node v1.22.64 - Latest Lux node version
  • github.com/luxfi/geth v1.16.64 - Our fork of go-ethereum (with PQ crypto precompiles)
  • github.com/luxfi/crypto v1.17.27 - Cryptographic primitives
  • github.com/luxfi/precompiles v0.1.2 - Standalone precompile contracts
  • github.com/luxfi/p2p - P2P networking package
  • github.com/luxfi/warp - Warp messaging package
  • github.com/luxfi/consensus - Consensus package

IMPORTANT: Package Usage

  • Use lp118 package for p2p handlers
  • Import: github.com/luxfi/p2p/lp118
  • All handler IDs: lp118.HandlerID
  • All functions: lp118.NewCachedHandler, lp118.NewSignatureAggregator
  • NEVER import from ava-labs packages
  • NEVER use go-ethereum directly, always use luxfi/geth

p2p.Handler Interface

The p2p.Handler interface uses these methods:

  • Gossip(ctx, nodeID, gossipBytes) - NOT AppGossip
  • Request(ctx, nodeID, deadline, requestBytes) ([]byte, *p2p.Error) - NOT AppRequest

p2p.Sender Interface

The p2p.Sender interface requires:

  • SendRequest(ctx, nodeIDs, requestID, request) error
  • SendResponse(ctx, nodeID, requestID, response) error
  • SendError(ctx, nodeID, requestID, errorCode, errorMessage) error
  • SendGossip(ctx, config p2p.SendConfig, msg) error

p2p.Network Methods

  • Request(ctx, nodeID, requestID, deadline, request) - NOT AppRequest
  • Response(ctx, nodeID, requestID, response) - NOT AppResponse
  • Gossip(ctx, nodeID, gossipBytes) - NOT AppGossip

warp Package Types

  • NewUnsignedMessage(networkID uint32, sourceChainID ids.ID, payload []byte) - takes ids.ID directly
  • Validator.NodeID is ids.NodeID - NOT []byte

Module Structure

/home/z/work/lux/evm/
├── plugin/evm/        # Main VM implementation
├── core/              # Core blockchain logic
├── consensus/         # Consensus engine (dummy for Lux)
├── eth/               # Ethereum protocol implementation
├── miner/             # Block building and mining
├── precompile/        # Precompiled contracts and warp
├── network/           # P2P networking
├── params/            # Chain configuration
├── scripts/           # Build and test scripts
└── warp/              # Cross-chain messaging

Current Build Status

✅ ALL TESTS PASSING - v0.8.18

All 62 test packages pass. Key fixes:

  1. PQ Crypto Precompiles - ML-DSA, SLH-DSA, ML-KEM integrated via geth v1.16.64
  2. Import Cycle Fixed - precompiles v0.1.2 has no geth/core/vm dependency
  3. Bind Tests Fixed - All ABI binding tests now pass

Package Versions (2025-12-25)

Package Version Status
evm v0.8.18 ✅ All tests pass
geth v1.16.64 ✅ With PQ precompiles
precompiles v0.1.2 ✅ Standalone
crypto v1.17.27 ✅ All tests pass
node v1.22.64 ✅ Builds clean

Post-Quantum Crypto Precompiles

LP-aligned addresses (P=2 for PQ/Identity family):

  • ML-DSA (FIPS 204) - Lattice-based signatures - 0x12202
  • SLH-DSA (FIPS 205) - Hash-based signatures - 0x12203
  • ML-KEM (FIPS 203) - Key encapsulation - 0x12201

EVM Upgrade Defaults (2026-03-29)

All EVM network upgrades are enabled at genesis (timestamp 0) by default:

  • EVMTimestamp = 0 (always)
  • DurangoTimestamp = 0 (Shanghai: PUSH0, warm coinbase)
  • EtnaTimestamp = 0 (Cancun: MCOPY, TSTORE, TLOAD, BLOBHASH, BLOBBASEFEE)
  • FortunaTimestamp = 0
  • GraniteTimestamp = 0

EVMDefaultChainConfig also sets ShanghaiTime and CancunTime at 0 with BlobScheduleConfig so geth's jump table selects the full Cancun instruction set.

The mapping: Durango -> Shanghai, Etna -> Cancun. These are set in SetEthUpgrades().

Stateful precompiles (nativeminter, feemanager, etc.) are NOT enabled in the default config because they require explicit permission configuration (admin addresses). Call SetAllGenesisPrecompiles() on the extras config to enable them.

Genesis precompile activation: ApplyPrecompileActivations now runs at genesis (parentTimestamp=nil). Previously it returned early, preventing genesis precompile state from being written. The fix ensures deterministic genesis because precompile configs are part of the chain config.

C-Chain Tx-Fee Routing — RewardManager → DAO Gov Safe (LIVE PATH)

Owner decision (supersedes the 50/50 below): 100% of every C-Chain tx fee accrues to the chain's DAO Gov Safe, DAO-governed and redirectable on-chain. This decouples collection from policy — the DAO later decides burn / validators / treasury via governance, on-chain, without another node upgrade. Real money on a 2T supply — staged devnet→testnet→mainnet, never touch mainnet fee config without staged proof + owner go.

Mechanism — existing RewardManager precompile, NO new fee-path code

The routing is stock C-Chain machinery, activated by config:

  • rewardmanager precompile at 0x10205 (precompile/contracts/rewardmanager/).
  • Activated via precompileUpgrades (upgrade.json) at a dated timestamp (coordinated fork, NOT a genesis edit), gated so mainnet is inert until set.
  • initialRewardConfig.rewardAddress = <DAO Gov Safe> → StoreRewardAddress. adminAddresses = [<DAO Gov Safe>] → the DAO can call setRewardAddress on-chain anytime (the "continually governed / redirectable" requirement; role-gated at contract.go:195 — non-admin callers revert).
  • Routing path (all existing production code): GetCoinbaseAt (core/blockchain_reader.go:411) reads GetStoredRewardAddress → sets header.Coinbase = reward address → core/state_transition.go:546 creditTxFee credits the full fee to the coinbase. So 100% of the fee lands at the DAO Safe.
  • The 50/50 creditTxFee/FeeSplitTimestamp code (below) stays committed but DORMANT (FeeSplitTimestamp nil ⇒ legacy full-fee-to-coinbase branch), so it is transparent to RewardManager and available if the DAO ever wants an in-protocol split.

Proven (in-process, production code paths)

core/reward_manager_routing_test.go — TestRewardManagerRoutesFeesToDAOSafe: with RewardManager active + reward = Lux DAO Gov Safe 0x8E29b816…, the production GetCoinbaseAt returns the DAO Safe (NOT blackhole); a real setRewardAddress tx from the admin redirects routing to a new address live (observed via GetCoinbaseAt). Combined with TestCreditTxFeeLegacy (full fee → coinbase, FeeSplit off) this proves 100% → reward address + on-chain DAO redirect. RewardManager's own suite (precompile/contracts/rewardmanager/) covers role-gating + storage.

Verified end to end on a multi-node network

Activation, routing and redirect were exercised on a running 5-node network with an unmodified node and evm build — RewardManager is already present, so this is a config change and not a rebuild.

  • Activation: append rewardManagerConfig to precompileUpgrades with initialRewardConfig.rewardAddress set to the chain's DAO Safe and adminAddresses to the address that will govern it, then restart the fleet to load it.
  • 100% routing: every tx fee accrues to the reward address; the blackhole address stays flat.
  • On-chain redirect: setRewardAddress from an admin moves accrual to the new address from the next block, and back again — the DAO keeps control without a node upgrade.
  • Determinism: the reward balance and the state root agree across every node at the same height, which is what makes this a consensus change rather than a local accounting one.

Per-chain DAO reward addresses (read canonical from ~/work/lux/standard/deployments/org-safes/)

  • Lux C-Chain 96369 → DAO Gov Safe 0x8E29b816c6C35b13cE1ff68D33E245C2bda8ac3D
  • Zoo 200200 → DAO Safe 0x229599f227231d8C90fcF1a78589F5DC4b7A6962
  • Hanzo / Pars → DAO Safes not yet in org-safes/ — STAGE before activation. Deployable upgrade.json templates: ~/work/lux/standard/deployments/rewardmanager/ (lux-96369.json, zoo-200200.json; blockTimestamp sentinel 9999999999 = inert until the operator sets the coordinated fork time).

How the upgrade schedule reaches the node

The C-Chain upgrade schedule is a file at <chain-config-dir>/C/upgrade.json. An operator delivering it by ConfigMap copies the key into that path at boot; the node reads it on start, so a schedule change is a restart and not a rebuild.

Tx-Fee 50/50 Split — burn + staking-reward fold (DORMANT option, NOT the live path)

Superseded as the live behavior by the RewardManager DAO routing above. This code is committed but gated OFF (FeeSplitTimestamp nil everywhere) and kept as a ready option if the DAO ever chooses an in-protocol burn+stake split. The P-Chain fold-in was NOT built (design only). Original design follows.

Owner decision (superseded): every C-Chain transaction fee is divided 50% true burn (real supply reduction) + 50% folded into the P-Chain staking reward (validators' returns grow from fee flow, unified into the native reward payout — NOT a separate C-Chain coinbase stream).

Current reality this replaces

The C-Chain credits the entire fee (base fee + tip, EVM-mode accounting) to block.Coinbase, which defaults to the blackhole 0x0100…0000 (core/state_transition.go fee-credit site; coinbase forced to blackhole by GetCoinbaseAt). Nothing is burned; the fee just piles up in an uncontrolled EOA (~3,778 LUX stuck there). The intended split was never built. Note: geth's own core/state_transition.go:569 is a different path used only by geth-direct chains — the C-Chain runs evm's vendored core/state_transition.go, so the split lives there.

Mechanism (the ONE seam, deterministic, config-gated)

Single fee-disbursement site: evm/core/state_transition.go execute(). The per-tx credit is delegated to creditTxFee (evm/core/fee_split.go):

  • Gate — extras.ChainConfig.FeeSplitTimestamp (a standalone fee-routing fork, sibling to AllowFeeRecipients; intentionally NOT in the NetworkUpgrades opcode-fork chain — it is orthogonal to EVM/Durango/Quasar/… and has no ordering relationship to them). IsFeeSplit(time) gates activation. A compatibility check (checkConfigCompatible) freezes the activation time once effective, so a node cannot silently reschedule it and fork.
  • Split — fee = gasUsed × effectiveGasPrice (a consensus value):
    • rewardShare = fee >> 1 (floor(fee/2)) → credited to extras.FeeRewardVault (0x0100…0002), a protocol-owned account that holds real, tracked balance.
    • burnShare = fee − rewardShare (ceil(fee/2)) → credited to no account. Never re-crediting it removes it from the account trie: the sum of all balances (the supply) drops by exactly burnShare. This is a true burn, the same mechanism as the EIP-1559 base-fee burn — NOT a transfer to a dead EOA (which leaves supply unchanged). The odd wei on an odd fee goes to the burn.
  • Legacy path unchanged — when FeeSplit is inactive, the full fee goes to the coinbase exactly as before.

Determinism: fee is a consensus value; the split is pure uint256 integer math (one right shift) to a fixed address — every validator computes an identical post-state, so the handler cannot fork the chain. A running multi-node net that keeps finalizing is the determinism proof.

Conservation invariant

Per tx (fee F): Δ(Σ balances) = −burnShare, Δ(vault) = +rewardShare, rewardShare + burnShare = F. Whole-system, after the vault is atomically exported to P-Chain and paid to validators (a move, not a mint): total_supply_after = total_supply_before − Σ burnShare. The reward half is supply-neutral (redistributed); the curve-based staking mint is orthogonal and unchanged. No mint for the fee half ⇒ structurally impossible to double-mint.

Implemented + PROVEN on real block execution (C-Chain half)

Files: evm/core/fee_split.go (helper), evm/core/state_transition.go (seam), evm/params/extras/config.go (FeeRewardVault, FeeSplitTimestamp, IsFeeSplit, compat, description). Tests (all pass, CGO_ENABLED=0 go test ./core/ ./params/extras/):

  • core/fee_split_test.go — split math, conservation, odd-wei rule, determinism (identical post-state root), legacy-path preserved.
  • params/extras/fee_split_test.go — activation-gate boundary + compat freeze.
  • core/fee_split_block_test.go — end-to-end on produced+accepted blocks: 10 LUX genesis, 5 transfers @ 21000 gas × 225 gwei ⇒
    • blackhole 0x0100…0000 balance = 0 (stuck behavior replaced)
    • vault 0x0100…0002 = 11,812,500,000,000,000 wei (exactly 50%)
    • BURNED = 11,812,500,000,000,000 wei — supply 10e18 → 9.9881875e18 (real decrease)
    • burn == vault (exact 50/50 on even fees); sender debit == value + fee. Conservation exact.

P-Chain fold-in (designed; scoped — the larger, consensus-critical lift)

The reward half must reach P-Chain validators as staking reward. Chosen model R1 (move, not mint) — simplest correct, conservation by construction:

  1. Periodic atomic export of the FeeRewardVault balance C→P (Avalanche/Lux shared-memory export/import — the native conservation-exact value-move; today exports are user-initiated, so a system-triggered epoch export is new machinery: a hook in the C-Chain FinalizeAndAssemble/atomic path emitting an export UTXO to a canonical P-Chain fee-reward-pool address, draining the vault).
  2. P-Chain fee-reward pool — new persisted state (vms/platformvm/state: Get/SetFeeRewardPool) funded by the import. (New persisted field ⇒ state serialization/migration ⇒ consensus-critical; must be gated + migration-tested.)
  3. Distribution — at RewardValidatorTx commit (vms/platformvm/txs/executor/proposal_tx_executor.go rewardValidatorTx, ~line 285 where reward := validator.PotentialReward), add the validator's pro-rata share of the pool (deterministic: by stake-weight over the accrual epoch) to the payout UTXO — unifying it into the native staking-reward path with no second mint.
  4. Supply sync — decrement P-Chain currentSupply by the epoch's burn B so the reward curve (vms/platformvm/reward/calculator.go, driven by remainingSupply = SupplyCap − currentSupply) sees true post-burn supply.

Rejected alternative R2 (burn-on-C + re-mint-on-P): same net supply, but needs a trustless cross-chain counter read and an exact burn==mint match (a double supply-touch = double-mint/loss risk). Violates "no double-mint, simplest correct".

Interpretation flag for owner: "unifies into the staking-reward mint" is realized as unifying into the staking-reward payout, funded by moved fees, not a second mint — the conservation-safe reading. Confirm before P-Chain build.

Rollout (gated — do NOT skip a stage)

  1. Devnet 96367 — build the C-Chain plugin via ARC CI (never local docker), deploy to the 5-validator devnet, set feeSplitTimestamp in the C-Chain upgrade config to a near-future time. Send txs; verify via RPC on all 5 nodes: eth_getBalance(0x0100…0002) grows by Σfee/2; eth_getBalance(0x0100…0000) stays flat; per-tx gasUsed+effective price ⇒ expected burn; identical state root / block hash at each height across all 5 (determinism); capture before/after + tx hashes. (Unit + block-level tests above already prove the invariants deterministically.)
  2. Testnet — same config, longer soak; confirm no fork, supply-decrease trend.
  3. Mainnet 96369 — ONLY after owner go: add feeSplitTimestamp (future) to the C-Chain upgrade config via a dated fork tx (not a genesis edit). Separate owner decision: sweep vs. burn the ~3,778 LUX already stuck at 0x0100…0000.

Guardrails honored: luxfi packages only; patch-semver; ARC CI builds (local builds here were unit-tests only); deterministic; supply conserved exactly.

Key Implementation Details

Context Management

  • VM uses context.Context instead of consensus.Context struct
  • Access consensus data via helper functions:
    • consensus.GetChainID(ctx)
    • consensus.GetNetworkID(ctx)
    • consensus.GetNodeID(ctx)
    • consensus.GetLogger(ctx)
    • consensus.GetWarpSigner(ctx)

Interface Compatibility

  • Block implements both chain.Block and consensuschain.Block
  • BuildBlock returns consensuschain.Block for compatibility
  • AppSender uses set.Set[ids.NodeID] for node sets
  • Version uses consensus/version.Application not node's version

Package Dependencies

NEVER use these packages:

  • ❌ github.com/ethereum/go-ethereum - Use github.com/luxfi/geth
  • ❌ github.com/ava-labs/* - Use github.com/luxfi/*
  • ❌ github.com/luxfi/node v1.16.x - Use v1.13.4 for compatibility

Always use:

  • ✅ github.com/luxfi/consensus - Local consensus package
  • ✅ github.com/luxfi/crypto - Local crypto package
  • ✅ github.com/luxfi/warp - Local warp package
  • ✅ github.com/luxfi/geth - Our Ethereum fork

Build Commands

cd /home/z/work/lux/evm
go build ./...  # Currently fails due to interface issues
go test ./...   # Will work after build issues are resolved

Common Issues and Fixes

  1. Version Requirements

    • Always use node v1.16.15 or later Lux versions
    • Never use ava-labs packages
    • Check go.mod replace directives
  2. Interface Compatibility

    • Block needs SetStatus method (even if no-op)
    • BuildBlock must return consensuschain.Block
    • Context is context.Context, not a struct
  3. Missing Metrics

    • Metrics registration is currently disabled (TODO)
    • Will be re-enabled when consensus context supports it
  4. ID Type Conversions

    // Convert between node's IDs and consensus IDs
    func nodeIDToConsensus(id nodeids.NodeID) ids.NodeID {
        var consensusID ids.NodeID
        copy(consensusID[:], id[:])
        return consensusID
    }
    

Testing

  • Run with -short flag for quick tests
  • 28 packages with tests, 14 without (expected)
  • Tests will pass after build issues are resolved

Important Notes

  • This module is actively being migrated from evm
  • Maintains backwards compatibility with existing Lux L2 chains
  • Uses single validator POA for development (k=1 consensus)
  • Major refactoring needed to reconcile ID type differences between packages

Documentation Status (2025-11-12)

✅ Documentation Enhanced

Successfully created comprehensive documentation for the Lux EVM implementation.

Documentation Created

  1. Enhanced index.mdx (/Users/z/work/lux/evm/docs/content/docs/index.mdx)
    • Complete EVM overview and architecture
    • Key differences from standard EVM
    • Smart contract deployment guide
    • Gas optimization strategies
    • Comprehensive API reference (eth, web3, net, admin, debug, validators, warp)
    • Integration with Lux blockchain
    • Performance tuning configuration
    • Security best practices
    • Troubleshooting guide
    • Migration guides from Ethereum and C-Chain

Documentation Features Added

  • Architecture Section: VM, Core, Precompiles detailed
  • API Reference: 40+ JSON-RPC endpoints documented
  • Code Examples: JavaScript, Solidity, configuration files
  • Performance Guide: State management, transaction pool, benchmarking
  • Security Guide: Access control, gas limits, cross-chain security
  • Troubleshooting: Common issues and debug commands
  • Migration Guides: From Ethereum and C-Chain

Build Status

  • ✅ Documentation site builds successfully
  • ✅ Next.js 16.0.1 with Turbopack
  • ✅ Static site generation working
  • ✅ All pages render correctly

Completeness Score: 95/100

What's Complete

  • ✅ Overview and introduction (100%)
  • ✅ Architecture documentation (100%)
  • ✅ API reference (100%)
  • ✅ Smart contract deployment (100%)
  • ✅ Gas optimization (100%)
  • ✅ Integration guide (100%)
  • ✅ Performance tuning (100%)
  • ✅ Security considerations (100%)
  • ✅ Troubleshooting (100%)
  • ✅ Migration guides (100%)

What Could Be Added (5%)

  • Additional code examples for each precompile
  • Detailed tutorials for specific use cases
  • Video documentation links
  • Interactive API explorer
  • Benchmark results and graphs

Precompiled Contracts Available

  1. DeployerAllowList - Contract deployment permissions
  2. FeeManager - Dynamic fee configuration
  3. NativeMinter - Native token minting
  4. RewardManager - Validator rewards
  5. TxAllowList - Transaction permissions
  6. Warp - Cross-chain messaging
  7. PQCrypto - Post-quantum cryptography
  8. Quasar - Advanced consensus features

Readonly Database Support (2025-11-22)

Status: ✅ VERIFIED WORKING

Successfully implemented and verified readonly database access for legacy PebbleDB databases.

Key Changes

  1. Database Factory Fix (~/work/lux/database/factory/pebbledb.go)

    • Added readOnly bool parameter to newPebbleDB() function
    • Passes readonly flag to pebbledb.New() instead of hardcoded false
    • Committed to database repo (commit aaee95a)
  2. EVM Integration (~/work/lux/evm/go.mod)

    • Added replace directive: replace github.com/luxfi/database => ../database
    • Enables EVM to use local database with readonly fix
  3. Test Verification (test-readonly-db.go)

    • Successfully opens 7.1GB legacy PebbleDB in readonly mode
    • Can read all keys without write access
    • No corruption or modification risk

Legacy Database Details

  • Location: /Users/z/work/lux/state/chaindata/lux-mainnet-96369/db/pebbledb
  • Size: 7.1GB (751 files)
  • Blockchain ID: dnmzhuf6poM6PUNQCe7MWWfBdTJEnddhHRNXz2x7H6qSmyBEJ
  • Chain ID: 96369
  • Purpose: Legacy evm data for regenesis export

Correct Migration Approach

IMPORTANT: Do NOT manually migrate database files between formats.

The proper workflow using lux-cli and VM interfaces:

  1. Deploy L2: Use lux l2 create and lux l2 deploy to create a Net
  2. Export Data: Use VM's exporter interface via lux-cli export commands
  3. Import to C-Chain: Use VM's importer interface via lux migrate import

VM Importer/Exporter Interface

Each VM must implement:

  • Exporter Interface: Serialize blockchain state to portable format
  • Importer Interface: Deserialize and load blockchain state
  • Format: VM-agnostic, standardized data structure

Current lux-cli Status

The lux migrate command exists but is incomplete:

  • lux migrate prepare - Placeholder, needs migration-tools implementation
  • lux migrate import - Not yet implemented
  • lux migrate bootstrap - Partial implementation
  • lux migrate validate - Not implemented

Implementation Needed

To complete the migration workflow:

  1. VM Exporter (plugin/evm/export.go):

    func (vm *VM) Export(ctx context.Context) ([]byte, error) {
        // Export blockchain state to standardized format
        // Include: genesis, blocks, state trie, metadata
    }
    
  2. VM Importer (plugin/evm/import.go):

    func (vm *VM) Import(ctx context.Context, data []byte) error {
        // Import blockchain state from standardized format
        // Validate and load into C-Chain database
    }
    
  3. lux-cli Migration Tools:

    • Implement migration-tools/migrate.go that calls VM exporter/importer
    • Complete lux migrate prepare to use VM interfaces
    • Implement lux migrate import for C-Chain import

Architecture Principles

  • ✅ Use VM's native export/import interfaces
  • ✅ Let each VM handle its own data format
  • ✅ Generic migration via standardized interfaces
  • ❌ NO manual database file copying
  • ❌ NO format-specific conversion scripts
  • ❌ NO direct database manipulation outside VM

Current Implementation Status (2025-11-23)

✅ Fixed:

  1. Import paths updated to use luxfi/geth instead of go-ethereum
  2. Import paths updated to use luxfi/ids instead of luxfi/node/ids
  3. Chainmigrate interfaces.go fixed with correct imports
  4. Duplicate ChainMigrator definition resolved (renamed struct to Migrator)
  5. Broken implementation files disabled (.go.broken extension)
  6. Package consistency verified - all luxfi packages used correctly

📦 Required Package Imports:

  • Ethereum types: github.com/luxfi/geth (NOT go-ethereum)
  • IDs: github.com/luxfi/ids (NOT luxfi/node/ids)
  • Logging: github.com/luxfi/log (ALWAYS use luxfi/log for consistency)
  • Chainmigrate: github.com/luxfi/node/chainmigrate

🔄 In Progress:

  • Fixing exporter.go to match actual VM structure
  • Need to access NetworkID from chainCtx, not config
  • Need to find correct method for GetTd (total difficulty)
  • Need to create proper error types (ErrMissing, ErrNotImplemented)
  • Need to convert uint256.Int to *big.Int for balance

⏳ Next Steps:

  1. Complete exporter.go fixes to compile successfully
  2. Create importer.go implementation
  3. Test export functionality with readonly database
  4. Create migration-tools in lux-cli that use these interfaces
  5. Complete lux migrate command implementation
  6. Test full export → import workflow
  7. Verify C-Chain can serve exported data via RPC

lux-cli Integration (2025-11-23)

✅ COMPLETE: ChainExporter integrated with lux-cli

Integration Architecture:

lux-cli migrate
    ↓
migration-tools/migrate (symlink)
    ↓  
node/cmd/chainmigrate/chainmigrate (binary)
    ↓
node/chainmigrate/interfaces.go (ChainExporter interface)
    ↓
evm/plugin/evm/exporter.go (implementation)

CLI Tool Location:

  • Binary: /Users/z/work/lux/node/cmd/chainmigrate/chainmigrate
  • Symlink: /Users/z/work/lux/cli/migration-tools/migrate

Usage via lux-cli:

lux migrate prepare \
  --source-db ~/.node/chaindata/subnet-96369/db/pebbledb \
  --output ./mainnet-migration \
  --network-id 96369 \
  --validators 5

Direct Binary Usage:

node/cmd/chainmigrate/chainmigrate \
  --src-pebble /path/to/source/db \
  --dst-leveldb /path/to/dest/db \
  --chain-id 96369 \
  --start-block 0 \
  --end-block 1000 \
  --batch-size 100

Features:

  • ✅ Uses luxfi/log for logging
  • ✅ Uses luxfi/geth for Ethereum types
  • ✅ Uses ChainExporter interface
  • ✅ Configurable batch sizes
  • ✅ Block range selection
  • ✅ Export-only and import-only modes

Integration Tests: All passing ✅

Next Steps:

  1. Complete full EVM integration (initialize VM with readonly DB)
  2. Implement importer.go for destination chain
  3. Test end-to-end export → import workflow

Integration Approach - RPC-BASED (2025-11-23)

✅ RPC Control: lux-cli uses netrunner + RPC only

Previous Approaches (WRONG): ❌

  1. Created ad-hoc cmd/chainmigrate binary in node repo
  2. Used symlinks to bridge binaries
  3. Used ChainExporter interface as Go import in lux-cli
  4. Direct Go package dependencies

Current Approach (CORRECT): ✅

  • lux-cli: RPC client for fleet control
  • netrunner: Deploys and manages node fleet
  • EVM MigrateAPI: RPC endpoints for export/import
  • NO Go package imports between cli and evm
  • Pure RPC communication only

Architecture:

lux-cli (RPC client)
    ↓ HTTP JSON-RPC calls
netrunner (fleet manager)
    ↓ deploys nodes
EVM node (with MigrateAPI)
    ↓ migrate_getBlocks
    ↓ migrate_importBlocks
Database (PebbleDB/LevelDB)

Implementation:

// lux-cli/cmd/migratecmd/utils.go
func runMigration(sourceRPC, destRPC string, chainID int64) error {
    // Get current block via RPC
    blockNum, err := getCurrentBlock(ctx, sourceRPC)

    // Call migrate_getBlocks via RPC (no Go imports!)
    req := &RPCRequest{
        Method: "migrate_getBlocks",
        Params: []interface{}{0, blockNum, 100},
    }
    callRPC(sourceRPC, req, &blocks)

    // Import via RPC to destination
    req = &RPCRequest{
        Method: "migrate_importBlocks",
        Params: []interface{}{blocks},
    }
    callRPC(destRPC, req, &result)
}

EVM RPC Endpoints:

// plugin/evm/api_migrate.go
type MigrateAPI struct {
    vm *VM
}

// RPC: migrate_getChainInfo
func (api *MigrateAPI) GetChainInfo() (*ChainInfo, error)

// RPC: migrate_getBlocks (batch, max 100 blocks)
func (api *MigrateAPI) GetBlocks(start, end, limit uint64) ([]*BlockData, error)

// RPC: migrate_streamBlocks (streaming via channels)
func (api *MigrateAPI) StreamBlocks(start, end uint64) (chan *BlockData, chan error)

// RPC: migrate_importBlocks
func (api *MigrateAPI) ImportBlocks(blocks []*BlockData) (int, error)

Benefits:

  • True fleet control via RPC (lux-cli controls remote nodes)
  • No Go package coupling between repos
  • Can control nodes anywhere (local, remote, cloud)
  • Netrunner handles deployment, lux-cli handles orchestration
  • Works with any number of nodes
  • Clean separation: deploy vs control vs execution

Workflow:

  1. Deploy source EVM with netrunner (readonly DB):

    netrunner engine start evm-source --data-dir=/readonly/db
    
  2. Deploy destination C-Chain with netrunner:

    netrunner engine start c-chain
    
  3. Run migration via lux-cli (auto-discovers RPC endpoints):

    lux migrate prepare
    # RPC endpoints discovered from netrunner at runtime
    # Source: ext/bc/<blockchain-id>/rpc (old 96369 net)
    # Dest: ext/bc/C/rpc (C-Chain)
    # Internal RPC uses port 9630 (not 9650)
    # Hosts/ports known at runtime, not hardcoded
    

RPC Path Format:

  • C-Chain: ext/bc/C/rpc (uses C alias)
  • Old 96369 Net: ext/bc/dnmzhuf6poM6PUNQCe7MWWfBdTJEnddhHRNXz2x7H6qSmyBEJ/rpc (uses blockchain ID)

MigrateAPI Registration Status (2025-11-23)

✅ COMPLETE: MigrateAPI Registered with EVM Node

The MigrateAPI has been successfully registered with the EVM node and is now available via RPC.

Changes Made:

  1. Added MigrateAPIEnabled config flag to plugin/evm/config/config.go
  2. Set default to true in plugin/evm/config/default_config.go
  3. Registered MigrateAPI in plugin/evm/vm.go (similar to WarpAPI)
  4. Fixed type errors in plugin/evm/api_migrate.go:
    • Changed Transactions field from []types.Transaction to []*types.Transaction
    • Fixed WithBody call to use types.Body directly

Available RPC Methods:

  • migrate_getChainInfo - Returns blockchain metadata (chain ID, network ID, current height, etc.)
  • migrate_getBlocks - Exports blocks in batches (max 100 blocks per call)
  • migrate_streamBlocks - Streams blocks via channels (not yet exposed via JSON-RPC)
  • migrate_importBlocks - Imports blocks to the blockchain

Configuration:

{
  "migrate-api-enabled": true  // Default: true
}

Testing Status:

  • ✅ EVM plugin builds successfully
  • ✅ MigrateAPI properly registered in RPC handler
  • ✅ CLI commands (export-data, import-data) implemented
  • ⏳ End-to-end RPC testing pending

Next Steps:

  1. Deploy EVM node with readonly database
  2. Test migrate_getChainInfo RPC call
  3. Test migrate_getBlocks with various block ranges
  4. Test full export → import workflow via lux-cli
  5. Verify imported data on destination chain

Properties this EVM holds

No beacon chain. Lux networks run their own consensus, so the Cancun-era EIP-4844 beacon fields are not part of a Lux EVM block and the blob-fee path is not reachable on a fork that does not enable it.

A genesis file has to reproduce the chain's block 0. For a chain being brought up against existing history, the genesis hash computed from genesis.json must equal the original block 0 hash exactly; import checks it rather than assuming it, because a genesis that differs describes a different chain.

Importing history is not a read-only operation. admin_importChain moves the chain database underneath everything reading it, so it runs against a node that is not concurrently serving those reads.

Post-Quantum Cryptography Precompiles (2025-12-24)

Status: ✅ COMPLETE - All PQ Crypto Precompiles Implemented and Tested

Lux EVM includes native precompiled contracts for NIST FIPS 203-205 post-quantum cryptography algorithms.

Precompile Addresses (LP-aligned)

Precompile Address Description
PQCrypto Unified 0x0000000000000000000000000000000000012201 All PQ crypto operations (P=2 PQ/Identity)
ML-DSA Verify 0x0000000000000000000000000000000000012202 Dedicated ML-DSA verification
SLH-DSA Verify 0x0000000000000000000000000000000000012203 Dedicated SLH-DSA verification

Gas Costs (Per Mode)

ML-DSA Signature Verification (FIPS 204)

Mode Security Mode Byte Gas Cost
ML-DSA-44 Level 2 0x44 75,000
ML-DSA-65 Level 3 0x65 100,000
ML-DSA-87 Level 5 0x87 150,000

ML-KEM Key Encapsulation (FIPS 203)

Mode Security Mode Byte Encap Gas Decap Gas
ML-KEM-512 Level 1 0x00 6,000 6,000
ML-KEM-768 Level 3 0x01 8,000 8,000
ML-KEM-1024 Level 5 0x02 10,000 10,000

SLH-DSA Signature Verification (FIPS 205)

Mode Hash Security Mode Byte Gas Cost
128s SHA-256 Level 1 0x00 50,000
128f SHA-256 Level 1 0x01 75,000
192s SHA-256 Level 3 0x02 100,000
192f SHA-256 Level 3 0x03 150,000
256s SHA-256 Level 5 0x04 175,000
256f SHA-256 Level 5 0x05 250,000
128s SHAKE Level 1 0x10 50,000
128f SHAKE Level 1 0x11 75,000
192s SHAKE Level 3 0x12 100,000
192f SHAKE Level 3 0x13 150,000
256s SHAKE Level 5 0x14 175,000
256f SHAKE Level 5 0x15 250,000

Implementation Files

precompile/contracts/
├── mldsa/
│   ├── contract.go       # ML-DSA precompile (182 lines)
│   ├── contract_test.go  # 334 lines, 10 test cases
│   └── module.go         # Registration
└── pqcrypto/
    ├── contract.go       # Unified PQ precompile (382 lines)
    ├── contract_test.go  # 234 lines, 20 test cases
    ├── module.go         # Registration
    └── config.go         # Configuration

Mode Byte Encoding

Critical: Precompile mode bytes differ from library internal values:

// Precompile mode bytes (used in input)
ModeMLDSA44 uint8 = 0x44  // Library: mldsa.MLDSA44 = 0
ModeMLDSA65 uint8 = 0x65  // Library: mldsa.MLDSA65 = 1
ModeMLDSA87 uint8 = 0x87  // Library: mldsa.MLDSA87 = 2

The precompile implementation converts between these formats in the Run() method.

Function Selectors (PQCrypto Unified)

Selector Bytes Operation
"mlds" 0x6d6c6473 ML-DSA Verify
"encp" 0x656e6370 ML-KEM Encapsulate
"decp" 0x64656370 ML-KEM Decapsulate
"slhs" 0x736c6873 SLH-DSA Verify

Test Status

=== ML-DSA Tests ===
TestMLDSAVerify_ValidSignature      PASS
TestMLDSAVerify_InvalidSignature    PASS
TestMLDSAVerify_WrongMessage        PASS
TestMLDSAVerify_InputTooShort       PASS
TestMLDSAVerify_EmptyMessage        PASS
TestMLDSAVerify_LargeMessage        PASS
TestMLDSAVerify_GasCost             PASS
TestMLDSAPrecompile_Address         PASS

=== PQCrypto Tests ===
TestPQCryptoPrecompile              PASS
TestMLDSAVerify                     PASS
TestMLKEMEncapsulateDecapsulate     PASS
TestSLHDSAVerify                    PASS
TestGasCalculation (15 subtests)    PASS

Total: 20 tests, 0 failures

Documentation

Full specification documented in:

  • LP-3520: Post-Quantum Cryptography Precompile Implementation Guide
  • LP-4200: Post-Quantum Cryptography Suite for Lux Network
  • LP-3502: ML-DSA Post-Quantum Signature Precompile

Dependencies

  • github.com/luxfi/crypto/mldsa - ML-DSA implementation (FIPS 204)
  • github.com/luxfi/crypto/mlkem - ML-KEM implementation (FIPS 203)
  • github.com/luxfi/crypto/slhdsa - SLH-DSA implementation (FIPS 205)
  • Backend: Cloudflare CIRCL (audited, FIPS compliant)

Pluggable Backends + Auto-Import

Backend selection (build tags)

Build Backend Notes
go build ./plugin Go EVM (Block-STM) Default. Pure Go.
-tags gpu Go EVM + Metal GPU bridge core/parallel/gpu_bridge.go. Requires working luxfi/gpu module. Currently broken upstream — MLX bindings (mlx_zeros, mlx_from_slice_int64, mlx_floor, mlx_full, etc.) are stubs in ~/work/luxcpp/mlx-c-api/mlx_c_api.c returning NULL with mismatched signatures. Don't use until upstream lands real MLX impls.
-tags cevm C++ EVM via cgo core/parallel/backend_cevm.go. Real GPU path — links ~/work/luxcpp/cevm/build-phase5b/lib/{libevm,libevm-gpu-state,libevm-metal-hosts,libevm-kernel-metal}. Requires bash chains/evm/cevm/fetch-luxcpp.sh to populate libs.
-tags revm Rust EVM via FFI core/parallel/backend_revm.go. Requires librustc_revm.a from luxfi/revm.

gpu_bridge.go:4 build tag is cgo && darwin && gpu (opt-in).

--import-chain-data plumbing

luxd's --import-chain-data=<path.rlp> flag now flows through to the EVM plugin:

  1. node/config/config.go:2012-2028 config bridge injects the path into nodeConfig.ChainConfigs["C"].Config JSON map.
  2. EVM plugin reads vm.config.ImportChainData (declared in plugin/evm/config/config.go, JSON key import-chain-data).
  3. After initializeChain() in plugin/evm/vm.go::Initialize(), if ImportChainData != "", calls importBlocksFromFile(chain, path, persistAccepted) from plugin/evm/admin_api.go:220. Same code path as admin_importChain RPC.
  4. Batches of 2,500 blocks. State trie committed every CommitInterval (default 4,096) blocks; acceptedBlockDB updated atomically with each commit.

Canonical mainnet hashes/state root and required precompiles live in ~/work/lux/genesis/LLM.md "Canonical Requirements" — single source of truth.

Build commands

cd ~/work/lux/evm

# Default: builds and installs the plugin at the canonical path
# (~/.lux/plugins/<EVMID> where EVMID = CB58(ids.ID{'e','v','m'}) per luxfi/constants.EVMID).
# Handles codesign on darwin in one shot. No hardcoded plugin paths, no aliases.
./scripts/build.sh

# Variants (build, then have luxd point its --plugin-dir at the chosen build):
./scripts/build.sh /tmp/evm-default            # plain Go EVM (default)
GOFLAGS='-tags cevm' ./scripts/build.sh /tmp/evm-cevm   # C++ backend (real GPU)
GOFLAGS='-tags revm' ./scripts/build.sh /tmp/evm-revm   # Rust REVM backend

The plugin filename is the binary's location: luxd's chain manager looks up <plugin-dir>/<VMID> and constants.EVMID = ids.ID{'e','v','m'} (CB58 = mgj786NP…) is the canonical primary-network C-Chain ID. Same ID is used for every EVM-based L2 chain in Lux. No second filename, no aliases.

Known upstream gaps

  • luxfi/[email protected] MLX C bindings unimplemented — stubs in ~/work/luxcpp/mlx-c-api/mlx_c_api.c. Don't use -tags gpu until landed.
  • luxfi/[email protected] API drift — accel.VMSession, NewVMSession, WithPriority, PriorityHigh undefined. Blocks chains/keyvm and likely other chains/*vm. Independent of EVM stack.
  • luxfi/coreth is DEPRECATED per coreth/DEPRECATED.md — use this repo (luxfi/evm) for all new C-Chain and L1 EVM work.