mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
655 lines
24 KiB
Go
655 lines
24 KiB
Go
// Copyright (C) 2019-2025, Lux Partners Limited. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package configs provides genesis configuration loading for Lux networks.
|
|
// This package is used by CLI, netrunner, and node to obtain genesis JSON
|
|
// for different network IDs.
|
|
//
|
|
// Dynamic P-Chain Allocations:
|
|
// P-Chain allocations can be specified dynamically at runtime via:
|
|
// - PCHAIN_ALLOCS: JSON string of allocations
|
|
// - PCHAIN_ALLOCS_FILE: Path to allocations JSON file
|
|
// - ~/.lux/genesis/{network}/pchain.json: Standard override location
|
|
//
|
|
// The C-Chain genesis remains embedded and immutable.
|
|
package configs
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/luxfi/genesis/pkg/genesis"
|
|
)
|
|
|
|
// Network ID constants (P-Chain)
|
|
// mainnet, testnet, devnet: proper public networks
|
|
// localnet: local development with LIGHT mnemonic (networkID=1337, EVM chainID=31337)
|
|
// anything else is custom (override via --genesis-file)
|
|
const (
|
|
// Network IDs (P-Chain). These identify the PRIMARY network.
|
|
MainnetID = 1
|
|
TestnetID = 2
|
|
DevnetID = 3
|
|
// LocalID is the canonical local single/multi-node dev network.
|
|
// Pair with LocalChainID = 31337 on the C-Chain (Anvil convention).
|
|
LocalID = 1337
|
|
|
|
// CustomID is the sentinel for any network ID outside the well-known
|
|
// {1, 2, 3, 1337} set — i.e. genuinely user-defined networks. It is
|
|
// deliberately NOT 1337 so callers can distinguish "this is the local
|
|
// dev network" (LocalID) from "this is some other custom network the
|
|
// caller will configure via --genesis-file" (CustomID).
|
|
CustomID uint32 = 0
|
|
|
|
// LocalnetID is a deprecated alias for LocalID; existing callers
|
|
// should migrate to LocalID. Kept here so older code keeps building
|
|
// during the rollout.
|
|
LocalnetID = LocalID
|
|
|
|
// Chain ID constants (C-Chain EVM).
|
|
MainnetChainID = 96369
|
|
TestnetChainID = 96368
|
|
DevnetChainID = 96367
|
|
// LocalChainID is the canonical local C-Chain EVM ID (Anvil convention).
|
|
LocalChainID = 31337
|
|
// CustomChainID is the sentinel C-Chain EVM ID for any chain outside
|
|
// the well-known {96369, 96368, 96367, 31337} set. Mirrors CustomID
|
|
// at the network-ID layer; the two should always be paired (a peer
|
|
// presenting CustomID at the network layer also presents
|
|
// CustomChainID at the EVM layer unless overridden via genesis-file).
|
|
CustomChainID uint32 = 0
|
|
// LocalnetChainID is a deprecated alias for LocalChainID.
|
|
LocalnetChainID = LocalChainID
|
|
)
|
|
|
|
//go:embed mainnet testnet devnet localnet
|
|
var embeddedGenesis embed.FS
|
|
|
|
// GetGenesis returns the genesis JSON bytes for a network ID.
|
|
// It supports dynamic P-Chain allocations via environment variables or files:
|
|
// - PCHAIN_ALLOCS: JSON string of allocations
|
|
// - PCHAIN_ALLOCS_FILE: Path to allocations JSON file
|
|
// - ~/.lux/genesis/{network}/pchain.json: Standard override location
|
|
//
|
|
// C-Chain genesis remains embedded and immutable.
|
|
func GetGenesis(networkID uint32) ([]byte, error) {
|
|
networkName := networkNameFromID(networkID)
|
|
|
|
// Check for dynamic P-Chain allocations
|
|
dynamicPChain := loadDynamicPChainAllocations(networkName)
|
|
|
|
if networkName != "" {
|
|
// Try to load from embedded FS with optional dynamic allocations
|
|
data, err := loadEmbeddedGenesisWithDynamic(networkName, dynamicPChain)
|
|
if err == nil {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Fall back to file system locations
|
|
return loadGenesisFromFS(networkID)
|
|
}
|
|
|
|
// GetGenesisWithAllocations returns genesis with custom P-Chain allocations.
|
|
// This allows booting networks with custom validator allocations.
|
|
func GetGenesisWithAllocations(networkID uint32, allocations []genesis.AllocationJSON) ([]byte, error) {
|
|
networkName := networkNameFromID(networkID)
|
|
|
|
// Convert allocations to PChainConfig
|
|
pchain := &genesis.PChainConfig{
|
|
Allocations: allocations,
|
|
}
|
|
|
|
if networkName != "" {
|
|
data, err := loadEmbeddedGenesisWithDynamic(networkName, pchain)
|
|
if err == nil {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to load genesis for network %d", networkID)
|
|
}
|
|
|
|
// loadDynamicPChainAllocations loads P-Chain allocations from environment or files.
|
|
func loadDynamicPChainAllocations(networkName string) *genesis.PChainConfig {
|
|
// First, check PCHAIN_ALLOCS environment variable (JSON string)
|
|
if allocsJSON := os.Getenv("PCHAIN_ALLOCS"); allocsJSON != "" {
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal([]byte(allocsJSON), &pchain); err == nil {
|
|
return &pchain
|
|
}
|
|
}
|
|
|
|
// Second, check PCHAIN_ALLOCS_FILE environment variable
|
|
if allocsFile := os.Getenv("PCHAIN_ALLOCS_FILE"); allocsFile != "" {
|
|
data, err := os.ReadFile(allocsFile)
|
|
if err == nil {
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal(data, &pchain); err == nil {
|
|
return &pchain
|
|
}
|
|
}
|
|
}
|
|
|
|
// Third, check standard override location ~/.lux/genesis/{network}/pchain.json
|
|
home, _ := os.UserHomeDir()
|
|
overridePath := filepath.Join(home, ".lux/genesis", networkName, "pchain.json")
|
|
data, err := os.ReadFile(overridePath)
|
|
if err == nil {
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal(data, &pchain); err == nil {
|
|
return &pchain
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadEmbeddedGenesisWithDynamic loads genesis with optional dynamic P-Chain allocations.
|
|
func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.PChainConfig) ([]byte, error) {
|
|
// Load network.json from embedded
|
|
networkData, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "network.json"))
|
|
if err != nil {
|
|
// Fall back to single genesis.json file
|
|
return embeddedGenesis.ReadFile(filepath.Join(networkName, "genesis.json"))
|
|
}
|
|
var network genesis.NetworkConfig
|
|
if err := json.Unmarshal(networkData, &network); err != nil {
|
|
return nil, fmt.Errorf("failed to parse network.json: %w", err)
|
|
}
|
|
|
|
// Check if split pchain.json/cchain.json exist - if not, fall back to genesis.json
|
|
_, pchainErr := embeddedGenesis.ReadFile(filepath.Join(networkName, "pchain.json"))
|
|
_, cchainErr := embeddedGenesis.ReadFile(filepath.Join(networkName, "cchain.json"))
|
|
if pchainErr != nil || cchainErr != nil {
|
|
// No split files, fall back to combined genesis.json (devnet case)
|
|
return embeddedGenesis.ReadFile(filepath.Join(networkName, "genesis.json"))
|
|
}
|
|
|
|
// Load P-Chain config - use dynamic if provided, otherwise embedded
|
|
var pchain genesis.PChainConfig
|
|
if dynamicPChain != nil && len(dynamicPChain.Allocations) > 0 {
|
|
pchain = *dynamicPChain
|
|
// If dynamic allocations don't have staking config, load from embedded
|
|
if pchain.InitialStakeDuration == 0 {
|
|
embeddedPChain, _ := loadEmbeddedPChainConfig(networkName)
|
|
if embeddedPChain != nil {
|
|
pchain.InitialStakeDuration = embeddedPChain.InitialStakeDuration
|
|
pchain.InitialStakeDurationOffset = embeddedPChain.InitialStakeDurationOffset
|
|
pchain.InitialStakedFunds = embeddedPChain.InitialStakedFunds
|
|
pchain.InitialStakers = embeddedPChain.InitialStakers
|
|
}
|
|
}
|
|
} else {
|
|
pchainData, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "pchain.json"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read pchain.json: %w", err)
|
|
}
|
|
if err := json.Unmarshal(pchainData, &pchain); err != nil {
|
|
return nil, fmt.Errorf("failed to parse pchain.json: %w", err)
|
|
}
|
|
}
|
|
|
|
// Opt-in chains: each is its own JSON shard in the embedded tree.
|
|
// Shard present → chain baked into primary genesis (a CreateChainTx
|
|
// is emitted). Shard absent → empty string, builder skips the entry.
|
|
// Runtime gate is luxd's --track-chains, not bake-time env knobs.
|
|
//
|
|
// To run a P-only network (a regulated securities L1, etc.), ship a config tree
|
|
// that omits every {x,c,d,q,a,b,t,z,g,k}chain.json. No knob, no
|
|
// hack — chain set is purely data-driven by which shards are
|
|
// present.
|
|
chainShards, err := loadAllChainShards(networkName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// SecurityProfile pin shard. Absent = legacy classical-compat boot.
|
|
securityProfile, err := loadSecurityProfilePin(networkName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// CertPolicy pin shard. Absent = LP-217 not pinned at genesis;
|
|
// node/genesis treats this as "no validation, no policy".
|
|
certPolicy, err := loadCertPolicyPin(networkName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build combined genesis config
|
|
config := genesis.ConfigOutput{
|
|
NetworkID: network.NetworkID,
|
|
Allocations: pchain.Allocations,
|
|
StartTime: network.StartTime,
|
|
InitialStakeDuration: pchain.InitialStakeDuration,
|
|
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
|
InitialStakedFunds: pchain.InitialStakedFunds,
|
|
InitialStakers: pchain.InitialStakers,
|
|
XChainGenesis: chainShards.X,
|
|
CChainGenesis: chainShards.C,
|
|
DChainGenesis: chainShards.D,
|
|
QChainGenesis: chainShards.Q,
|
|
AChainGenesis: chainShards.A,
|
|
BChainGenesis: chainShards.B,
|
|
TChainGenesis: chainShards.T,
|
|
ZChainGenesis: chainShards.Z,
|
|
GChainGenesis: chainShards.G,
|
|
KChainGenesis: chainShards.K,
|
|
SecurityProfile: securityProfile,
|
|
CertPolicy: certPolicy,
|
|
Message: network.Message,
|
|
}
|
|
|
|
return json.Marshal(config)
|
|
}
|
|
|
|
// chainSet is the full primary-network chain shard set. Each
|
|
// field is the raw JSON content of the corresponding shard file, or
|
|
// "" when the shard is absent. Absent fields produce no chain entry
|
|
// in the builder's chains slice — the operator's filesystem is the
|
|
// declarative source of truth for which primary-network chains exist.
|
|
type chainSet struct {
|
|
X, C, D, Q, A, B, T, Z, G, K string
|
|
}
|
|
|
|
// primaryChainShardFiles lists every primary-network chain shard
|
|
// filename in canonical order. Order matters: builder.FromConfig
|
|
// preserves it when assembling the chains slice, so changing this
|
|
// list shifts the P-Chain genesis byte layout. Append-only.
|
|
var primaryChainShardFiles = [...]string{
|
|
"xchain.json",
|
|
"cchain.json",
|
|
"dchain.json",
|
|
"qchain.json",
|
|
"achain.json",
|
|
"bchain.json",
|
|
"tchain.json",
|
|
"zchain.json",
|
|
"gchain.json",
|
|
"kchain.json",
|
|
}
|
|
|
|
// chainSetSlots returns the address-of-field slots for s in the
|
|
// canonical order of primaryChainShardFiles. Used by the embedded and
|
|
// FS loaders to bind shard files to ConfigOutput fields without
|
|
// repeating the per-chain switch.
|
|
func (s *chainSet) slots() []*string {
|
|
return []*string{&s.X, &s.C, &s.D, &s.Q, &s.A, &s.B, &s.T, &s.Z, &s.G, &s.K}
|
|
}
|
|
|
|
// loadAllChainShards reads every primary-network chain shard from the
|
|
// embedded tree for networkName. Missing shards become empty fields
|
|
// (chain skipped at build time).
|
|
func loadAllChainShards(networkName string) (chainSet, error) {
|
|
var s chainSet
|
|
slots := s.slots()
|
|
for i, file := range primaryChainShardFiles {
|
|
v, err := loadOptionalChainShard(networkName, file)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
*slots[i] = v
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// readAllChainShards is the FS-backed counterpart used by the on-disk
|
|
// fallback loader (~/.lux/genesis/<network>/, etc.).
|
|
func readAllChainShards(dir string) (chainSet, error) {
|
|
var s chainSet
|
|
slots := s.slots()
|
|
for i, file := range primaryChainShardFiles {
|
|
v, err := readDirShard(dir, file)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
*slots[i] = v
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// loadOptionalChainShard reads a per-network chain genesis shard from the
|
|
// embedded tree. Returns the file contents on success, "" when the file is
|
|
// absent (chain not baked into this network's primary genesis), or an error
|
|
// for any other read failure.
|
|
//
|
|
// One pattern, one implementation; adding a new primary-network chain
|
|
// is one entry in primaryChainShardFiles plus a slot on chainSet,
|
|
// not a new env knob and not a new branch in the builder.
|
|
func loadOptionalChainShard(networkName, filename string) (string, error) {
|
|
data, err := embeddedGenesis.ReadFile(filepath.Join(networkName, filename))
|
|
if err != nil {
|
|
// Absent shard means the chain isn't part of this network's primary
|
|
// genesis. Distinct from a real read error — empty FS embed returns
|
|
// fs.ErrNotExist via PathError. Treat anything fs-not-exist as "skip
|
|
// this chain"; surface everything else.
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return "", nil
|
|
}
|
|
return "", fmt.Errorf("read embedded %s/%s: %w", networkName, filename, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// readDirShard is the FS-backed counterpart to loadOptionalChainShard. Used by
|
|
// the on-disk fallback loader (~/.lux/genesis/<network>/, etc.).
|
|
func readDirShard(dir, filename string) (string, error) {
|
|
data, err := os.ReadFile(filepath.Join(dir, filename))
|
|
if err != nil {
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return "", nil
|
|
}
|
|
return "", fmt.Errorf("read %s/%s: %w", dir, filename, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// loadSecurityProfilePin reads the per-network securityProfile.json shard if
|
|
// present in the embedded tree. Returns nil with no error when the file is
|
|
// absent (legacy classical-compat boot). The shard layout matches the
|
|
// pkg/genesis.SecurityProfile struct (profileID + profileHashHex), keeping
|
|
// the embedded tree and the on-disk operator file format identical.
|
|
func loadSecurityProfilePin(networkName string) (*genesis.SecurityProfile, error) {
|
|
data, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "securityProfile.json"))
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
var sp genesis.SecurityProfile
|
|
if err := json.Unmarshal(data, &sp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s/securityProfile.json: %w", networkName, err)
|
|
}
|
|
return &sp, nil
|
|
}
|
|
|
|
// loadCertPolicyPin reads the per-network certPolicy.json shard if present
|
|
// in the embedded tree. Returns nil with no error when the file is absent
|
|
// (the LP-217 pin is optional for backwards compatibility with genesis
|
|
// files written before LP-217 activation). The shard layout matches the
|
|
// pkg/genesis.CertPolicy struct (mode/variant/timeoutMs/fallback), kept
|
|
// identical between the embedded tree and the on-disk operator file format
|
|
// so a deploy can drop a certPolicy.json next to securityProfile.json with
|
|
// no other change.
|
|
func loadCertPolicyPin(networkName string) (*genesis.CertPolicy, error) {
|
|
data, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "certPolicy.json"))
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
var cp genesis.CertPolicy
|
|
if err := json.Unmarshal(data, &cp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s/certPolicy.json: %w", networkName, err)
|
|
}
|
|
return &cp, nil
|
|
}
|
|
|
|
// loadEmbeddedPChainConfig loads only the P-Chain config from embedded.
|
|
func loadEmbeddedPChainConfig(networkName string) (*genesis.PChainConfig, error) {
|
|
pchainData, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "pchain.json"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal(pchainData, &pchain); err != nil {
|
|
return nil, err
|
|
}
|
|
return &pchain, nil
|
|
}
|
|
|
|
// GetConfig returns the parsed genesis Config for a network ID.
|
|
func GetConfig(networkID uint32) (*genesis.Config, error) {
|
|
data, err := GetGenesis(networkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The embedded genesis files use string-encoded addresses (ConfigOutput format).
|
|
// Parse as ConfigOutput first, then convert to Config.
|
|
var output genesis.ConfigOutput
|
|
if err := json.Unmarshal(data, &output); err != nil {
|
|
return nil, fmt.Errorf("failed to parse genesis config: %w", err)
|
|
}
|
|
|
|
// Convert ConfigOutput → Config by parsing string addresses to binary.
|
|
return genesis.ParseConfigOutput(&output, networkID)
|
|
}
|
|
|
|
// IsCustom reports whether the networkID is a user-defined network
|
|
// outside the well-known {Mainnet, Testnet, Devnet, Local + their
|
|
// C-Chain aliases} set. Mirrors luxfi/constants.IsCustom; both the
|
|
// genesis layer and the constants layer agree on the classification
|
|
// so a network can be classified consistently end-to-end.
|
|
func IsCustom(networkID uint32) bool {
|
|
switch networkID {
|
|
case MainnetID, MainnetChainID,
|
|
TestnetID, TestnetChainID,
|
|
DevnetID, DevnetChainID,
|
|
LocalID, LocalChainID:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// networkNameFromID returns the network directory name for a network ID.
|
|
// Accepts both network IDs (1, 2, 3, 1337) and chain IDs (96369, 96368, 96367, 31337)
|
|
// as aliases. User-defined custom networks return "" — callers must
|
|
// supply a genesis file (`--genesis-file`) for them since there are no
|
|
// embedded canonical configs to load from disk.
|
|
func networkNameFromID(networkID uint32) string {
|
|
switch networkID {
|
|
case MainnetID, MainnetChainID:
|
|
return "mainnet"
|
|
case TestnetID, TestnetChainID:
|
|
return "testnet"
|
|
case DevnetID, DevnetChainID:
|
|
return "devnet"
|
|
case LocalID, LocalChainID:
|
|
return "localnet"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// GetCanonicalGenesisBytes returns the canonical genesis bytes for a network.
|
|
// This function builds the genesis from split files (network.json, pchain.json, cchain.json)
|
|
// to ensure cChainGenesis is properly serialized as a JSON string.
|
|
//
|
|
// CRITICAL: The embedded genesis.json stores cChainGenesis as an object for easy editing,
|
|
// but luxd requires it to be a JSON-encoded string. This function handles the conversion.
|
|
func GetCanonicalGenesisBytes(networkID uint32) ([]byte, error) {
|
|
networkName := networkNameFromID(networkID)
|
|
if networkName == "" {
|
|
return nil, fmt.Errorf("unknown network ID: %d", networkID)
|
|
}
|
|
|
|
// First, try to build from split files (network.json + pchain.json + cchain.json)
|
|
// This properly stringifies cChainGenesis
|
|
data, err := buildCanonicalGenesisFromSplitFiles(networkName)
|
|
if err == nil {
|
|
return data, nil
|
|
}
|
|
|
|
// Fall back to file system locations with split files
|
|
home, _ := os.UserHomeDir()
|
|
candidates := []string{
|
|
filepath.Join(home, "work/lux/genesis/configs", networkName),
|
|
filepath.Join(home, ".lux/genesis", networkName),
|
|
filepath.Join("/etc/lux/genesis", networkName),
|
|
}
|
|
|
|
for _, dir := range candidates {
|
|
if data, err := buildGenesisFromDir(dir); err == nil {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("canonical genesis not found for network %s (need split files: network.json, pchain.json, cchain.json)", networkName)
|
|
}
|
|
|
|
// buildCanonicalGenesisFromSplitFiles builds genesis from embedded split files.
|
|
// This ensures cChainGenesis is properly serialized as a JSON string.
|
|
func buildCanonicalGenesisFromSplitFiles(networkName string) ([]byte, error) {
|
|
// Load network.json
|
|
networkData, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "network.json"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read network.json: %w", err)
|
|
}
|
|
var network genesis.NetworkConfig
|
|
if err := json.Unmarshal(networkData, &network); err != nil {
|
|
return nil, fmt.Errorf("failed to parse network.json: %w", err)
|
|
}
|
|
|
|
// Load pchain.json
|
|
pchainData, err := embeddedGenesis.ReadFile(filepath.Join(networkName, "pchain.json"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read pchain.json: %w", err)
|
|
}
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal(pchainData, &pchain); err != nil {
|
|
return nil, fmt.Errorf("failed to parse pchain.json: %w", err)
|
|
}
|
|
|
|
// Opt-in chains via embedded shards. Same data-driven contract as
|
|
// the primary loader: shard present → chain emitted; absent → skipped.
|
|
chainShards, err := loadAllChainShards(networkName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Build combined genesis config
|
|
config := genesis.ConfigOutput{
|
|
NetworkID: network.NetworkID,
|
|
Allocations: pchain.Allocations,
|
|
StartTime: network.StartTime,
|
|
InitialStakeDuration: pchain.InitialStakeDuration,
|
|
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
|
InitialStakedFunds: pchain.InitialStakedFunds,
|
|
InitialStakers: pchain.InitialStakers,
|
|
XChainGenesis: chainShards.X,
|
|
CChainGenesis: chainShards.C,
|
|
DChainGenesis: chainShards.D,
|
|
QChainGenesis: chainShards.Q,
|
|
AChainGenesis: chainShards.A,
|
|
BChainGenesis: chainShards.B,
|
|
TChainGenesis: chainShards.T,
|
|
ZChainGenesis: chainShards.Z,
|
|
GChainGenesis: chainShards.G,
|
|
KChainGenesis: chainShards.K,
|
|
Message: network.Message,
|
|
}
|
|
|
|
return json.Marshal(config)
|
|
}
|
|
|
|
// loadGenesisFromFS loads genesis from file system locations.
|
|
func loadGenesisFromFS(networkID uint32) ([]byte, error) {
|
|
networkName := networkNameFromID(networkID)
|
|
if networkName == "" {
|
|
networkName = "localnet"
|
|
}
|
|
|
|
home, _ := os.UserHomeDir()
|
|
candidates := []string{
|
|
filepath.Join(home, "work/lux/genesis", networkName),
|
|
filepath.Join(home, ".lux/genesis", networkName),
|
|
filepath.Join("/etc/lux/genesis", networkName),
|
|
}
|
|
|
|
for _, dir := range candidates {
|
|
// Try component files first
|
|
networkPath := filepath.Join(dir, "network.json")
|
|
if _, err := os.Stat(networkPath); err == nil {
|
|
return buildGenesisFromDir(dir)
|
|
}
|
|
|
|
// Try single genesis.json
|
|
genesisPath := filepath.Join(dir, "genesis.json")
|
|
if data, err := os.ReadFile(genesisPath); err == nil {
|
|
return data, nil
|
|
}
|
|
|
|
// Try primary.json (our generated output)
|
|
primaryPath := filepath.Join(dir, "primary.json")
|
|
if data, err := os.ReadFile(primaryPath); err == nil {
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("genesis not found for network %d", networkID)
|
|
}
|
|
|
|
// buildGenesisFromDir builds genesis from component files in a directory.
|
|
func buildGenesisFromDir(dir string) ([]byte, error) {
|
|
// Load network.json
|
|
networkData, err := os.ReadFile(filepath.Join(dir, "network.json"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read network.json: %w", err)
|
|
}
|
|
var network genesis.NetworkConfig
|
|
if err := json.Unmarshal(networkData, &network); err != nil {
|
|
return nil, fmt.Errorf("failed to parse network.json: %w", err)
|
|
}
|
|
|
|
// Load pchain.json
|
|
pchainData, err := os.ReadFile(filepath.Join(dir, "pchain.json"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read pchain.json: %w", err)
|
|
}
|
|
var pchain genesis.PChainConfig
|
|
if err := json.Unmarshal(pchainData, &pchain); err != nil {
|
|
return nil, fmt.Errorf("failed to parse pchain.json: %w", err)
|
|
}
|
|
|
|
// Opt-in chains: read every shard from dir. Same data-driven
|
|
// contract as the embedded loader.
|
|
chainShards, err := readAllChainShards(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// SecurityProfile pin — per-dir shard, fall back to embedded by basename.
|
|
var securityProfile *genesis.SecurityProfile
|
|
if data, ferr := os.ReadFile(filepath.Join(dir, "securityProfile.json")); ferr == nil {
|
|
var sp genesis.SecurityProfile
|
|
if err := json.Unmarshal(data, &sp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s/securityProfile.json: %w", dir, err)
|
|
}
|
|
securityProfile = &sp
|
|
} else {
|
|
var perr error
|
|
securityProfile, perr = loadSecurityProfilePin(filepath.Base(dir))
|
|
if perr != nil {
|
|
return nil, perr
|
|
}
|
|
}
|
|
|
|
// Build combined genesis config
|
|
config := genesis.ConfigOutput{
|
|
NetworkID: network.NetworkID,
|
|
Allocations: pchain.Allocations,
|
|
StartTime: network.StartTime,
|
|
InitialStakeDuration: pchain.InitialStakeDuration,
|
|
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
|
InitialStakedFunds: pchain.InitialStakedFunds,
|
|
InitialStakers: pchain.InitialStakers,
|
|
XChainGenesis: chainShards.X,
|
|
CChainGenesis: chainShards.C,
|
|
DChainGenesis: chainShards.D,
|
|
QChainGenesis: chainShards.Q,
|
|
AChainGenesis: chainShards.A,
|
|
BChainGenesis: chainShards.B,
|
|
TChainGenesis: chainShards.T,
|
|
ZChainGenesis: chainShards.Z,
|
|
GChainGenesis: chainShards.G,
|
|
KChainGenesis: chainShards.K,
|
|
SecurityProfile: securityProfile,
|
|
Message: network.Message,
|
|
}
|
|
|
|
return json.Marshal(config)
|
|
}
|