chainid: add EVMChainIDFor(brand, env string) string-input helper

LP-018 §Canonical EVM chainID map specifies the (brand × env) → uint64
table. The existing typed lookup EVMChainID(family, env) takes a
NetworkFamily + uint32; CRD validators, admission webhooks, and
operator-side helpers often hold brand+env as raw strings instead.

EVMChainIDFor wraps EVMChainID with case-insensitive string parsing for
both brand and env ("mainnet"|"main", "testnet"|"test", etc.). No
parallel data table — one source of truth.

Also retire stale "sovereign=true|false per LP-182" docstrings on
Family* constants; the locked NetworkSpec carries only networkID +
validators, and FamilyLiquid/FamilyPars can run as either Anchored
(validators > 0 on a Lux primary) or Independent (own networkID).

Update LP-182 references to LP-018 (LP-182 archived).
This commit is contained in:
Hanzo AI
2026-06-02 12:01:03 -07:00
parent 266de9e8af
commit 435ba69bbb
2 changed files with 110 additions and 21 deletions
+46 -17
View File
@@ -1,15 +1,14 @@
// Copyright (C) 2024-2026, Lux Partners Limited. All rights reserved.
// See the file LICENSE for licensing terms.
// LP-182 §Appendix A — canonical EVM chainID map per (network family,
// environment) tuple. This is the source of truth that operator CRs
// and per-network genesis blobs must agree with; a misconfigured
// `NetworkSpec.EVMChainID` that does not appear in this map is an
// authoring error.
// LP-018 §Canonical EVM chainID map per (network family, environment)
// tuple. This is the source of truth that operator CRs and per-network
// genesis blobs must agree with; a misconfigured `NetworkSpec.EVMChainID`
// that does not appear in this map is an authoring error.
//
// One source of truth, one way to look up: pass a (family, env) pair,
// get back the EVMChainID (or 0 for "unknown / TBD"). The map covers
// every brand-X-environment cell at LP-182 publication time; new cells
// every brand-X-environment cell at LP-018 publication time; new cells
// land here when the corresponding genesis configs land in the
// embedded tree.
@@ -27,27 +26,28 @@ const (
// FamilyLux is the Lux primary network family (C-Chain).
FamilyLux NetworkFamily = "lux"
// FamilyHanzo is the Hanzo L2 family hosted on the Lux primary
// network's validator set (sovereign=false per LP-182).
// network's validator set (validators==0 per LP-018).
FamilyHanzo NetworkFamily = "hanzo"
// FamilyZoo is the Zoo L2 family hosted on the Lux primary network's
// validator set (sovereign=false per LP-182).
// validator set (validators==0 per LP-018).
FamilyZoo NetworkFamily = "zoo"
// FamilySPC is the SPC L2 family hosted on the Lux primary network's
// validator set (sovereign=false per LP-182).
// validator set (validators==0 per LP-018).
FamilySPC NetworkFamily = "spc"
// FamilyLiquid is the Liquid sovereign L1 family with its own
// validator set (sovereign=true per LP-182).
// FamilyLiquid is the Liquid network family. Runs as Anchored on a
// Lux primary (validators>0 with networkID in {1,2,3,1337}) or as
// Independent (own networkID = 8675309/8675310/8675312 per env).
FamilyFamily = "liquid"
// FamilyPars is the Pars sovereign L1 family with its own validator
// set (sovereign=true per LP-182).
// FamilyPars is the Pars network family. Runs as Anchored on a Lux
// primary or as Independent — same dual-mode option as Liquid.
FamilyPars NetworkFamily = "pars"
// FamilyOsage is the Osage sovereign L1 family (placeholder pending
// FamilyOsage is the Osage network family (placeholder pending
// genesis publication).
FamilyOsage NetworkFamily = "osage"
)
// EVMChainID returns the canonical EVM chainID for a (family, env)
// tuple per LP-182 §Appendix A. The env argument MUST be one of
// tuple per LP-018 §Canonical EVM chainID map. The env argument MUST be one of
// MainnetID / TestnetID / DevnetID / LocalID (the four canonical
// primary network IDs). Any other value returns (0, false).
//
@@ -118,14 +118,14 @@ func EVMChainID(family NetworkFamily, env uint32) (uint64, bool) {
case DevnetID:
return 494951, true
}
// Osage cells are TBD as of LP-182 publication; lookup returns
// Osage cells are TBD as of LP-018 publication; lookup returns
// (0, false) until the genesis configs land here.
}
return 0, false
}
// NetworkFamilyOf returns the canonical NetworkFamily for a chainID,
// per LP-182 §Appendix A. The first uint64 → family mapping that
// per LP-018 §Canonical EVM chainID map. The first uint64 → family mapping that
// matches wins. The boolean is false when no family claims the
// chainID (caller must surface this as an authoring error).
//
@@ -174,3 +174,32 @@ func ParseNetworkFamily(s string) (NetworkFamily, bool) {
}
return "", false
}
// EVMChainIDFor is the string-input convenience over EVMChainID for
// callers that hold brand+env as text (CRD validators, operator-side
// helpers, k8s admission webhooks). Case-insensitive on brand.
//
// env must be one of "mainnet", "testnet", "devnet", "localnet" (case-
// insensitive). Any other env or unknown brand returns (0, false). One
// way to look up by string; delegates to the typed EVMChainID — there
// is no parallel data table.
func EVMChainIDFor(brand, env string) (uint64, bool) {
fam, ok := ParseNetworkFamily(brand)
if !ok {
return 0, false
}
var envID uint32
switch strings.ToLower(env) {
case "mainnet", "main":
envID = MainnetID
case "testnet", "test":
envID = TestnetID
case "devnet", "dev":
envID = DevnetID
case "localnet", "local":
envID = LocalID
default:
return 0, false
}
return EVMChainID(fam, envID)
}
+64 -4
View File
@@ -5,10 +5,10 @@ package configs
import "testing"
// TestEVMChainID_KnownCells locks the canonical map per LP-182
// §Appendix A. Any change to these values is a backward-incompatible
// chainID rebrand and would replay-protect-conflict with
// already-deployed contracts; this test is the protective floor.
// TestEVMChainID_KnownCells locks the canonical map per LP-018
// §Canonical EVM chainID map. Any change to these values is a
// backward-incompatible chainID rebrand and would replay-protect-conflict
// with already-deployed contracts; this test is the protective floor.
func TestEVMChainID_KnownCells(t *testing.T) {
cases := []struct {
family NetworkFamily
@@ -50,6 +50,9 @@ func TestEVMChainID_KnownCells(t *testing.T) {
// TestEVMChainID_UnknownReturnsFalse — an unknown family or an env
// outside the canonical four must return (0, false), not a silent
// default. This protects against typos in operator CRs.
//
// (Filename retains lp182_ prefix because that was the file's name at
// commit time and the rename produces churn unrelated to behavior.)
func TestEVMChainID_UnknownReturnsFalse(t *testing.T) {
if got, ok := EVMChainID(NetworkFamily("bogus"), MainnetID); ok || got != 0 {
t.Errorf("EVMChainID(bogus, mainnet) = (%d, %v), want (0, false)", got, ok)
@@ -123,3 +126,60 @@ func TestParseNetworkFamily_CaseInsensitive(t *testing.T) {
t.Errorf("ParseNetworkFamily(bogus) = (%q, %v), want (\"\", false)", got, ok)
}
}
// TestEVMChainIDFor_StringInputs locks the string-input convenience
// wrapper to the full LP-182 §Appendix A table. This is the API
// CRD validators / operator webhooks call.
func TestEVMChainIDFor_StringInputs(t *testing.T) {
cases := []struct {
brand, env string
want uint64
}{
{"lux", "mainnet", 96369},
{"lux", "testnet", 96368},
{"lux", "devnet", 96370},
{"lux", "localnet", 31337},
{"LUX", "MAINNET", 96369},
{"Lux", "Main", 96369},
{"hanzo", "mainnet", 36963},
{"hanzo", "testnet", 36962},
{"hanzo", "devnet", 36964},
{"zoo", "mainnet", 200200},
{"zoo", "testnet", 200201},
{"zoo", "devnet", 200202},
{"spc", "mainnet", 36911},
{"spc", "testnet", 36910},
{"spc", "devnet", 36912},
{"liquid", "mainnet", 8675309},
{"liquid", "testnet", 8675310},
{"liquid", "devnet", 8675312},
{"pars", "mainnet", 7070},
{"pars", "testnet", 494950},
{"pars", "devnet", 494951},
}
for _, c := range cases {
got, ok := EVMChainIDFor(c.brand, c.env)
if !ok {
t.Errorf("EVMChainIDFor(%q,%q) = (_, false), want (%d, true)", c.brand, c.env, c.want)
continue
}
if got != c.want {
t.Errorf("EVMChainIDFor(%q,%q) = %d, want %d", c.brand, c.env, got, c.want)
}
}
}
// TestEVMChainIDFor_UnknownReturnsFalse — typos and TBD cells return
// (0, false) instead of a silent default.
func TestEVMChainIDFor_UnknownReturnsFalse(t *testing.T) {
for _, c := range []struct{ brand, env string }{
{"bogus", "mainnet"},
{"lux", "bogus"},
{"hanzo", "localnet"}, // hanzo has no localnet cell
{"", ""},
} {
if got, ok := EVMChainIDFor(c.brand, c.env); ok || got != 0 {
t.Errorf("EVMChainIDFor(%q,%q) = (%d, %v), want (0, false)", c.brand, c.env, got, ok)
}
}
}