mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
Sweep "brand" out of the API entirely. Package and types now match the
domain vocabulary the user sees in chain.yaml + CLI output:
pkg/brand → pkg/network (one package per concept)
Brand → Spec (parsed chain.yaml)
NetCfg → Env (per-env block)
RuntimeProfile → Profile
RuntimeProfile.Brand → Profile.Name
ChainLock → Lock
$LUX_BRAND_PATH → $LUX_NETWORK_PATH
Surface BOTH identifiers distinctly — they are deliberately different:
NetworkID uint32 — validator wire, --network-id, info.getNetworkID
PrimaryEvmChainID uint64 — C-chain EIP-155, eth_chainId, MetaMask
Lux brand keeps them apart (NID 1 / EVM 96369). Sovereign-L1 brand
forks (Zoo, Hanzo, Pars, Osage, Liquidity) collapse them by convention
(Zoo NID == EVM == 200200). The validator no longer enforces equality;
both are surfaced in lux ps and lux info.
New verb:
lux info <name>/<env> full Profile dump (both IDs, ports, paths,
snapshot name, service label, RPC endpoints,
live probe state)
lux ps output now shows NETWORK_ID and EVM_CHAIN_ID as separate columns.
chain.lock manifest now records both IDs so booting against a foreign
EVM-chain-ID dataDir is also rejected (previously only checked
networkID).
102 lines
2.8 KiB
Go
102 lines
2.8 KiB
Go
package network
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
const lockFileName = "chain.lock"
|
|
|
|
// LockPath is <DataDir>/chain.lock.
|
|
func (p *Profile) LockPath() string {
|
|
return filepath.Join(p.DataDir, lockFileName)
|
|
}
|
|
|
|
// ReadLock loads the lock if present. Returns (nil, nil) when absent.
|
|
func (p *Profile) ReadLock() (*Lock, error) {
|
|
b, err := os.ReadFile(p.LockPath()) //nolint:gosec
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
var lk Lock
|
|
if err := json.Unmarshal(b, &lk); err != nil {
|
|
return nil, fmt.Errorf("parse %s: %w", p.LockPath(), err)
|
|
}
|
|
return &lk, nil
|
|
}
|
|
|
|
// WriteLock writes a fresh manifest. Caller computes genesisHash.
|
|
func (p *Profile) WriteLock(genesisHash string) error {
|
|
if err := os.MkdirAll(p.DataDir, 0o750); err != nil {
|
|
return err
|
|
}
|
|
lk := Lock{
|
|
Version: SchemaVersion,
|
|
Name: p.Name,
|
|
Env: p.Env,
|
|
NetworkID: p.NetworkID,
|
|
PrimaryEvmChainID: p.PrimaryEvmChainID,
|
|
HTTPPort: p.HTTPPort,
|
|
StakingPort: p.StakingPort,
|
|
GenesisHash: genesisHash,
|
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
b, err := json.MarshalIndent(lk, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(p.LockPath(), b, 0o600)
|
|
}
|
|
|
|
// VerifyOrCreate inspects DataDir. If a lock exists it must match the
|
|
// profile (NetworkID + genesisHash). If absent and DataDir is empty,
|
|
// we write a fresh lock. If absent but DataDir has chain data, the dir
|
|
// is orphaned — refuse to mount so we don't corrupt unknown state.
|
|
func (p *Profile) VerifyOrCreate(genesisHash string) error {
|
|
lk, err := p.ReadLock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if lk != nil {
|
|
if lk.NetworkID != p.NetworkID {
|
|
return fmt.Errorf(
|
|
"%s networkID mismatch: lock=%d, profile=%d (refusing to mount foreign state)",
|
|
p.DataDir, lk.NetworkID, p.NetworkID,
|
|
)
|
|
}
|
|
if lk.PrimaryEvmChainID != 0 && p.PrimaryEvmChainID != 0 && lk.PrimaryEvmChainID != p.PrimaryEvmChainID {
|
|
return fmt.Errorf(
|
|
"%s primaryEvmChainID mismatch: lock=%d, profile=%d",
|
|
p.DataDir, lk.PrimaryEvmChainID, p.PrimaryEvmChainID,
|
|
)
|
|
}
|
|
if lk.GenesisHash != "" && genesisHash != "" && lk.GenesisHash != genesisHash {
|
|
return fmt.Errorf(
|
|
"%s genesisHash mismatch: lock=%s, computed=%s",
|
|
p.DataDir, lk.GenesisHash, genesisHash,
|
|
)
|
|
}
|
|
if lk.HTTPPort != 0 && lk.HTTPPort != p.HTTPPort {
|
|
return fmt.Errorf(
|
|
"%s httpPort mismatch: lock=%d, profile=%d",
|
|
p.DataDir, lk.HTTPPort, p.HTTPPort,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
if entries, _ := os.ReadDir(p.DataDir); len(entries) > 0 {
|
|
return fmt.Errorf(
|
|
"%s has data but no %s — refusing to mount unknown state. delete dir or write %s by hand",
|
|
p.DataDir, lockFileName, lockFileName,
|
|
)
|
|
}
|
|
return p.WriteLock(genesisHash)
|
|
}
|