mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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]>
This commit is contained in:
@@ -54,7 +54,7 @@ The `p2p.Sender` interface requires:
|
||||
├── network/ # P2P networking
|
||||
├── params/ # Chain configuration
|
||||
├── scripts/ # Build and test scripts
|
||||
└── warp/ # Cross-subnet messaging
|
||||
└── warp/ # Cross-chain messaging
|
||||
```
|
||||
|
||||
## Current Build Status
|
||||
@@ -137,28 +137,23 @@ Combined with `TestCreditTxFeeLegacy` (full fee → coinbase, FeeSplit off) this
|
||||
100% → reward address + on-chain DAO redirect. RewardManager's own suite
|
||||
(`precompile/contracts/rewardmanager/`) covers role-gating + storage.
|
||||
|
||||
### LIVE DEVNET PROOF — COMPLETED (deployed 5-node devnet, chainId 96367)
|
||||
Cluster `do-sfo3-lux-k8s` ns `lux-devnet`, sts `luxd` (luxd-0..4, node v1.36.2 / evm
|
||||
v1.104.7 — RewardManager confirmed present, **no rebuild**). C-Chain RPC:
|
||||
`9650/v1/bc/C/rpc`. Config path: CM `luxd-chain-upgrades` key `cchain-upgrade.json`
|
||||
→ boot copies to `/data/configs/chains/C/upgrade.json`.
|
||||
### 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.
|
||||
|
||||
- **Pre-req recovery**: the C-Chain was consensus-wedged (all 5 spamming `failed to
|
||||
fetch preferred block`, heights split 72/89/109). Recovered by graceful pod-delete
|
||||
rolling restart (sanctioned k8s, never pkill) → 5/5 reconverged.
|
||||
- **Activation**: appended `rewardManagerConfig` (blockTimestamp 1784057456,
|
||||
admin=0x9011 EOA I control, initialRewardConfig.rewardAddress=DAO Safe 0x8E29) to
|
||||
the CM (47→48 precompileUpgrades); rolling-restarted all 5 to load it.
|
||||
- **100% routing**: 5 fee txs (blocks 116-120) → reward `0x8E29` grew by
|
||||
**2,730,000,000,105,000 wei == Σ fees exactly**; blackhole `0x0100..00` **flat**
|
||||
(Δ0). tx e.g. `0x4d76afbd…`,`0x21ea8b4b…`,`0x4a660aa3…`,`0xdffca465…`,`0x31d9dea4…`.
|
||||
- **DAO redirect (on-chain)**: admin `setRewardAddress(0xEAbC…)` tx `0x67544986…`
|
||||
status 0x1 @124 → `currentRewardAddress` 0x8E29→0xEAbC; next 4 txs → 0xEAbC grew by
|
||||
**2,184,000,000,084,000 == Σ fees**, DAO Safe **flat**. Restored to DAO Safe
|
||||
(tx `0x82f5bbd0…` status 0x1) — second admin-control demo.
|
||||
- **Determinism 5/5**: block 120 reward balance `0xba381304aac30` AND stateRoot
|
||||
`0x09a05474…`; block 124 stateRoot `0x7a31669cc285…` — **identical across all 5
|
||||
nodes**. Final: 5/5 converged @130, routing = DAO Safe.
|
||||
- **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`
|
||||
@@ -168,51 +163,11 @@ Deployable `upgrade.json` templates: `~/work/lux/standard/deployments/rewardmana
|
||||
(`lux-96369.json`, `zoo-200200.json`; `blockTimestamp` sentinel 9999999999 = inert until
|
||||
the operator sets the coordinated fork time).
|
||||
|
||||
### Deployed-cluster config path (do-sfo3-lux-k8s)
|
||||
C-Chain upgrade schedule is delivered by a ConfigMap key `cchain-upgrade.json`, copied by
|
||||
the boot script to `/data/configs/chains/C/upgrade.json`:
|
||||
- **devnet** ns `lux-devnet`, http-port **9650**, CM `luxd-chain-upgrades`.
|
||||
- **testnet** ns `lux-testnet`, http-port **9640**, CM `luxd-startup`.
|
||||
- **mainnet** ns `lux-mainnet`, CM `luxd-startup`.
|
||||
sts `luxd` is `OnDelete`/`Parallel`, managed-by `lux-operator` (CM has no ownerRef → a
|
||||
direct `kubectl patch` persists across pod/node restarts). RPC path is `/v1/bc/C/rpc`.
|
||||
Activation: patch the CM's `cchain-upgrade.json` (append `rewardManagerConfig`), then a
|
||||
coordinated restart (`kubectl delete pod` one-at-a-time, or `scale 0→5`; **never pkill**).
|
||||
**Before the fork ts, verify all 5 nodes hold a byte-identical `/data/configs/chains/C/
|
||||
upgrade.json` (`sha256sum`)** — a per-node config mismatch is the ONLY fork risk. A live
|
||||
config check via `--wait=false` delete is unreliable (old pod lingers "ready"); confirm the
|
||||
NEW pod (config-presence grep) before proceeding.
|
||||
### 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.
|
||||
|
||||
### Coordinated mainnet runbook (owner GO only — one change per chain)
|
||||
1. Append to the live CM `luxd-startup` key `cchain-upgrade.json`, at ONE dated future
|
||||
`blockTimestamp`:
|
||||
- `precompileUpgrades += rewardManagerConfig{ adminAddresses:[<DAO Safe>],
|
||||
initialRewardConfig.rewardAddress:<DAO Safe> }`
|
||||
- **Lux C 96369 only** also `stateUpgrades += stranded-LUX sweep` (below).
|
||||
Templates: `~/work/lux/standard/deployments/rewardmanager/{lux-96369,zoo-200200}.json`
|
||||
(+ `lux-96369-strandedlux-sweep.json`), `blockTimestamp` sentinel `9999999999` → set to GO time.
|
||||
2. Rolling restart all 5; verify all-5 `sha256` identical BEFORE the ts. Abort/reschedule if any differ.
|
||||
3. After the ts, drive a tx; verify on all 5: fee → DAO Safe, blackhole flat, identical roots.
|
||||
Per chain: Lux C 96369 → `0x8E29b816c6C35b13cE1ff68D33E245C2bda8ac3D`; Zoo 200200 →
|
||||
`0x229599f227231d8C90fcF1a78589F5DC4b7A6962`; Hanzo/Pars after their Safes are staged.
|
||||
|
||||
### Stranded-LUX sweep — stateUpgrade is ADD-ONLY (design constraint)
|
||||
~3,778 LUX sit at the blackhole `0x0100…0000` (uncontrolled EOA, no key) on Lux C 96369
|
||||
(**Zoo/others: none**). A "move" is NOT directly expressible: `stateupgrade/state_upgrade.go`
|
||||
applies `balanceChange` via `state.AddBalance` (unsigned uint256) — it can CREDIT the DAO but
|
||||
**cannot DEBIT** 0x0100…00; a credit-only edit would MINT ~3778 LUX. Two correct options:
|
||||
- **(chosen, config-only) set-code sweeper**: `stateUpgrades` sets 22-byte runtime
|
||||
`0x738e29b816c6c35b13ce1ff68d33e245c2bda8ac3dff` (`PUSH20 <DAO>; SELFDESTRUCT`) at
|
||||
0x0100…00; then ONE permissionless tx to 0x0100…00 transfers its FULL balance to the DAO
|
||||
(exact, deterministic, no mint, no rebuild; post-EIP-6780 selfdestruct just moves value).
|
||||
Artifact: `deployments/rewardmanager/lux-96369-strandedlux-sweep.json`. Prove on devnet
|
||||
(still holds ~1.1 LUX) before mainnet.
|
||||
- **(alt) signed-balanceChange**: a ~3-line luxfi/evm change (negative → `SubBalance`) makes
|
||||
stateUpgrade a true move — but needs a node rebuild + redeploy (not config-only).
|
||||
Recommend DECOUPLING the sweep from the RewardManager activation (routing is proven +
|
||||
config-only; the sweep mechanism needs owner sign-off + a devnet dry-run).
|
||||
|
||||
---
|
||||
## 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
|
||||
@@ -784,514 +739,20 @@ The MigrateAPI has been successfully registered with the EVM node and is now ava
|
||||
|
||||
---
|
||||
|
||||
## Lux Blockchain Deployment Analysis (2025-12-18)
|
||||
## Properties this EVM holds
|
||||
|
||||
### Executive Summary
|
||||
**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.
|
||||
|
||||
This analysis examines the deployment process for Zoo and SPC subnets, identifying why C-Chain import succeeded while subnet imports fail with `ErrPrunedAncestor`.
|
||||
**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.
|
||||
|
||||
### Current State
|
||||
|
||||
| Chain | RLP File | Size | Import Method | Status |
|
||||
|-------|----------|------|---------------|--------|
|
||||
| C-Chain (96369) | lux-mainnet-96369.rlp | 1.28GB | admin.importChain | WORKED |
|
||||
| Zoo (200200) | zoo-mainnet-200200.rlp | 1.3MB | admin.importChain | ErrPrunedAncestor |
|
||||
| SPC (36911) | spc-mainnet-36911.rlp | 7.8KB | admin.importChain | Not tested yet |
|
||||
|
||||
---
|
||||
|
||||
### 1. Root Cause: Zoo RPC 404
|
||||
|
||||
**Diagnosis:** The subnet RPC endpoint returns 404 because the EVM plugin is not properly initialized or the blockchain is not registered with the node.
|
||||
|
||||
**Key Findings:**
|
||||
|
||||
1. **Plugin Registration**: EVM must be registered as a plugin with the node. The C-Chain uses `cchainvm` which is built into the node, but Zoo/SPC require the external `evm` plugin.
|
||||
|
||||
2. **Blockchain ID Mismatch**: The RPC path uses the blockchain ID (e.g., `ext/bc/<blockchain-id>/rpc`). If the subnet is not tracking the correct blockchain ID, RPC returns 404.
|
||||
|
||||
3. **Subnet Validation**: The node must be a validator for the subnet OR have explicit tracking enabled via `--track-subnets`.
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Ensure subnet tracking
|
||||
lux-node --track-subnets=<subnet-id>
|
||||
|
||||
# Verify blockchain registration
|
||||
curl -X POST http://localhost:9650/v1/info -d '{
|
||||
"jsonrpc":"2.0",
|
||||
"id":1,
|
||||
"method":"info.getBlockchainID",
|
||||
"params":{"alias":"zoo"}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Root Cause: ErrPrunedAncestor
|
||||
|
||||
**Location:** `/Users/z/work/lux/evm/core/block_validator.go:111-116`
|
||||
|
||||
```go
|
||||
// Ancestor block must be known.
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
return consensus.ErrPrunedAncestor
|
||||
}
|
||||
```
|
||||
|
||||
**The Check Flow:**
|
||||
|
||||
1. `HasBlockAndState` calls:
|
||||
- `GetBlock(hash, number)` - checks if block exists
|
||||
- `HasState(block.Root())` - checks if state trie exists
|
||||
|
||||
2. `HasState` implementation (`blockchain_reader.go:241-244`):
|
||||
```go
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, err := bc.stateCache.OpenTrie(hash)
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
**Critical Issue:** `OpenTrie` requires the FULL state trie to exist in the database. When a subnet starts fresh:
|
||||
|
||||
- Genesis block is written via `genesis.Commit()`
|
||||
- Genesis state is committed to the trie database
|
||||
- BUT the trie might not be accessible via `OpenTrie` if:
|
||||
- The trie is stored in snapshot form only
|
||||
- The trie root wasn't properly committed to disk
|
||||
- The state scheme (HashDB vs PathDB) differs
|
||||
|
||||
**Root Cause:** Genesis state is committed, but `stateCache.OpenTrie(genesisRoot)` fails because the trie nodes are not where `OpenTrie` expects them.
|
||||
|
||||
---
|
||||
|
||||
### 3. Why C-Chain Worked But Zoo Doesn't
|
||||
|
||||
**C-Chain (cchainvm):**
|
||||
|
||||
1. **Built into node**: The C-Chain VM is compiled directly into the node binary
|
||||
2. **Shared genesis**: Uses the network's genesis file which includes C-Chain state
|
||||
3. **Continuous state**: C-Chain has been running since network genesis - full state trie exists
|
||||
4. **State scheme consistency**: Uses the same state scheme as the node's defaults
|
||||
|
||||
**Zoo/SPC (EVM plugin):**
|
||||
|
||||
1. **External plugin**: Loaded dynamically, separate initialization path
|
||||
2. **Fresh genesis**: Genesis is created from the subnet's genesis.json at deployment time
|
||||
3. **State initialization gap**: Genesis state may be written but not properly accessible
|
||||
4. **Import before consensus**: Import happens before the VM is fully bootstrapped
|
||||
|
||||
**Key Difference - State Cache Initialization:**
|
||||
|
||||
```go
|
||||
// In blockchain.go - NewBlockChain
|
||||
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
|
||||
|
||||
// The stateCache is created with:
|
||||
// - bc.db: the ethdb.Database
|
||||
// - bc.triedb: the trie database
|
||||
|
||||
// For C-Chain: triedb already has the state from continuous operation
|
||||
// For EVM: triedb only has genesis state from fresh Commit()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. What Needs to be Fixed
|
||||
|
||||
#### Option A: Ensure Genesis State is Properly Accessible (RECOMMENDED)
|
||||
|
||||
**Location:** `core/genesis.go:417-451` (Genesis.Commit)
|
||||
|
||||
The issue is that `Genesis.Commit` writes the state, but the trie database might not be fully synced to disk or accessible via `OpenTrie`.
|
||||
|
||||
**Fix:**
|
||||
```go
|
||||
// In genesis.go Commit function, after writing genesis:
|
||||
func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Block, error) {
|
||||
block := g.toBlock(db, triedb)
|
||||
// ... existing code ...
|
||||
|
||||
// CRITICAL: Ensure trie is committed and accessible
|
||||
if err := triedb.Commit(block.Root(), false); err != nil {
|
||||
return nil, fmt.Errorf("failed to commit genesis trie: %w", err)
|
||||
}
|
||||
|
||||
// Verify the state is accessible
|
||||
if _, err := triedb.NodeReader(block.Root()); err != nil {
|
||||
return nil, fmt.Errorf("genesis state not accessible after commit: %w", err)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Option B: Initialize State Cache Before Import
|
||||
|
||||
**Location:** `plugin/evm/admin.go:108-154` (ImportChain)
|
||||
|
||||
Before importing blocks, ensure the genesis state is properly loaded:
|
||||
|
||||
```go
|
||||
func (p *Admin) ImportChain(_ *http.Request, args *ImportChainArgs, reply *ImportChainReply) error {
|
||||
// ... existing checks ...
|
||||
|
||||
chain := p.vm.eth.BlockChain()
|
||||
genesis := chain.Genesis()
|
||||
|
||||
// Verify genesis state is accessible
|
||||
if !chain.HasState(genesis.Root()) {
|
||||
log.Warn("Genesis state not accessible, attempting to recover")
|
||||
// Recommit genesis if needed
|
||||
if err := p.vm.ensureGenesisState(); err != nil {
|
||||
return fmt.Errorf("failed to ensure genesis state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Now proceed with import
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Option C: Use StateAt Instead of OpenTrie in HasState
|
||||
|
||||
**Location:** `core/blockchain_reader.go:241-244`
|
||||
|
||||
The current `HasState` uses `OpenTrie` which requires full trie. Consider using `StateAt` which can fall back to snapshots:
|
||||
|
||||
```go
|
||||
// Current (strict):
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, err := bc.stateCache.OpenTrie(hash)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Alternative (more lenient, but maintains integrity):
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
// First try OpenTrie (preferred - full state)
|
||||
if _, err := bc.stateCache.OpenTrie(hash); err == nil {
|
||||
return true
|
||||
}
|
||||
// Fall back to StateAt which can use snapshots
|
||||
// Only for genesis block (number == 0)
|
||||
if bc.Genesis() != nil && bc.Genesis().Root() == hash {
|
||||
_, err := bc.StateAt(hash)
|
||||
return err == nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
**WARNING:** This option requires careful consideration - it's a workaround, not a fix.
|
||||
|
||||
---
|
||||
|
||||
### 5. Step-by-Step Plan to Get All Chains Operational
|
||||
|
||||
#### Phase 1: Verify Genesis State Accessibility
|
||||
|
||||
```bash
|
||||
# 1. Check genesis state in database
|
||||
cd /Users/z/work/lux/evm
|
||||
|
||||
# 2. Create a diagnostic tool to verify genesis state
|
||||
# Add to plugin/evm/admin.go:
|
||||
|
||||
# RPC: admin_verifyGenesisState
|
||||
# Returns: {genesisHash, genesisRoot, stateAccessible, snapshotAvailable}
|
||||
```
|
||||
|
||||
#### Phase 2: Fix Genesis Commit
|
||||
|
||||
1. Update `core/genesis.go:Commit()` to ensure state is fully committed
|
||||
2. Add verification step after commit
|
||||
3. Test with fresh database
|
||||
|
||||
```bash
|
||||
# Test genesis commit
|
||||
go test ./core -run TestGenesisCommit -v
|
||||
```
|
||||
|
||||
#### Phase 3: Deploy Zoo Subnet
|
||||
|
||||
```bash
|
||||
# 1. Create fresh database directory
|
||||
mkdir -p /tmp/zoo-test/db
|
||||
|
||||
# 2. Generate genesis with allocations
|
||||
cat > /tmp/zoo-genesis.json << 'EOF'
|
||||
{
|
||||
"config": {
|
||||
"chainId": 200200,
|
||||
"evmTimestamp": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"gasLimit": 12000000,
|
||||
"targetBlockRate": 2,
|
||||
"minBaseFee": 25000000000,
|
||||
"targetGas": 15000000,
|
||||
"baseFeeChangeDenominator": 36,
|
||||
"minBlockGasCost": 0,
|
||||
"maxBlockGasCost": 1000000,
|
||||
"blockGasCostStep": 200000
|
||||
}
|
||||
},
|
||||
"alloc": {
|
||||
"9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
|
||||
"balance": "0x193e5939a08ce9dbd480000000"
|
||||
}
|
||||
},
|
||||
"timestamp": "0x6727e9c3",
|
||||
"gasLimit": "0xb71b00",
|
||||
"difficulty": "0x0",
|
||||
"baseFeePerGas": "0x5d21dba00"
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. Deploy with lux CLI
|
||||
lux l2 deploy zoo \
|
||||
--genesis /tmp/zoo-genesis.json \
|
||||
--vm-binary /Users/z/work/lux/evm/build/evm \
|
||||
--local
|
||||
|
||||
# 4. Verify RPC is responding
|
||||
curl -X POST http://localhost:9650/v1/bc/zoo/rpc \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
|
||||
```
|
||||
|
||||
#### Phase 4: Import Historical Blocks
|
||||
|
||||
```bash
|
||||
# 1. Connect to running node's admin API
|
||||
curl -X POST http://localhost:9650/v1/bc/zoo/admin \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"jsonrpc":"2.0",
|
||||
"id":1,
|
||||
"method":"admin_importChain",
|
||||
"params":{
|
||||
"file":"/Users/z/work/lux/state/rlp/zoo-mainnet/zoo-mainnet-200200.rlp"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
#### Phase 5: Verify Import Success
|
||||
|
||||
```bash
|
||||
# Check block height
|
||||
curl -X POST http://localhost:9650/v1/bc/zoo/rpc \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
|
||||
|
||||
# Check specific block
|
||||
curl -X POST http://localhost:9650/v1/bc/zoo/rpc \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"jsonrpc":"2.0",
|
||||
"id":1,
|
||||
"method":"eth_getBlockByNumber",
|
||||
"params":["0x64",true]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Phase 6: Test AMM Commands
|
||||
|
||||
```bash
|
||||
# Add liquidity
|
||||
lux amm add-liquidity \
|
||||
--chain zoo \
|
||||
--token0 0x... \
|
||||
--token1 0x... \
|
||||
--amount0 1000 \
|
||||
--amount1 1000
|
||||
|
||||
# Swap
|
||||
lux amm swap \
|
||||
--chain zoo \
|
||||
--tokenIn 0x... \
|
||||
--tokenOut 0x... \
|
||||
--amountIn 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Technical Deep Dive: State Trie Architecture
|
||||
|
||||
```
|
||||
Genesis.Commit()
|
||||
|
|
||||
v
|
||||
+----------------+----------------+
|
||||
| |
|
||||
v v
|
||||
statedb.Commit() triedb.Commit()
|
||||
| |
|
||||
v v
|
||||
State Root Hash Trie Nodes to Disk
|
||||
| |
|
||||
+---------> rawdb.Write <---------+
|
||||
|
|
||||
v
|
||||
ethdb.Database
|
||||
|
|
||||
+------------+------------+
|
||||
| | |
|
||||
v v v
|
||||
HashDB PathDB Snapshots
|
||||
```
|
||||
|
||||
**The Gap:** After `Genesis.Commit()`, the state root is stored and trie nodes are written, BUT:
|
||||
|
||||
1. `stateCache.OpenTrie(root)` requires trie nodes to be in a specific location
|
||||
2. If using HashDB scheme, nodes must be in `triedb.HashDB`
|
||||
3. If using PathDB scheme (not supported per code), nodes must be in `triedb.PathDB`
|
||||
4. Snapshots provide account/storage data but NOT trie structure
|
||||
|
||||
**Solution:** Ensure `triedb.Commit(root, true)` is called with `report=true` to force disk sync, and verify accessibility via `NodeReader`.
|
||||
|
||||
---
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Change | Priority |
|
||||
|------|--------|----------|
|
||||
| `core/genesis.go` | Add state verification after Commit | HIGH |
|
||||
| `plugin/evm/admin.go` | Add genesis state check before import | HIGH |
|
||||
| `core/blockchain.go` | Log state accessibility during NewBlockChain | MEDIUM |
|
||||
| `plugin/evm/vm.go` | Add ensureGenesisState helper | MEDIUM |
|
||||
|
||||
---
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
- [ ] Genesis state accessible via `OpenTrie`
|
||||
- [ ] Block 0 (genesis) exists in rawdb
|
||||
- [ ] Canonical hash for block 0 set correctly
|
||||
- [ ] Chain config stored with genesis hash
|
||||
- [ ] Snapshot for genesis state available
|
||||
- [ ] Import chain skips genesis block (number == 0)
|
||||
- [ ] Parent hash of block 1 matches genesis hash
|
||||
- [ ] State root of genesis matches expected value
|
||||
|
||||
---
|
||||
|
||||
### Known Issues and Workarounds
|
||||
|
||||
**Issue 1:** `admin.importChain` skips Accept calls
|
||||
**Location:** `plugin/evm/admin.go:231-233`
|
||||
**Impact:** Imported blocks may not be finalized
|
||||
**Workaround:** Manually trigger consensus acceptance after import
|
||||
|
||||
**Issue 2:** StateScheme PathDB not supported
|
||||
**Location:** `plugin/evm/vm.go:623-626`
|
||||
**Impact:** Must use HashDB scheme
|
||||
**Workaround:** Ensure `state-scheme: "hash"` in config
|
||||
|
||||
**Issue 3:** Snapshot delayed init when state sync enabled
|
||||
**Location:** `plugin/evm/vm.go:590`
|
||||
**Impact:** Snapshots not available during import
|
||||
**Workaround:** Disable state sync for import node
|
||||
|
||||
## Cancun/BeaconRoot Disabled (2025-12-22)
|
||||
|
||||
### Status: ✅ FIXED - Lux Networks Do Not Use Ethereum Beacon Chain
|
||||
|
||||
Lux networks use their own consensus mechanism and do NOT use Ethereum's beacon chain. The following Cancun-era EIP-4844 fields are NOT required for Lux EVM blocks:
|
||||
|
||||
- `ExcessBlobGas` - NOT required
|
||||
- `BlobGasUsed` - NOT required
|
||||
- `ParentBeaconRoot` - NOT required
|
||||
|
||||
### Files Modified
|
||||
|
||||
1. **`consensus/dummy/consensus.go`** (lines 231-238)
|
||||
- Removed mandatory beaconRoot check
|
||||
- Removed mandatory excessBlobGas/blobGasUsed check
|
||||
- Only rejects if BlobGasUsed > 0 (which shouldn't happen)
|
||||
|
||||
2. **`plugin/evm/block_verification.go`** (lines 136-144)
|
||||
- Removed mandatory excessBlobGas check
|
||||
- Removed mandatory blobGasUsed check
|
||||
- Removed mandatory parentBeaconRoot check
|
||||
- Only rejects if BlobGasUsed > 0
|
||||
|
||||
### Why This Matters
|
||||
|
||||
Historic blocks from Lux networks (e.g., Zoo chain 200200) were created before Cancun was even conceived. These blocks don't have:
|
||||
- `ExcessBlobGas` field
|
||||
- `BlobGasUsed` field
|
||||
- `ParentBeaconRoot` field
|
||||
|
||||
Setting `cancunTime: null` in genesis doesn't help because the EVM code still checks these fields during block import via `admin.importChain`.
|
||||
|
||||
### Key Principle
|
||||
|
||||
**Lux networks are NOT Ethereum.** We use our own consensus (Quasar/POA) and don't need Ethereum's beacon chain validators or blob transaction support.
|
||||
|
||||
### Testing Verification
|
||||
|
||||
Successfully imported 799 blocks from Zoo chain (200200) after these fixes:
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:9630/v1/bc/<zoo-id>/admin \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"admin.importChain","params":{"file":"/path/to/zoo-mainnet-200200.rlp"}}'
|
||||
|
||||
# Response:
|
||||
{"jsonrpc":"2.0","result":{"success":true,"blocksImported":799,"heightAfter":799}}
|
||||
```
|
||||
|
||||
## CalcBlobFee Panic Fix (2026-01-29)
|
||||
|
||||
### Status: ✅ FIXED - EVM Plugin No Longer Panics During Chain Initialization
|
||||
|
||||
Fixed a panic that occurred during EVM plugin initialization: `"calculating blob fee on unsupported fork"`.
|
||||
|
||||
### Root Cause
|
||||
|
||||
The `collectUnflattenedLogs()` function in `core/blockchain.go` was calling `eip4844.CalcBlobFee()` when a block had `ExcessBlobGas` set, but without checking if the Cancun fork was actually active. The `CalcBlobFee()` function panics if called on a chain without Cancun fork enabled.
|
||||
|
||||
**Stack trace:**
|
||||
```
|
||||
CalcBlobFee() → collectUnflattenedLogs() → collectLogs() → reorg() →
|
||||
writeKnownBlock() → setPreference() → loadLastState() → NewBlockChain() → PANIC
|
||||
```
|
||||
|
||||
### File Modified
|
||||
|
||||
**`core/blockchain.go`** (lines 1667-1672):
|
||||
```go
|
||||
// Before (panics):
|
||||
if excessBlobGas != nil {
|
||||
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header())
|
||||
}
|
||||
|
||||
// After (safe):
|
||||
// Only calculate blob fee if Cancun fork is active AND block has ExcessBlobGas.
|
||||
// Without the IsCancun check, CalcBlobFee panics with "calculating blob fee on unsupported fork"
|
||||
// when blocks have ExcessBlobGas set but the chain config doesn't have Cancun enabled.
|
||||
if excessBlobGas != nil && bc.chainConfig.IsCancun(b.Number(), b.Time()) {
|
||||
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header())
|
||||
}
|
||||
```
|
||||
|
||||
### Why This Happened
|
||||
|
||||
- Blocks may have `ExcessBlobGas` field set (e.g., from RLP imports or state sync)
|
||||
- Chain config may not have Cancun fork enabled (especially for legacy Lux networks)
|
||||
- The code assumed if `ExcessBlobGas != nil`, then Cancun must be active (wrong!)
|
||||
|
||||
### Verification
|
||||
|
||||
After the fix, EVM plugin initializes successfully without panic:
|
||||
```
|
||||
[EVM-DEBUG] parseGenesis succeeded: chainID=1337 alloc=16 accounts
|
||||
[EVM-DEBUG] initializeChain: eth.New succeeded
|
||||
[EVM-DEBUG] Chain initialized successfully
|
||||
```
|
||||
**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)
|
||||
|
||||
@@ -1418,208 +879,6 @@ Full specification documented in:
|
||||
|
||||
---
|
||||
|
||||
## SPC Chain Genesis Recovery (2025-12-28)
|
||||
|
||||
### Problem: Genesis Hash Mismatch for Existing Chain
|
||||
|
||||
When deploying SPC chain with RLP block import, the genesis hash computed from genesis.json must match the original chain's block 0 hash exactly. Otherwise block import fails.
|
||||
|
||||
### Solution: Extract Original Genesis from ChainData
|
||||
|
||||
Extracted the original genesis alloc from the SPC pebbledb chaindata by:
|
||||
1. Analyzing pathdb key structure to find account hashes
|
||||
2. Extracting addresses from RLP transaction data (sender addresses from blocks 1-10)
|
||||
3. Matching Keccak256(address) with account hashes to identify addresses
|
||||
4. Computing state root to verify correct alloc
|
||||
|
||||
### SPC Genesis Configuration
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Chain ID | 36911 |
|
||||
| Genesis Hash | `0x4dc9fd5cf4ee64609f140ba0aa50f320cadf0ae8b59a29415979bc05b17cfac8` |
|
||||
| State Root | `0xb75eb0a501516b8d6e691c705660f05f77bc23c47378158152ba543f74556c6f` |
|
||||
| Timestamp | 1731369637 (0x67329aa5) |
|
||||
| GasLimit | 12000000 (0xb71b00) |
|
||||
| BaseFee | 25000000000 (0x5d21dba00) |
|
||||
| Token Symbol | SPC |
|
||||
| Total Supply | 1,000,000,000 SPC (1 billion) |
|
||||
|
||||
### Genesis Alloc (2 entries)
|
||||
|
||||
```json
|
||||
{
|
||||
"alloc": {
|
||||
"0200000000000000000000000000000000000005": {
|
||||
"code": "0x01",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x1"
|
||||
},
|
||||
"12c6EE1d226225756F57B75957d2BF3Ab2e8597e": {
|
||||
"balance": "0x33b2e3c9fd0803ce8000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Address | Role | Balance |
|
||||
|---------|------|---------|
|
||||
| `0x0200...0005` | Warp Precompile | 0 (code=0x01, nonce=1) |
|
||||
| `0x12c6EE1d...` | Main Token Holder | 1,000,000,000 SPC |
|
||||
|
||||
### Transaction History (Blocks 1-10)
|
||||
|
||||
The main holder distributed tokens to 9 addresses:
|
||||
- Block 1: 1,000,000 SPC to `0x53dc35fA...`
|
||||
- Block 2: 9,000,000 SPC to `0x3eB5a2b6...`
|
||||
- Blocks 3-10: Further distribution to other addresses
|
||||
|
||||
### Genesis File Location
|
||||
|
||||
- **Path**: `/Users/z/work/lux/state/rlp/spc-mainnet/genesis.json`
|
||||
- **RLP Blocks**: `/Users/z/work/lux/state/rlp/spc-mainnet/spc-mainnet-36911.rlp`
|
||||
- **ChainData**: `/Users/z/work/lux/state/pebbledb/spc-mainnet/`
|
||||
|
||||
### Key Insight
|
||||
|
||||
The genesis produces the correct state root naturally. The original genesis had only 2 accounts:
|
||||
1. The warp precompile at `0x0200...0005`
|
||||
2. The main token holder at `0x12c6EE1d...` with the full supply
|
||||
|
||||
All other addresses were created through subsequent transactions.
|
||||
|
||||
---
|
||||
|
||||
## P-Chain/Info API Deadlock Fix (2026-01-05)
|
||||
|
||||
### Problem: API Timeouts After admin_importChain
|
||||
|
||||
After calling `admin_importChain`, P-chain and Info APIs would hang/timeout. The C-Chain RPC continued working, but cross-chain API calls would fail.
|
||||
|
||||
### Root Cause
|
||||
|
||||
The `PostImportCallback` was called **synchronously** while `chainmu.Lock()` was held:
|
||||
1. `SetLastAcceptedBlockDirect()` acquires `chainmu.Lock()`
|
||||
2. `PostImportCallback` is called synchronously (within the lock)
|
||||
3. PostImportCallback may contend with P-chain/Info API locks
|
||||
4. **Deadlock**: APIs waiting for chainmu, chainmu waiting for APIs
|
||||
|
||||
### Solution: Async PostImportCallback
|
||||
|
||||
Made the callback run in a goroutine to prevent cross-chain mutex contention.
|
||||
|
||||
### Files Fixed
|
||||
|
||||
**eth/api_admin.go** (lines 180-196):
|
||||
```go
|
||||
// CRITICAL: Call the post-import callback to update the VM layer's acceptedBlockDB.
|
||||
// Without this, ReadLastAccepted() returns genesis hash on restart because
|
||||
// acceptedBlockDB is not updated by the admin API import path.
|
||||
//
|
||||
// Run asynchronously to avoid deadlock: SetLastAcceptedBlockDirect holds chainmu.Lock(),
|
||||
// and PostImportCallback may contend with P-chain/Info API locks. By returning success
|
||||
// immediately after state commit and letting the callback complete in background,
|
||||
// we prevent cross-chain mutex contention that causes API timeouts.
|
||||
go func() {
|
||||
if err := api.eth.CallPostImportCallback(lastInsertedBlock.Hash(), lastInsertedBlock.NumberU64()); err != nil {
|
||||
log.Error("PostImportCallback failed", "error", err)
|
||||
return
|
||||
}
|
||||
log.Info("ImportChain: post-import callback completed asynchronously")
|
||||
}()
|
||||
```
|
||||
|
||||
**plugin/evm/admin_api.go** (lines 315-323):
|
||||
```go
|
||||
// CRITICAL: Call PostImportCallback to update acceptedBlockDB for persistence across restarts.
|
||||
// Without this, the VM's lastAcceptedKey won't be updated, causing blocks to be lost on restart.
|
||||
//
|
||||
// Run asynchronously to avoid deadlock: SetLastAcceptedBlockDirect holds chainmu.Lock(),
|
||||
// and PostImportCallback may contend with P-chain/Info API locks.
|
||||
if finalBlock != nil {
|
||||
go func(hash common.Hash, height uint64) {
|
||||
if err := eth.CallPostImportCallback(hash, height); err != nil {
|
||||
log.Error("PostImportCallback failed", "error", err)
|
||||
return
|
||||
}
|
||||
log.Info("admin_importChain: post-import callback completed asynchronously", "height", height)
|
||||
}(finalBlock.Hash(), finalBlock.NumberU64())
|
||||
}
|
||||
```
|
||||
|
||||
### Also Fixed: Duplicate Logging
|
||||
|
||||
Removed duplicate logging patterns where `log.Error()` was followed by `return fmt.Errorf()`, which caused the same error to be logged twice.
|
||||
|
||||
### Verification
|
||||
|
||||
All APIs responding after block import:
|
||||
- C-Chain RPC: `eth_blockNumber` returns correct height
|
||||
- P-Chain: `platform.getBlockchains` responds immediately
|
||||
- Info API: `info.getNodeVersion` responds immediately
|
||||
|
||||
---
|
||||
|
||||
## ZAP Transport Type Assertion Fix (2026-01-29)
|
||||
|
||||
### Status: ✅ FIXED - ZAP Transport Working for C-Chain (EVM)
|
||||
|
||||
Fixed a type assertion failure where `*zap.Client` wasn't being recognized as `chain.ChainVM` in the node's chains manager type switch.
|
||||
|
||||
### Problem
|
||||
|
||||
When starting the network with ZAP transport (no gRPC), the C-Chain would fail with:
|
||||
```
|
||||
unsupported VM type: *zap.Client
|
||||
```
|
||||
|
||||
The ZAP handshake succeeded, but the chains manager's type switch at `chains/manager.go:880` didn't match `*zap.Client` against `chain.ChainVM`.
|
||||
|
||||
### Root Cause
|
||||
|
||||
The issue was caused by the `go.work` workspace. With all packages (node, vm, consensus) using local versions via go.work, the type definitions must be consistent across all packages. If the packages were built at different times or with different states, the `chain.ChainVM` interface from `github.com/luxfi/vm/chain` wouldn't match.
|
||||
|
||||
### Solution
|
||||
|
||||
Rebuild all packages consistently within the go.work workspace:
|
||||
1. Build the node: `cd /Users/z/work/lux/node && go build -o build/luxd ./main`
|
||||
2. Build the EVM plugin: `cd /Users/z/work/lux/evm && go build -o ~/.lux/plugins/current/<VMID> ./plugin`
|
||||
|
||||
### Verification
|
||||
|
||||
After rebuilding, the C-Chain initializes successfully via ZAP:
|
||||
```
|
||||
plugin handshake succeeded via ZAP
|
||||
VM client connected via ZAP
|
||||
DEBUG: About to check VM type vmType=*zap.Client
|
||||
creating linear chain
|
||||
ZAP handleInitialize
|
||||
ZAP VM initialized successfully
|
||||
VM initialized via ZAP
|
||||
CHAIN CREATED SUCCESSFULLY chainAlias=C vmName=evm
|
||||
C-Chain automining ENABLED, starting automining loop
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
- **`/Users/z/work/lux/node/chains/manager.go:880`** - Type switch that checks `chain.ChainVM`
|
||||
- **`/Users/z/work/lux/node/vms/rpcchainvm/zap/client.go`** - ZAP client that implements `chain.ChainVM`
|
||||
- **`/Users/z/work/lux/vm/chain/interfaces.go`** - Defines `chain.ChainVM` as alias to `block.ChainVM`
|
||||
- **`/Users/z/work/lux/go.work`** - Workspace file that includes all local packages
|
||||
|
||||
### Key Insight
|
||||
|
||||
With `go.work`, Go uses local package versions instead of published versions from the module cache. This means:
|
||||
- All packages must be built together for type consistency
|
||||
- The `chain.ChainVM` interface from the local vm package must match what the node imports
|
||||
- The compile-time check `var _ chain.ChainVM = (*Client)(nil)` in the ZAP client ensures interface compliance
|
||||
|
||||
### Additional Notes
|
||||
|
||||
The remaining network startup failures (optional chains K, G, Z, T, B, A, Q) are due to missing VM plugins for optional VMs (Key, Graph, ZK, Threshold, Bridge, AI, Quantum), not ZAP transport issues. Build with `-tags=allvms` to include these optional VMs.
|
||||
|
||||
---
|
||||
|
||||
## Pluggable Backends + Auto-Import
|
||||
|
||||
### Backend selection (build tags)
|
||||
@@ -1680,5 +939,5 @@ for every EVM-based L2 chain in Lux. No second filename, no aliases.
|
||||
`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 + subnet EVM work.
|
||||
(`luxfi/evm`) for all new C-Chain and L1 EVM work.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user