mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
269 lines
9.6 KiB
Go
269 lines
9.6 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
||
// See the file LICENSE for licensing terms.
|
||
|
||
package configs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
)
|
||
|
||
// TestGetGenesis_CChainShardPresentEmbedsCChainGenesis confirms that for
|
||
// every embedded network that ships a cchain.json shard, the marshalled
|
||
// primary genesis JSON contains a non-empty cChainGenesis field — i.e. the
|
||
// builder will emit a C-Chain CreateChainTx for that network.
|
||
//
|
||
// This is the new contract that replaced the LUX_DISABLE_CCHAIN env knob:
|
||
// chain presence is data-driven (does the shard exist?) rather than
|
||
// runtime-toggled (is an env set?). Adding/removing a chain from a
|
||
// network's primary genesis is a file-tree edit, not an operator switch.
|
||
func TestGetGenesis_CChainShardPresentEmbedsCChainGenesis(t *testing.T) {
|
||
for _, name := range []string{"mainnet", "testnet", "localnet"} {
|
||
t.Run(name, func(t *testing.T) {
|
||
data, err := GetGenesis(networkIDFromName(t, name))
|
||
if err != nil {
|
||
t.Fatalf("GetGenesis(%s): %v", name, err)
|
||
}
|
||
var m map[string]any
|
||
if err := json.Unmarshal(data, &m); err != nil {
|
||
t.Fatalf("parse %s: %v", name, err)
|
||
}
|
||
got, _ := m["cChainGenesis"].(string)
|
||
if got == "" {
|
||
t.Fatalf("%s: expected non-empty cChainGenesis (embedded shard present)", name)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestGetGenesis_XChainShardPresentEmbedsXChainGenesis is the X-Chain
|
||
// equivalent of the C-Chain test above. Every Lux network ships an
|
||
// xchain.json shard (the small asset descriptor
|
||
// `{"symbol":"LUX","name":"Lux","denomination":9}`); the loader must
|
||
// thread it into the marshalled primary genesis JSON as the
|
||
// xChainGenesis field, where the builder later parses it to construct
|
||
// the XVM genesis and CreateChainTx.
|
||
//
|
||
// Together with TestBuildGenesisFromDir_AbsentShardEmptyOptIn (which
|
||
// covers the absent-shard case for X), this enforces the same data-
|
||
// driven contract for X that we already have for C/Q/Z: chain
|
||
// presence is a file-tree edit, not an operator switch. The previous
|
||
// hardcoded `Symbol: "LUX", Name: "Lux", Denomination: 9` literal in
|
||
// builder.FromConfig is now sourced from the shard, so a P-only
|
||
// network (P-only shape) can opt out by simply omitting the file.
|
||
func TestGetGenesis_XChainShardPresentEmbedsXChainGenesis(t *testing.T) {
|
||
for _, name := range []string{"mainnet", "testnet", "localnet"} {
|
||
t.Run(name, func(t *testing.T) {
|
||
data, err := GetGenesis(networkIDFromName(t, name))
|
||
if err != nil {
|
||
t.Fatalf("GetGenesis(%s): %v", name, err)
|
||
}
|
||
var m map[string]any
|
||
if err := json.Unmarshal(data, &m); err != nil {
|
||
t.Fatalf("parse %s: %v", name, err)
|
||
}
|
||
got, _ := m["xChainGenesis"].(string)
|
||
if got == "" {
|
||
t.Fatalf("%s: expected non-empty xChainGenesis (embedded shard present)", name)
|
||
}
|
||
// Sanity-check the shard parses and pins the canonical Lux asset.
|
||
var asset struct {
|
||
Symbol string `json:"symbol"`
|
||
Name string `json:"name"`
|
||
Denomination byte `json:"denomination"`
|
||
}
|
||
if err := json.Unmarshal([]byte(got), &asset); err != nil {
|
||
t.Fatalf("%s: xChainGenesis shard unparseable: %v", name, err)
|
||
}
|
||
if asset.Symbol != "LUX" || asset.Name != "Lux" || asset.Denomination != 9 {
|
||
t.Fatalf("%s: xChainGenesis shard drift: got %+v want {LUX Lux 9}", name, asset)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestBuildGenesisFromDir_AbsentShardEmptyOptIn confirms the FS-fallback
|
||
// loader honours the same "shard absent → opt-out" semantics as the
|
||
// embedded loader. An operator who wants a P+X-only network just omits
|
||
// the relevant chain shards from their dir; no env knob, no flag.
|
||
func TestBuildGenesisFromDir_AbsentShardEmptyOptIn(t *testing.T) {
|
||
dir := t.TempDir()
|
||
|
||
write := func(name, body string) {
|
||
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
|
||
t.Fatalf("write %s: %v", name, err)
|
||
}
|
||
}
|
||
// Minimal shards: network + P-Chain only. No X/C/Q/Z. This is the
|
||
// P-only shape — every primary-network chain (X included)
|
||
// is opt-in by shard presence.
|
||
write("network.json", `{"networkID":1337,"startTime":1735689600,"message":"P-only test"}`)
|
||
write("pchain.json", `{
|
||
"allocations":[],
|
||
"initialStakeDuration":31536000,
|
||
"initialStakeDurationOffset":5400,
|
||
"initialStakedFunds":[],
|
||
"initialStakers":[]
|
||
}`)
|
||
|
||
data, err := buildGenesisFromDir(dir)
|
||
if err != nil {
|
||
t.Fatalf("buildGenesisFromDir: %v", err)
|
||
}
|
||
var m map[string]any
|
||
if err := json.Unmarshal(data, &m); err != nil {
|
||
t.Fatalf("parse: %v", err)
|
||
}
|
||
for _, field := range []string{"xChainGenesis", "cChainGenesis", "qChainGenesis", "zChainGenesis"} {
|
||
if got, _ := m[field].(string); got != "" {
|
||
t.Fatalf("absent shard should yield empty %s; got %q", field, got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// networkIDFromName resolves a network directory name to its canonical
|
||
// network ID. Test helper that mirrors networkNameFromID's inverse.
|
||
func networkIDFromName(t *testing.T, name string) uint32 {
|
||
t.Helper()
|
||
switch name {
|
||
case "mainnet":
|
||
return MainnetID
|
||
case "testnet":
|
||
return TestnetID
|
||
case "devnet":
|
||
return DevnetID
|
||
case "localnet":
|
||
return LocalID
|
||
default:
|
||
t.Fatalf("unknown network name %q", name)
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// TestGetGenesis_AllPrimaryChainsBakedIn locks the contract that every
|
||
// primary network (mainnet, testnet, devnet, localnet) ships ALL 10
|
||
// primary-network chain shards baked into its genesis. This means the
|
||
// chains spawn at network startup with NO post-launch CreateBlockchainTx —
|
||
// chain set is fully data-driven by which shards are committed.
|
||
//
|
||
// The 10 primary-network chains, in canonical order:
|
||
//
|
||
// X XVM exchange / UTXO asset
|
||
// C EVM contracts
|
||
// D DexVM decentralized exchange (CLOB + AMM + perps)
|
||
// Q QuantumVM post-quantum consensus (Quasar / ML-DSA / ML-KEM)
|
||
// A AIVM AI verification / attestation
|
||
// B BridgeVM cross-chain bridge (MPC signing)
|
||
// T ThresholdVM threshold FHE encrypted compute (LP-?? F-chain semantic)
|
||
// Z ZKVM zero-knowledge proofs / private state
|
||
// G GraphVM graph database
|
||
// K KeyVM KMS — MPC topology (LP-?? M-chain semantic)
|
||
//
|
||
// P-Chain is the primary network itself and carries the validator set + chain
|
||
// registry; it has no shard slot because it isn't a CreateChainTx entry.
|
||
//
|
||
// If a future genesis intentionally needs to omit a chain (e.g. a P+X-only
|
||
// regulated-securities L1), that's a NEW config-tree, not a regression on the
|
||
// canonical Lux primary networks. Editing this test to drop a chain on
|
||
// mainnet/testnet/devnet is a load-bearing decision — bring it to design
|
||
// review.
|
||
func TestGetGenesis_AllPrimaryChainsBakedIn(t *testing.T) {
|
||
required := []string{
|
||
"xChainGenesis",
|
||
"cChainGenesis",
|
||
"dChainGenesis",
|
||
"qChainGenesis",
|
||
"aChainGenesis",
|
||
"bChainGenesis",
|
||
"tChainGenesis",
|
||
"zChainGenesis",
|
||
"gChainGenesis",
|
||
"kChainGenesis",
|
||
}
|
||
for _, name := range []string{"mainnet", "testnet", "devnet", "localnet"} {
|
||
t.Run(name, func(t *testing.T) {
|
||
data, err := GetGenesis(networkIDFromName(t, name))
|
||
if err != nil {
|
||
t.Fatalf("GetGenesis(%s): %v", name, err)
|
||
}
|
||
var m map[string]any
|
||
if err := json.Unmarshal(data, &m); err != nil {
|
||
t.Fatalf("parse %s: %v", name, err)
|
||
}
|
||
for _, field := range required {
|
||
got, _ := m[field].(string)
|
||
if got == "" {
|
||
t.Fatalf("%s: required chain field %q is empty — primary network must ship every letter-chain shard", name, field)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestPrimaryChainShards_PerChainCanonicalChainID locks the canonical
|
||
// per-letter EVM chainId scheme so any drift between the network configs
|
||
// is caught at test time. Pattern:
|
||
//
|
||
// letter base = 96369 + 100*(idx_in_alphabet_relative_to_C)
|
||
// per-network offset: testnet = base-1, devnet = base+1 (mirrors C-Chain)
|
||
//
|
||
// Exception: the C-Chain devnet EVM chainID is the owner-locked canonical
|
||
// value 96367 (see DevnetChainID), NOT base+1 (96370). The +1 rule holds
|
||
// for the D/Q/A/... shards below; C is the one cell pinned by the
|
||
// canonical map rather than derived from the arithmetic.
|
||
//
|
||
// This produces:
|
||
//
|
||
// C(96369/96368/96367), D(96469/96468/96470), Q(96569/96568/96570),
|
||
// A(96669/96668/96670), B(96769/96768/96770), T(96869/96868/96870),
|
||
// Z(96969/96968/96970), G(97069/97068/97070), K(97169/97168/97170)
|
||
//
|
||
// Each ID is unique across {network × letter} so a misrouted tx cannot
|
||
// be replayed against the wrong chain.
|
||
func TestPrimaryChainShards_PerChainCanonicalChainID(t *testing.T) {
|
||
want := map[string]map[string]int{
|
||
"mainnet": {
|
||
"d": 96469, "q": 96569, "a": 96669, "b": 96769,
|
||
"t": 96869, "z": 96969, "g": 97069, "k": 97169,
|
||
},
|
||
"testnet": {
|
||
"d": 96468, "q": 96568, "a": 96668, "b": 96768,
|
||
"t": 96868, "z": 96968, "g": 97068, "k": 97168,
|
||
},
|
||
"devnet": {
|
||
"d": 96470, "q": 96570, "a": 96670, "b": 96770,
|
||
"t": 96870, "z": 96970, "g": 97070, "k": 97170,
|
||
},
|
||
"localnet": {
|
||
"d": 31447, "q": 31557, "a": 31667, "b": 31777,
|
||
"t": 31887, "z": 31997, "g": 32107, "k": 32217,
|
||
},
|
||
}
|
||
for net, byLetter := range want {
|
||
net := net
|
||
t.Run(net, func(t *testing.T) {
|
||
for letter, wantID := range byLetter {
|
||
path := filepath.Join("..", "configs", net, letter+"chain.json")
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
t.Fatalf("%s/%schain.json: %v", net, letter, err)
|
||
}
|
||
var shard map[string]any
|
||
if err := json.Unmarshal(data, &shard); err != nil {
|
||
t.Fatalf("%s/%schain.json parse: %v", net, letter, err)
|
||
}
|
||
// chainId is stored as a JSON number → unmarshals to float64
|
||
gotF, ok := shard["chainId"].(float64)
|
||
if !ok {
|
||
t.Fatalf("%s/%schain.json: chainId missing or wrong type, got %T", net, letter, shard["chainId"])
|
||
}
|
||
if int(gotF) != wantID {
|
||
t.Fatalf("%s/%schain.json: chainId got %d want %d", net, letter, int(gotF), wantID)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|