mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
genesis: shard-driven primary chain set (X opt-in like C/Q/Z)
Make every primary-network chain (X/C/D/Q/A/B/T/Z/G/K) opt-in via its
own <x>chain.json shard. Absent shard -> empty ConfigOutput field ->
builder skips the CreateChainTx -> daemon doesn't start that chain.
Lux mainnet/testnet/devnet ship full chain set as shards; localnet and
local ship the same canonical X asset shard. P-only sovereign L1 boot
shape (<tenant> etc.) is achieved by shipping pchain.json+network.json
only — no env knob, no special builder branch.
X-Chain genesis was previously hardcoded inside FromConfig as a literal
{Symbol:"LUX",Name:"Lux",Denomination:9}. Now sourced from xchain.json
shard, which keeps the same canonical asset for Lux but lets a sibling
network (lqd / <tenant>) ship its own descriptor or ship none and
boot a P-only primary network.
builder.FromConfig:
- HRP hoisted above the X-Chain conditional (used on both branches).
- X-Chain construction gated on config.XChainGenesis != "".
- Returns ids.Empty for xAssetID when X is absent.
- Single chainEntries table replaces the per-chain switch and the
DefaultChainGenesis stub-data fallback.
- "platform" alias renamed to "protocol" (PChainAliases / VMAliases /
Aliases output) — value is "P-Chain", served by ProtocolVM.
configs.loadAllChainShards / readAllChainShards:
- Single dispatch over primaryChainShardFiles in canonical order
(X,C,D,Q,A,B,T,Z,G,K). Adding a new primary chain is one entry +
one slot, not a new env knob and not a new builder branch.
pkg/genesis/types.go:
- ConfigOutput.XChainGenesis (omitempty) added; threaded through
Config <-> ConfigOutput conversions.
Tests:
- builder/builder_test.go: TestFromConfig_ChainSetIsShardDriven pins
the contract — full mainnet emits 10 chains + non-empty xAssetID;
same config with all chain shards stripped emits 0 chains + empty
xAssetID, validators + UTXOs intact.
- configs/chain_shards_test.go: TestGetGenesis_XChainShardPresent...
asserts every Lux network embeds the canonical {LUX,Lux,9} asset;
AbsentShardEmptyOptIn extended to cover xChainGenesis.
This commit is contained in:
@@ -111,7 +111,7 @@ Hex: 0x193e5939a08ce9dbd480000000
|
||||
configs/
|
||||
├── mainnet/
|
||||
│ ├── cchain.json # C-Chain genesis (chain ID 96369)
|
||||
│ ├── pchain.json # P-Chain genesis (Platform)
|
||||
│ ├── pchain.json # P-Chain genesis (ProtocolVM)
|
||||
│ ├── network.json # Network ID, message, startTime
|
||||
│ └── bootstrappers.json # Bootstrap nodes
|
||||
├── testnet/
|
||||
@@ -238,7 +238,7 @@ Format: {chain}-{hrp}1{data}{checksum}
|
||||
Example: P-lux1ck0t9h5u7jvvzhx29n99guqjsfkpzt67wgx7wg
|
||||
|
||||
Chain Prefixes:
|
||||
- P-lux1... (P-Chain/Platform)
|
||||
- P-lux1... (P-Chain — ProtocolVM)
|
||||
- X-lux1... (X-Chain/Exchange)
|
||||
- C-0x... (C-Chain/EVM hex)
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@ This repository contains the canonical genesis configurations for all Lux networ
|
||||
genesis/
|
||||
├── mainnet/ # LUX Mainnet (Chain ID: 96369)
|
||||
│ ├── cchain.json # C-Chain genesis (EVM compatible)
|
||||
│ ├── pchain.json # P-Chain genesis (Platform chain)
|
||||
│ ├── pchain.json # P-Chain genesis (ProtocolVM)
|
||||
│ ├── xchain.json # X-Chain genesis (Exchange chain)
|
||||
│ ├── network.json # Network configuration
|
||||
│ └── genesis.json # Full network genesis
|
||||
|
||||
+2
-2
@@ -123,7 +123,7 @@ var LocalValidatorFeeConfig fee.Config
|
||||
|
||||
```go
|
||||
var VMAliases = map[ids.ID][]string{
|
||||
constants.PlatformVMID: {"platform"},
|
||||
constants.PlatformVMID: {"protocol"}, // upstream constant still PlatformVMID; alias is "protocol"
|
||||
constants.XVMID: {"xvm"},
|
||||
constants.EVMID: {"evm"},
|
||||
secp256k1fx.ID: {"secp256k1fx"},
|
||||
@@ -155,7 +155,7 @@ func main() {
|
||||
|
||||
// Build genesis bytes
|
||||
config := builder.GetConfig(constants.MainnetID)
|
||||
genesisBytes, luxAssetID, err := builder.FromConfig(config)
|
||||
genesisBytes, xAssetID, err := builder.FromConfig(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+150
-150
@@ -38,8 +38,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// PChainAliases are the default aliases for the P-Chain
|
||||
PChainAliases = []string{"P", "platform"}
|
||||
// PChainAliases are the default aliases for the P-Chain — the
|
||||
// protocol chain that holds the validator set, chain registry,
|
||||
// and ordering. Served by ProtocolVM (formerly PlatformVM).
|
||||
PChainAliases = []string{"P", "protocol"}
|
||||
// XChainAliases are the default aliases for the X-Chain
|
||||
XChainAliases = []string{"X", "xvm"}
|
||||
// CChainAliases are the default aliases for the C-Chain
|
||||
@@ -61,12 +63,9 @@ var (
|
||||
// KChainAliases are the default aliases for the K-Chain (KMS)
|
||||
KChainAliases = []string{"K", "kms", "kmsvm"}
|
||||
|
||||
// DefaultChainGenesis provides empty but valid genesis for specialty chains
|
||||
DefaultChainGenesis = `{"version":1,"message":"Lux Chain Genesis"}`
|
||||
|
||||
// VMAliases are the default aliases for VMs
|
||||
VMAliases = map[ids.ID][]string{
|
||||
constants.PlatformVMID: {"platform"},
|
||||
constants.PlatformVMID: {"protocol"},
|
||||
constants.XVMID: {"xvm"},
|
||||
constants.EVMID: {"evm"},
|
||||
constants.DexVMID: {"dexvm", "dex"},
|
||||
@@ -316,72 +315,107 @@ func GetConfig(networkID uint32) *genesiscfg.Config {
|
||||
return genesiscfg.GetConfig(networkID)
|
||||
}
|
||||
|
||||
// FromConfig builds genesis bytes from a config
|
||||
// FromConfig builds genesis bytes from a config.
|
||||
//
|
||||
// X-Chain is opt-in via config.XChainGenesis. The shard is a small JSON
|
||||
// asset descriptor:
|
||||
//
|
||||
// {"symbol":"LUX","name":"Lux","denomination":9}
|
||||
//
|
||||
// When present, the builder constructs an XVM genesis whose primary
|
||||
// asset is that descriptor (with initial holders sourced from
|
||||
// config.Allocations) and emits a CreateChainTx for the X-Chain.
|
||||
// xAssetID returned to the caller is the X-Chain primary asset ID
|
||||
// (sha256 over xvmGenesisBytes) — the same ID P-Chain stake UTXOs are
|
||||
// denominated in.
|
||||
//
|
||||
// When absent, no XVM genesis is built, no X-Chain CreateChainTx is
|
||||
// emitted, and xAssetID is returned as ids.Empty. The primary network
|
||||
// then has no native UTXO asset: validator stakes denominated against
|
||||
// ids.Empty have no on-chain asset backing, which is the
|
||||
// permissioned-set semantics used by L1s whose value capture lives on
|
||||
// an L2 chain ( etc.) with its own asset model. Minting a
|
||||
// real primary-network asset requires X — there is no other genesis
|
||||
// site for it.
|
||||
func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
// Build XVM (X-Chain) genesis
|
||||
lux := xgenesis.GenesisAssetDefinition{
|
||||
Name: "Lux",
|
||||
Symbol: "LUX",
|
||||
Denomination: 9,
|
||||
InitialState: xgenesis.AssetInitialState{},
|
||||
}
|
||||
memoBytes := []byte{}
|
||||
|
||||
// Sort allocations for deterministic output
|
||||
type allocation struct {
|
||||
ETHAddr ids.ShortID
|
||||
LUXAddr ids.ShortID
|
||||
InitialAmount uint64
|
||||
}
|
||||
xAllocations := []allocation{}
|
||||
for _, a := range config.Allocations {
|
||||
if a.InitialAmount > 0 {
|
||||
xAllocations = append(xAllocations, allocation{
|
||||
ETHAddr: a.ETHAddr,
|
||||
LUXAddr: a.LUXAddr,
|
||||
InitialAmount: a.InitialAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get HRP for this network to format bech32 addresses
|
||||
// HRP is needed for X-Chain holder addresses, P-Chain protocol
|
||||
// allocations, staker allocations, and reward addresses — define
|
||||
// once, before the X-Chain conditional, so it's available on both
|
||||
// paths.
|
||||
hrp := constants.GetHRP(config.NetworkID)
|
||||
|
||||
for _, a := range xAllocations {
|
||||
// Format address as bech32 for the X-Chain
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address: %w", err)
|
||||
}
|
||||
lux.InitialState.FixedCap = append(lux.InitialState.FixedCap, xgenesis.GenesisHolder{
|
||||
Amount: a.InitialAmount,
|
||||
Address: bech32Addr,
|
||||
})
|
||||
// Add ETH address to memo for reference
|
||||
ethAddrStr := a.ETHAddr.Hex()
|
||||
if len(ethAddrStr) > 2 { // "0x" prefix
|
||||
memoBytes = append(memoBytes, []byte(ethAddrStr[2:])...)
|
||||
}
|
||||
}
|
||||
lux.Memo = memoBytes
|
||||
|
||||
xvmGenesis, err := xgenesis.NewGenesis(
|
||||
config.NetworkID,
|
||||
map[string]xgenesis.GenesisAssetDefinition{
|
||||
"LUX": lux,
|
||||
},
|
||||
var (
|
||||
xvmGenesisBytes []byte
|
||||
xAssetID = ids.Empty
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
xvmGenesisBytes, err := xvmGenesis.Bytes()
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't serialize xvm genesis: %w", err)
|
||||
}
|
||||
if config.XChainGenesis != "" {
|
||||
var asset struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Name string `json:"name"`
|
||||
Denomination byte `json:"denomination"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(config.XChainGenesis), &asset); err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("invalid xChainGenesis shard: %w", err)
|
||||
}
|
||||
primary := xgenesis.GenesisAssetDefinition{
|
||||
Name: asset.Name,
|
||||
Symbol: asset.Symbol,
|
||||
Denomination: asset.Denomination,
|
||||
InitialState: xgenesis.AssetInitialState{},
|
||||
}
|
||||
memoBytes := []byte{}
|
||||
|
||||
luxAssetID, err := XAssetID(xvmGenesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't generate LUX asset ID: %w", err)
|
||||
// Sort allocations for deterministic output
|
||||
type allocation struct {
|
||||
ETHAddr ids.ShortID
|
||||
LUXAddr ids.ShortID
|
||||
InitialAmount uint64
|
||||
}
|
||||
xAllocations := []allocation{}
|
||||
for _, a := range config.Allocations {
|
||||
if a.InitialAmount > 0 {
|
||||
xAllocations = append(xAllocations, allocation{
|
||||
ETHAddr: a.ETHAddr,
|
||||
LUXAddr: a.LUXAddr,
|
||||
InitialAmount: a.InitialAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range xAllocations {
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address: %w", err)
|
||||
}
|
||||
primary.InitialState.FixedCap = append(primary.InitialState.FixedCap, xgenesis.GenesisHolder{
|
||||
Amount: a.InitialAmount,
|
||||
Address: bech32Addr,
|
||||
})
|
||||
ethAddrStr := a.ETHAddr.Hex()
|
||||
if len(ethAddrStr) > 2 { // "0x" prefix
|
||||
memoBytes = append(memoBytes, []byte(ethAddrStr[2:])...)
|
||||
}
|
||||
}
|
||||
primary.Memo = memoBytes
|
||||
|
||||
xvmGenesis, err := xgenesis.NewGenesis(
|
||||
config.NetworkID,
|
||||
map[string]xgenesis.GenesisAssetDefinition{
|
||||
asset.Symbol: primary,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
xvmGenesisBytes, err = xvmGenesis.Bytes()
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't serialize xvm genesis: %w", err)
|
||||
}
|
||||
|
||||
xAssetID, err = XAssetID(xvmGenesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't generate LUX asset ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
genesisTime := time.Unix(int64(config.StartTime), 0)
|
||||
@@ -395,13 +429,13 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build platform allocations
|
||||
// Build protocol allocations
|
||||
initiallyStaked := set.Set[ids.ShortID]{}
|
||||
for _, addr := range config.InitialStakedFunds {
|
||||
initiallyStaked.Add(addr)
|
||||
}
|
||||
|
||||
platformAllocations := []genesis.Allocation{}
|
||||
protocolAllocations := []genesis.Allocation{}
|
||||
skippedAllocations := []genesiscfg.Allocation{}
|
||||
for _, a := range config.Allocations {
|
||||
if initiallyStaked.Contains(a.LUXAddr) {
|
||||
@@ -415,7 +449,7 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for P-chain: %w", err)
|
||||
}
|
||||
platformAllocations = append(platformAllocations, genesis.Allocation{
|
||||
protocolAllocations = append(protocolAllocations, genesis.Allocation{
|
||||
Locktime: unlock.Locktime,
|
||||
Amount: unlock.Amount,
|
||||
Address: bech32Addr,
|
||||
@@ -508,87 +542,53 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Helper to get genesis data or default
|
||||
getGenesis := func(data string) []byte {
|
||||
if data == "" {
|
||||
return []byte(DefaultChainGenesis)
|
||||
}
|
||||
return []byte(data)
|
||||
// Primary-network chain registry. Each entry pairs a genesis-data
|
||||
// source with the VM that consumes it. Entries with empty
|
||||
// GenesisData are SKIPPED — luxd will not start a chain manager
|
||||
// for them. This is the data-driven contract: which primary-network
|
||||
// chains exist is a function of which shards the operator shipped,
|
||||
// not a runtime knob.
|
||||
//
|
||||
// Order is fixed (X→C→D→Q→A→B→T→Z→G→K) and preserved across
|
||||
// presence/absence so byte-identical genesis holds when the same
|
||||
// shard set is supplied. Append-only — reordering shifts the
|
||||
// P-Chain genesis byte layout.
|
||||
chainEntries := []struct {
|
||||
GenesisData []byte
|
||||
VMID ids.ID
|
||||
Name string
|
||||
FxIDs []ids.ID // X-Chain only; nil for everything else
|
||||
}{
|
||||
{GenesisData: xvmGenesisBytes, VMID: constants.XVMID, Name: "X-Chain",
|
||||
FxIDs: []ids.ID{secp256k1fx.ID, nftfx.ID, propertyfx.ID}},
|
||||
{GenesisData: []byte(config.CChainGenesis), VMID: constants.EVMID, Name: "C-Chain"},
|
||||
{GenesisData: []byte(config.DChainGenesis), VMID: constants.DexVMID, Name: "D-Chain"},
|
||||
{GenesisData: []byte(config.QChainGenesis), VMID: constants.QuantumVMID, Name: "Q-Chain"},
|
||||
{GenesisData: []byte(config.AChainGenesis), VMID: constants.AIVMID, Name: "A-Chain"},
|
||||
{GenesisData: []byte(config.BChainGenesis), VMID: constants.BridgeVMID, Name: "B-Chain"},
|
||||
{GenesisData: []byte(config.TChainGenesis), VMID: constants.ThresholdVMID, Name: "T-Chain"},
|
||||
{GenesisData: []byte(config.ZChainGenesis), VMID: constants.ZKVMID, Name: "Z-Chain"},
|
||||
{GenesisData: []byte(config.GChainGenesis), VMID: constants.GraphVMID, Name: "G-Chain"},
|
||||
{GenesisData: []byte(config.KChainGenesis), VMID: constants.KeyVMID, Name: "K-Chain"},
|
||||
}
|
||||
|
||||
// Specify all 11 chains
|
||||
chains := []genesis.Chain{
|
||||
{
|
||||
GenesisData: xvmGenesisBytes,
|
||||
chains := []genesis.Chain{}
|
||||
for _, e := range chainEntries {
|
||||
if len(e.GenesisData) == 0 {
|
||||
continue
|
||||
}
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: e.GenesisData,
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.XVMID,
|
||||
FxIDs: []ids.ID{
|
||||
secp256k1fx.ID,
|
||||
nftfx.ID,
|
||||
propertyfx.ID,
|
||||
},
|
||||
Name: "X-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: []byte(config.CChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.EVMID,
|
||||
Name: "C-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.DChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.DexVMID,
|
||||
Name: "D-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.QChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.QuantumVMID,
|
||||
Name: "Q-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.AChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.AIVMID,
|
||||
Name: "A-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.BChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.BridgeVMID,
|
||||
Name: "B-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.TChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.ThresholdVMID,
|
||||
Name: "T-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.ZChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.ZKVMID,
|
||||
Name: "Z-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.GChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.GraphVMID,
|
||||
Name: "G-Chain",
|
||||
},
|
||||
{
|
||||
GenesisData: getGenesis(config.KChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.KeyVMID,
|
||||
Name: "K-Chain",
|
||||
},
|
||||
VMID: e.VMID,
|
||||
FxIDs: e.FxIDs,
|
||||
Name: e.Name,
|
||||
})
|
||||
}
|
||||
|
||||
pChainGenesis, err := genesis.New(
|
||||
luxAssetID,
|
||||
xAssetID,
|
||||
config.NetworkID,
|
||||
platformAllocations,
|
||||
protocolAllocations,
|
||||
validators,
|
||||
chains,
|
||||
config.StartTime,
|
||||
@@ -596,13 +596,13 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
config.Message,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("problem while building platform chain's genesis state: %w", err)
|
||||
return nil, ids.Empty, fmt.Errorf("problem while building protocol chain's genesis state: %w", err)
|
||||
}
|
||||
pChainGenesisBytes, err := pChainGenesis.Bytes()
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("problem while serializing platform chain's genesis state: %w", err)
|
||||
return nil, ids.Empty, fmt.Errorf("problem while serializing protocol chain's genesis state: %w", err)
|
||||
}
|
||||
return pChainGenesisBytes, luxAssetID, nil
|
||||
return pChainGenesisBytes, xAssetID, nil
|
||||
}
|
||||
|
||||
// FromFile loads genesis config from file and builds genesis bytes
|
||||
@@ -685,9 +685,9 @@ func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, err
|
||||
apiAliases := map[string][]string{
|
||||
path.Join(constants.ChainAliasPrefix, constants.PlatformChainID.String()): {
|
||||
"P",
|
||||
"platform",
|
||||
"protocol",
|
||||
path.Join(constants.ChainAliasPrefix, "P"),
|
||||
path.Join(constants.ChainAliasPrefix, "platform"),
|
||||
path.Join(constants.ChainAliasPrefix, "protocol"),
|
||||
},
|
||||
}
|
||||
chainAliases := map[ids.ID][]string{
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/genesis/configs"
|
||||
"github.com/luxfi/ids"
|
||||
pgenesis "github.com/luxfi/protocol/p/genesis"
|
||||
)
|
||||
|
||||
func TestGetStakingConfig(t *testing.T) {
|
||||
@@ -171,6 +174,68 @@ func TestChainAliases(t *testing.T) {
|
||||
require.Contains(t, CChainAliases, "C")
|
||||
}
|
||||
|
||||
// TestFromConfig_ChainSetIsShardDriven exercises the data-driven
|
||||
// chain-presence contract end-to-end:
|
||||
//
|
||||
// - Lux mainnet ships every primary-network chain shard
|
||||
// (X/C/D/Q/A/B/T/Z/G/K) → FromConfig must emit all 10
|
||||
// CreateChainTx entries and a non-empty xAssetID (X is the home of
|
||||
// the LUX asset).
|
||||
//
|
||||
// - Strip every chain shard from the same Config — same allocations,
|
||||
// same validators, same network ID — and FromConfig must succeed,
|
||||
// return ids.Empty for xAssetID, and produce a P-Chain genesis
|
||||
// with zero CreateChainTx entries. This is the L1-from-P-only
|
||||
// boot path: a sovereign L1 (lqd-style) ships only pchain.json +
|
||||
// network.json and gets a primary-network of just P-Chain, with
|
||||
// funded addresses and weighted validators ready to issue
|
||||
// CreateChainTx for whatever L2 chains it wants post-genesis.
|
||||
//
|
||||
// "Shard set = chain set" is the one-way contract; this test pins it.
|
||||
func TestFromConfig_ChainSetIsShardDriven(t *testing.T) {
|
||||
cfg, err := configs.GetConfig(constants.MainnetID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg)
|
||||
require.NotEmpty(t, cfg.XChainGenesis, "mainnet ships xchain.json")
|
||||
|
||||
// Full Lux mainnet: 10 chains + real LUX asset ID.
|
||||
bytes, xAssetID, err := FromConfig(cfg)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, ids.Empty, xAssetID, "mainnet has X-Chain → real asset ID")
|
||||
|
||||
full, err := pgenesis.Parse(bytes)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, full.Chains, 10, "mainnet emits all 10 primary chains")
|
||||
|
||||
// Strip every chain shard. Allocations + validators stay intact —
|
||||
// the P-Chain itself is the foundation, not a CreateChainTx entry.
|
||||
cfg.XChainGenesis = ""
|
||||
cfg.CChainGenesis = ""
|
||||
cfg.DChainGenesis = ""
|
||||
cfg.QChainGenesis = ""
|
||||
cfg.AChainGenesis = ""
|
||||
cfg.BChainGenesis = ""
|
||||
cfg.TChainGenesis = ""
|
||||
cfg.ZChainGenesis = ""
|
||||
cfg.GChainGenesis = ""
|
||||
cfg.KChainGenesis = ""
|
||||
|
||||
bytes, xAssetID, err = FromConfig(cfg)
|
||||
require.NoError(t, err, "P-only build must succeed")
|
||||
require.Equal(t, ids.Empty, xAssetID, "no X-Chain → no LUX asset → ids.Empty")
|
||||
|
||||
pOnly, err := pgenesis.Parse(bytes)
|
||||
require.NoError(t, err, "P-only genesis bytes must parse")
|
||||
require.Empty(t, pOnly.Chains, "P-only network has zero CreateChainTx entries")
|
||||
|
||||
// "If wallet/address funded for keys" — the P-Chain still carries
|
||||
// the Lux mainnet allocations (encoded as P-Chain UTXOs) and
|
||||
// validator stakes, so a sovereign L1 booted from this shape has
|
||||
// funded addresses and weighted validators ready to operate.
|
||||
require.NotEmpty(t, pOnly.Validators, "P-only must have validators (funded staker keys)")
|
||||
require.NotEmpty(t, pOnly.UTXOs, "P-only must have funded addresses (UTXOs)")
|
||||
}
|
||||
|
||||
func TestDefaultFeeConfigs(t *testing.T) {
|
||||
// Test dynamic fee configs
|
||||
configs := []struct {
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ func main() {
|
||||
validators = flag.Int("validators", 3, "Number of validators for mnemonic-based genesis")
|
||||
walletKeys = flag.Int("wallet-keys", 0, "Number of mnemonic-derived wallet keys to fund (Lux-internal hardened path m/44'/9000'/nid'/1'/i'; requires MNEMONIC env)")
|
||||
walletAmount = flag.Uint64("wallet-amount", 10000, "Allocation per wallet key in LUX (default: 10000)")
|
||||
bip44WalletKeys = flag.Int("bip44-wallet-keys", 0, "Number of canonical BIP44 wallet keys to fund (m/44'/9000'/0'/0/i — matches Lux/Avalanche web wallet & subnet-bootstrap CLIs)")
|
||||
bip44WalletKeys = flag.Int("bip44-wallet-keys", 0, "Number of canonical BIP44 wallet keys to fund (m/44'/9000'/0'/0/i — SLIP-0044 coin type 9000)")
|
||||
cchainPath = flag.String("cchain", "", "Path to existing C-Chain genesis (preserves original)")
|
||||
format = flag.String("format", "pretty", "Output format: json, pretty")
|
||||
showHelp = flag.Bool("help", false, "Show help")
|
||||
|
||||
@@ -38,6 +38,52 @@ func TestGetGenesis_CChainShardPresentEmbedsCChainGenesis(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (Liquidity-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
|
||||
@@ -50,10 +96,10 @@ func TestBuildGenesisFromDir_AbsentShardEmptyOptIn(t *testing.T) {
|
||||
t.Fatalf("write %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
// Minimal shards: network + P-Chain only. No C/Q/Z. This is the
|
||||
// Liquidity-shape: primary network with P+X, everything else opt-in
|
||||
// post-genesis via CreateChainTx.
|
||||
write("network.json", `{"networkID":1337,"startTime":1735689600,"message":"P+X only test"}`)
|
||||
// Minimal shards: network + P-Chain only. No X/C/Q/Z. This is the
|
||||
// P-only Liquidity 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,
|
||||
@@ -70,7 +116,7 @@ func TestBuildGenesisFromDir_AbsentShardEmptyOptIn(t *testing.T) {
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
for _, field := range []string{"cChainGenesis", "qChainGenesis", "zChainGenesis"} {
|
||||
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)
|
||||
}
|
||||
|
||||
+109
-46
@@ -200,17 +200,11 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
|
||||
// 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+X-only network (Liquidity etc.), ship a config tree
|
||||
// that omits cchain.json / qchain.json / zchain.json. No knob, no hack.
|
||||
cchainData, err := loadOptionalChainShard(networkName, "cchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qchainData, err := loadOptionalChainShard(networkName, "qchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zchainData, err := loadOptionalChainShard(networkName, "zchain.json")
|
||||
// To run a P-only network (Liquidity 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
|
||||
}
|
||||
@@ -230,9 +224,16 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
|
||||
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: pchain.InitialStakedFunds,
|
||||
InitialStakers: pchain.InitialStakers,
|
||||
CChainGenesis: cchainData,
|
||||
QChainGenesis: qchainData,
|
||||
ZChainGenesis: zchainData,
|
||||
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,
|
||||
}
|
||||
@@ -240,14 +241,79 @@ func loadEmbeddedGenesisWithDynamic(networkName string, dynamicPChain *genesis.P
|
||||
return json.Marshal(config)
|
||||
}
|
||||
|
||||
// chainShardSet 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 chainShardSet 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",
|
||||
}
|
||||
|
||||
// chainShardSetSlots 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 *chainShardSet) 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) (chainShardSet, error) {
|
||||
var s chainShardSet
|
||||
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) (chainShardSet, error) {
|
||||
var s chainShardSet
|
||||
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.
|
||||
//
|
||||
// This is the single dispatch point for every opt-in primary-network chain
|
||||
// (C, Q, Z, and any future additions). One pattern, one implementation;
|
||||
// adding a new chain is one call site in the loader, not a new env knob.
|
||||
// One pattern, one implementation; adding a new primary-network chain
|
||||
// is one entry in primaryChainShardFiles plus a slot on chainShardSet,
|
||||
// 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 {
|
||||
@@ -419,18 +485,9 @@ func buildCanonicalGenesisFromSplitFiles(networkName string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("failed to parse pchain.json: %w", err)
|
||||
}
|
||||
|
||||
// Opt-in chains via embedded shards. Absent file → empty string →
|
||||
// chain skipped by builder. One pattern across every primary-network
|
||||
// chain.
|
||||
cchainData, err := loadOptionalChainShard(networkName, "cchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qchainData, err := loadOptionalChainShard(networkName, "qchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zchainData, err := loadOptionalChainShard(networkName, "zchain.json")
|
||||
// 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
|
||||
}
|
||||
@@ -444,9 +501,16 @@ func buildCanonicalGenesisFromSplitFiles(networkName string) ([]byte, error) {
|
||||
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: pchain.InitialStakedFunds,
|
||||
InitialStakers: pchain.InitialStakers,
|
||||
CChainGenesis: cchainData,
|
||||
QChainGenesis: qchainData,
|
||||
ZChainGenesis: zchainData,
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -512,17 +576,9 @@ func buildGenesisFromDir(dir string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("failed to parse pchain.json: %w", err)
|
||||
}
|
||||
|
||||
// Opt-in chains: read each shard from dir. Missing file → empty string →
|
||||
// builder skips the entry. Same pattern as the embedded loader.
|
||||
cchainData, err := readDirShard(dir, "cchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qchainData, err := readDirShard(dir, "qchain.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zchainData, err := readDirShard(dir, "zchain.json")
|
||||
// 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
|
||||
}
|
||||
@@ -552,9 +608,16 @@ func buildGenesisFromDir(dir string) ([]byte, error) {
|
||||
InitialStakeDurationOffset: pchain.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: pchain.InitialStakedFunds,
|
||||
InitialStakers: pchain.InitialStakers,
|
||||
CChainGenesis: cchainData,
|
||||
QChainGenesis: qchainData,
|
||||
ZChainGenesis: zchainData,
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":1,"message":"Lux Chain Genesis"}
|
||||
@@ -0,0 +1 @@
|
||||
{"symbol":"LUX","name":"Lux","denomination":9}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
type ChainRole string
|
||||
|
||||
const (
|
||||
RolePlatform ChainRole = "P" // Platform/staking chain
|
||||
RoleProtocol ChainRole = "P" // Protocol chain — validator set, chain registry, ordering
|
||||
RoleExchange ChainRole = "X" // Exchange/asset transfer chain
|
||||
RoleContract ChainRole = "C" // Contract/EVM chain
|
||||
RoleQuantum ChainRole = "Q" // Quantum-resistant chain
|
||||
@@ -185,12 +185,12 @@ func DefaultMainnetMapping() *ChainMapping {
|
||||
return &ChainMapping{
|
||||
Version: 1,
|
||||
Chains: map[ChainRole]*ChainConfig{
|
||||
RolePlatform: {
|
||||
RoleProtocol: {
|
||||
ChainID: ids.PChainID,
|
||||
VMID: ids.Empty, // Platform VM ID
|
||||
Role: RolePlatform,
|
||||
Name: "Platform Chain",
|
||||
Aliases: []string{"P", "platform"},
|
||||
VMID: ids.Empty, // ProtocolVM ID
|
||||
Role: RoleProtocol,
|
||||
Name: "P-Chain",
|
||||
Aliases: []string{"P", "protocol"},
|
||||
},
|
||||
RoleExchange: {
|
||||
ChainID: ids.XChainID,
|
||||
|
||||
@@ -98,6 +98,7 @@ func GetConfigFile(filepath string) (*Config, error) {
|
||||
InitialStakeDurationOffset: output.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: stakedFunds,
|
||||
InitialStakers: stakers,
|
||||
XChainGenesis: output.XChainGenesis,
|
||||
CChainGenesis: output.CChainGenesis,
|
||||
DChainGenesis: output.DChainGenesis,
|
||||
QChainGenesis: output.QChainGenesis,
|
||||
@@ -302,9 +303,11 @@ func ParseConfigOutput(output *ConfigOutput, networkID uint32) (*Config, error)
|
||||
InitialStakeDurationOffset: output.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: stakedFunds,
|
||||
InitialStakers: stakers,
|
||||
XChainGenesis: output.XChainGenesis,
|
||||
CChainGenesis: output.CChainGenesis,
|
||||
DChainGenesis: output.DChainGenesis,
|
||||
QChainGenesis: output.QChainGenesis,
|
||||
AChainGenesis: output.AChainGenesis,
|
||||
BChainGenesis: output.BChainGenesis,
|
||||
TChainGenesis: output.TChainGenesis,
|
||||
ZChainGenesis: output.ZChainGenesis,
|
||||
|
||||
+8
-7
@@ -890,9 +890,10 @@ func BuildWalletAllocations(nid uint32, numKeys int, amountPerKey uint64) ([]All
|
||||
|
||||
// BuildBIP44WalletAllocations derives wallet keys on the canonical BIP44
|
||||
// path m/44'/9000'/0'/0/i (purpose-44' / coin-9000' / account-0' hardened;
|
||||
// change-0 / index-i NON-hardened). This is the path the Lux/Avalanche
|
||||
// web wallet and any BIP44-conformant client uses. Returns free (no
|
||||
// vesting) spending allocations for each key.
|
||||
// change-0 / index-i NON-hardened). 9000 is the SLIP-0044 coin type Lux
|
||||
// inherits from its upstream lineage; any BIP44-conformant wallet using
|
||||
// that coin type derives the same key set. Returns free (no vesting)
|
||||
// spending allocations for each key.
|
||||
//
|
||||
// Use this instead of BuildWalletAllocations when the receiving consumer
|
||||
// (e.g. a subnet-bootstrap CLI) expects classical BIP44 web-wallet
|
||||
@@ -1113,8 +1114,8 @@ func buildConfigFromKeyInfos(networkID uint32, validatorKeys []KeyInfo, allKeys
|
||||
|
||||
// Track which staking addresses already have an allocation. Validator
|
||||
// addresses that aren't already in allKeys need their own stake
|
||||
// allocation; otherwise PlatformVM rejects the genesis with
|
||||
// "validator has not weight" because there's no UTXO at the
|
||||
// allocation; otherwise the ProtocolVM rejects the genesis
|
||||
// with "validator has not weight" because there's no UTXO at the
|
||||
// validator's stakedFunds address.
|
||||
allocByAddr := make(map[ids.ShortID]bool, len(allocations))
|
||||
for _, a := range allocations {
|
||||
@@ -1122,7 +1123,7 @@ func buildConfigFromKeyInfos(networkID uint32, validatorKeys []KeyInfo, allKeys
|
||||
}
|
||||
// stakeAmount is the locktime-locked stake each validator
|
||||
// contributes. Matches the prior genesis behaviour (3B nLUX across
|
||||
// three future-locktime buckets), so the PlatformVM sees a
|
||||
// three future-locktime buckets), so the ProtocolVM sees a
|
||||
// non-zero stake for each initial staker.
|
||||
const stakeAmount = uint64(1_000_000_000)
|
||||
now := uint64(time.Now().Unix())
|
||||
@@ -1138,7 +1139,7 @@ func buildConfigFromKeyInfos(networkID uint32, validatorKeys []KeyInfo, allKeys
|
||||
|
||||
// Ensure the validator's staking address has a corresponding
|
||||
// allocation. If absent, add one with locked stake so the
|
||||
// PlatformVM can weight the staker.
|
||||
// ProtocolVM can weight the staker.
|
||||
if !allocByAddr[key.StakingAddr] {
|
||||
allocations = append(allocations, Allocation{
|
||||
ETHAddr: key.ETHAddr,
|
||||
|
||||
@@ -21,6 +21,7 @@ type Config struct {
|
||||
InitialStakeDurationOffset uint64 `json:"initialStakeDurationOffset"`
|
||||
InitialStakedFunds []ids.ShortID `json:"initialStakedFunds"`
|
||||
InitialStakers []Staker `json:"initialStakers"`
|
||||
XChainGenesis string `json:"xChainGenesis,omitempty"` // X-Chain: UTXO Exchange VM (small JSON: {"symbol":"LUX","name":"Lux","denomination":9}); empty → skip X-Chain
|
||||
CChainGenesis string `json:"cChainGenesis"`
|
||||
DChainGenesis string `json:"dChainGenesis,omitempty"` // D-Chain: DEX VM genesis
|
||||
QChainGenesis string `json:"qChainGenesis,omitempty"` // Q-Chain: Quantum VM genesis
|
||||
@@ -34,8 +35,8 @@ type Config struct {
|
||||
|
||||
// Chains defines additional chains to include in genesis beyond the
|
||||
// built-in alphabet chains (C, D, Q, B, T, Z, G, K). Each entry becomes
|
||||
// a CreateChainTx in the platform genesis. This allows L1/subnet chains
|
||||
// to be embedded directly in the genesis for automatic bootstrap.
|
||||
// a CreateChainTx in the P-Chain genesis. This allows L1/L2
|
||||
// chains to be embedded directly for automatic bootstrap.
|
||||
Chains []ChainEntry `json:"chains,omitempty"`
|
||||
|
||||
// ChainMapping provides dynamic chain ID configuration per network.
|
||||
@@ -244,7 +245,7 @@ type WarpConfig struct {
|
||||
}
|
||||
|
||||
// ChainEntry defines an additional chain to create at genesis.
|
||||
// Used for embedding L1/L2 chains directly in the platform genesis.
|
||||
// Used for embedding L1/L2 chains directly in the P-Chain genesis.
|
||||
type ChainEntry struct {
|
||||
VMID string `json:"vmID"` // VM ID (base58 encoded)
|
||||
Name string `json:"name"` // Human-readable chain name
|
||||
@@ -266,6 +267,7 @@ type ConfigOutput struct {
|
||||
InitialStakeDurationOffset uint64 `json:"initialStakeDurationOffset"`
|
||||
InitialStakedFunds []string `json:"initialStakedFunds"`
|
||||
InitialStakers []StakerJSON `json:"initialStakers"`
|
||||
XChainGenesis string `json:"xChainGenesis,omitempty"` // X-Chain: UTXO Exchange VM (small JSON: {"symbol":"LUX","name":"Lux","denomination":9}); empty → skip X-Chain
|
||||
CChainGenesis string `json:"cChainGenesis"`
|
||||
DChainGenesis string `json:"dChainGenesis,omitempty"` // D-Chain: DEX VM genesis
|
||||
QChainGenesis string `json:"qChainGenesis,omitempty"` // Q-Chain: Quantum VM genesis
|
||||
@@ -323,6 +325,7 @@ func (c *Config) ToJSON(hrp string) *ConfigOutput {
|
||||
InitialStakeDurationOffset: c.InitialStakeDurationOffset,
|
||||
InitialStakedFunds: stakedFunds,
|
||||
InitialStakers: stakers,
|
||||
XChainGenesis: c.XChainGenesis,
|
||||
CChainGenesis: c.CChainGenesis,
|
||||
DChainGenesis: c.DChainGenesis,
|
||||
QChainGenesis: c.QChainGenesis,
|
||||
|
||||
Reference in New Issue
Block a user