mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
The legacy T-Chain conflated two orthogonal threshold primitives on one
ThresholdVM substrate. LP-134 decomplects them into two distinct
primary-network chains, both served by the shared ThresholdVM library:
F-Chain (F/fhe/fhevm) — threshold FHE, confidential compute / off-EVM
threshold DECRYPT. ThresholdVM in FHE mode.
M-Chain (M/mpc/mpcvm) — threshold MPC, CGGMP21/FROST bridge-custody
SIGNING. ThresholdVM in MPC mode. EVM verify
surface = 0x0800..02 (FROST) + 0x0800..03
(CGGMP21), verify-only, ceremony off-EVM.
Forward-only: tchain.json is retired on all 4 nets (T was never bootstrapped
with live state — a clean 404 forward-split, no state migration). F-Chain
occupies T's vacated shard slot (same VM, keeps Z/G/K byte-stable); M-Chain
is appended. Fresh non-colliding chainIds at the next free indices 9/10
(F: 97269/97268/97270/32327, M: 97369/97368/97370/32437); T's 96869-series
is not recycled.
K-Chain decomplect ("K stays keyvm" per LP-134): the M/mpc/mpcvm aliases and
MPC params were erroneously carried on the KeyVM shard (the "mpc->K backwards"
wiring). Moved them to M-Chain; kchain.json is now pure KMS key management.
Chain-level wiring (one way, data-driven by shard presence):
- pkg/genesis: ConfigOutput/Config gain FChainGenesis (T's slot) +
MChainGenesis; TChainGenesis removed.
- configs.go chainSet/primaryChainShardFiles/slots + config.go
optionalChainShards: tchain.json -> fchain.json, append mchain.json.
- builder: F and M both map to constants.ThresholdVMID (shared substrate)
and are disambiguated in Aliases() by CreateChainTx.BlockchainName.
- chain_mapping role taxonomy: RoleThreshold("T") retired -> RoleFHE("F")
+ RoleMPC("M"); RoleKMS unchanged. (Canonical native FChainID/MChainID
await a luxfi/ids change; the closed P/C/X/Q/A/B/T/Z/G/I/K/D set is
out of scope here. Active path pins EVM chainIds in the shards.)
Tests: chain_shards_test updated to 11 chains + new F/M chainIds; builder
emits 11 chains; new lp134_split_test asserts the decomplect invariants
(F is FHE-only, M is MPC-only, K is KMS-only, no alias collision across
{F,M,K}, tchain.json retired). go build/vet/test green (GOWORK=off).
263 lines
7.9 KiB
Go
263 lines
7.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package builder
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/genesis/configs"
|
|
"github.com/luxfi/ids"
|
|
pgenesis "github.com/luxfi/proto/p/genesis"
|
|
)
|
|
|
|
func TestGetStakingConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
networkID uint32
|
|
}{
|
|
{"Mainnet", constants.MainnetID},
|
|
{"Testnet", constants.TestnetID},
|
|
{"CustomID", constants.CustomID},
|
|
{"Custom", 12345},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := GetStakingConfig(tt.networkID)
|
|
|
|
// Verify basic constraints
|
|
require.GreaterOrEqual(t, cfg.UptimeRequirement, 0.0)
|
|
require.LessOrEqual(t, cfg.UptimeRequirement, 1.0)
|
|
require.Greater(t, cfg.MinValidatorStake, uint64(0))
|
|
require.GreaterOrEqual(t, cfg.MaxValidatorStake, cfg.MinValidatorStake)
|
|
require.Greater(t, cfg.MinDelegatorStake, uint64(0))
|
|
require.Greater(t, cfg.MinStakeDuration, time.Duration(0))
|
|
require.GreaterOrEqual(t, cfg.MaxStakeDuration, cfg.MinStakeDuration)
|
|
|
|
// RewardConfig is populated by builder with node-specific types
|
|
// The genesis package only provides the base staking parameters
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTxFeeConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
networkID uint32
|
|
}{
|
|
{"Mainnet", constants.MainnetID},
|
|
{"Testnet", constants.TestnetID},
|
|
{"CustomID", constants.CustomID},
|
|
{"Custom", 12345},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := GetTxFeeConfig(tt.networkID)
|
|
|
|
// Verify basic fee config
|
|
require.Greater(t, cfg.TxFee, uint64(0))
|
|
require.Greater(t, cfg.CreateAssetTxFee, uint64(0))
|
|
|
|
// Verify dynamic fee config
|
|
require.Greater(t, uint64(cfg.DynamicFeeConfig.MaxCapacity), uint64(0))
|
|
require.Greater(t, uint64(cfg.DynamicFeeConfig.MaxPerSecond), uint64(0))
|
|
|
|
// Verify validator fee config
|
|
require.Greater(t, uint64(cfg.ValidatorFeeConfig.Capacity), uint64(0))
|
|
require.Greater(t, uint64(cfg.ValidatorFeeConfig.Target), uint64(0))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetBootstrappers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
networkID uint32
|
|
}{
|
|
{"Mainnet", constants.MainnetID},
|
|
{"Testnet", constants.TestnetID},
|
|
{"CustomID", constants.CustomID},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
bootstrappers, err := GetBootstrappers(tt.networkID)
|
|
require.NoError(t, err)
|
|
|
|
// Bootstrappers may not be configured for all networks
|
|
// This is acceptable - they can be provided via config
|
|
|
|
// Verify each bootstrapper has valid ID and IP
|
|
for _, b := range bootstrappers {
|
|
require.NotEqual(t, b.ID.String(), "")
|
|
require.True(t, b.IP.IsValid())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSampleBootstrappers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
networkID uint32
|
|
count int
|
|
}{
|
|
{"Mainnet_5", constants.MainnetID, 5},
|
|
{"Mainnet_10", constants.MainnetID, 10},
|
|
{"Testnet_3", constants.TestnetID, 3},
|
|
{"Custom_0", constants.CustomID, 0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sampled, err := SampleBootstrappers(tt.networkID, tt.count)
|
|
require.NoError(t, err)
|
|
|
|
// Should not exceed requested count
|
|
require.LessOrEqual(t, len(sampled), tt.count)
|
|
|
|
// Should not exceed available bootstrappers
|
|
all, err := GetBootstrappers(tt.networkID)
|
|
require.NoError(t, err)
|
|
require.LessOrEqual(t, len(sampled), len(all))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
networkID uint32
|
|
}{
|
|
{"Mainnet", constants.MainnetID},
|
|
{"Testnet", constants.TestnetID},
|
|
{"MainnetChainID", constants.MainnetChainID},
|
|
{"TestnetChainID", constants.TestnetChainID},
|
|
{"CustomID", constants.CustomID},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := GetConfig(tt.networkID)
|
|
require.NotNil(t, cfg)
|
|
// Config should have a valid NetworkID
|
|
require.Greater(t, cfg.NetworkID, uint32(0))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVMAliases(t *testing.T) {
|
|
// Verify all expected VMs have aliases
|
|
require.Contains(t, VMAliases, constants.PlatformVMID)
|
|
require.Contains(t, VMAliases, constants.XVMID)
|
|
require.Contains(t, VMAliases, constants.EVMID)
|
|
|
|
// Verify aliases are non-empty
|
|
for vmID, aliases := range VMAliases {
|
|
require.NotEmpty(t, aliases, "VM %s should have aliases", vmID)
|
|
}
|
|
}
|
|
|
|
func TestChainAliases(t *testing.T) {
|
|
require.NotEmpty(t, PChainAliases)
|
|
require.NotEmpty(t, XChainAliases)
|
|
require.NotEmpty(t, CChainAliases)
|
|
|
|
require.Contains(t, PChainAliases, "P")
|
|
require.Contains(t, XChainAliases, "X")
|
|
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 utxoAssetID (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 utxoAssetID, and produce a P-Chain genesis
|
|
// with zero CreateChainTx entries. This is the primary-from-P-only
|
|
// boot path: a sovereign primary network (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 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, utxoAssetID, err := FromConfig(cfg)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, ids.Empty, utxoAssetID, "mainnet has X-Chain → real asset ID")
|
|
|
|
pgc, err := pvmGenesisCodec()
|
|
require.NoError(t, err)
|
|
|
|
full, err := pgenesis.Parse(pgc, bytes)
|
|
require.NoError(t, err)
|
|
require.Len(t, full.Chains, 11, "mainnet emits all 11 primary chains (LP-134: X,C,D,Q,A,B,F,Z,G,K,M)")
|
|
|
|
// 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.FChainGenesis = ""
|
|
cfg.ZChainGenesis = ""
|
|
cfg.GChainGenesis = ""
|
|
cfg.KChainGenesis = ""
|
|
cfg.MChainGenesis = ""
|
|
|
|
bytes, utxoAssetID, err = FromConfig(cfg)
|
|
require.NoError(t, err, "P-only build must succeed")
|
|
require.Equal(t, ids.Empty, utxoAssetID, "no X-Chain → no LUX asset → ids.Empty")
|
|
|
|
pOnly, err := pgenesis.Parse(pgc, 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 {
|
|
name string
|
|
config interface{}
|
|
}{
|
|
{"MainnetDynamic", MainnetDynamicFeeConfig},
|
|
{"TestnetDynamic", TestnetDynamicFeeConfig},
|
|
{"LocalDynamic", LocalDynamicFeeConfig},
|
|
{"MainnetValidator", MainnetValidatorFeeConfig},
|
|
{"TestnetValidator", TestnetValidatorFeeConfig},
|
|
{"LocalValidator", LocalValidatorFeeConfig},
|
|
}
|
|
|
|
for _, tt := range configs {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.NotNil(t, tt.config)
|
|
})
|
|
}
|
|
}
|