Files
zeekay b81715a57a genesis: split T-Chain into F-Chain (FHE) + M-Chain (MPC) per LP-134
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).
2026-06-28 07:40:05 -07:00

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)
})
}
}