Files
node/config/config_test.go
zeekayandHanzo Dev c5e999487c Green the 8 post-bump test failures (real fixes, no delete-to-hide)
- spending.go: harden spendingTx.Bytes() against nil msg (uninitialized tx no
  longer panics the spend-verification path; production always sets msg).
- config/genesis-builder tests: codec-era Codec.Marshal -> native g.Bytes();
  AddValidator field access -> methods (Weight()/Validator()/StakeOuts());
  drop unused pchaintxs imports.
- config test: consensus params K=30 alpha 16->20/25 to satisfy v1.36's tighter
  BFT bound (2*alpha-K >= floor((K-1)/3)+1); assertions updated.
- version/compatibility.json: register Current v1.32.11 under RPCChainVM proto 42
  (pre-existing registration gap).
- cevm e2e: genesis fixture completed with gasLimit+difficulty (strict geth
  genesis validation in the installed CEvm plugin; pre-existing fixture drift).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-10 21:20:17 -07:00

754 lines
24 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/go-json-experiment/json"
"log"
"os"
"path/filepath"
"testing"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
consensusconfig "github.com/luxfi/consensus/config"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/node/chains"
"github.com/luxfi/node/genesis/builder"
"github.com/luxfi/node/nets"
pchaingenesis "github.com/luxfi/node/vms/platformvm/genesis"
)
const chainConfigFilenameExtension = ".ex"
// equalChainConfigs compares two ChainConfig maps using bytes.Equal for the
// []byte fields, so nil and empty []byte compare equal. json/v2 unmarshals
// absent/null []byte fields to empty (non-nil), unlike v1 which left them nil.
func equalChainConfigs(t *testing.T, expected, actual map[string]chains.ChainConfig) {
t.Helper()
require := require.New(t)
require.Equal(len(expected), len(actual), "map length mismatch")
for k, want := range expected {
got, ok := actual[k]
require.True(ok, "missing key %q", k)
require.True(bytes.Equal(want.Config, got.Config),
"%q Config: expected %x got %x", k, want.Config, got.Config)
require.True(bytes.Equal(want.Upgrade, got.Upgrade),
"%q Upgrade: expected %x got %x", k, want.Upgrade, got.Upgrade)
}
}
func TestGetChainConfigsFromFiles(t *testing.T) {
tests := map[string]struct {
configs map[string]string
upgrades map[string]string
expected map[string]chains.ChainConfig
}{
"no chain configs": {
configs: map[string]string{},
upgrades: map[string]string{},
expected: map[string]chains.ChainConfig{},
},
"valid chain-id": {
configs: map[string]string{"yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp": "hello", "2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm": "world"},
upgrades: map[string]string{"yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp": "helloUpgrades"},
expected: func() map[string]chains.ChainConfig {
m := map[string]chains.ChainConfig{}
id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
require.NoError(t, err)
m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}
id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
require.NoError(t, err)
m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}
return m
}(),
},
"valid alias": {
configs: map[string]string{"C": "hello", "X": "world"},
upgrades: map[string]string{"C": "upgradess"},
expected: func() map[string]chains.ChainConfig {
m := map[string]chains.ChainConfig{}
m["C"] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("upgradess")}
m["X"] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}
return m
}(),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
root := t.TempDir()
configJSON := fmt.Sprintf(`{%q: %q}`, ChainConfigDirKey, root)
configFile := setupConfigJSON(t, root, configJSON)
chainsDir := root
// Create custom configs
for key, value := range test.configs {
chainDir := filepath.Join(chainsDir, key)
setupFile(t, chainDir, chainConfigFileName+chainConfigFilenameExtension, value)
}
for key, value := range test.upgrades {
chainDir := filepath.Join(chainsDir, key)
setupFile(t, chainDir, chainUpgradeFileName+chainConfigFilenameExtension, value)
}
v := setupViper(configFile)
// Parse config
require.Equal(root, v.GetString(ChainConfigDirKey))
chainConfigs, err := getChainConfigs(v)
require.NoError(err)
equalChainConfigs(t, test.expected, chainConfigs)
})
}
}
func TestGetChainConfigsDirNotExist(t *testing.T) {
tests := map[string]struct {
structure string
file map[string]string
expectedErr error
expected map[string]chains.ChainConfig
}{
"cdir not exist": {
structure: "/",
file: map[string]string{"config.ex": "noeffect"},
expectedErr: errCannotReadDirectory,
expected: nil,
},
"cdir is file ": {
structure: "/",
file: map[string]string{"cdir": "noeffect"},
expectedErr: errCannotReadDirectory,
expected: nil,
},
"chain subdir not exist": {
structure: "/cdir/",
file: map[string]string{"config.ex": "noeffect"},
expectedErr: nil,
expected: map[string]chains.ChainConfig{},
},
"full structure": {
structure: "/cdir/C/",
file: map[string]string{"config.ex": "hello"},
expectedErr: nil,
expected: map[string]chains.ChainConfig{"C": {Config: []byte("hello"), Upgrade: []byte(nil)}},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
root := t.TempDir()
chainConfigDir := filepath.Join(root, "cdir")
configJSON := fmt.Sprintf(`{%q: %q}`, ChainConfigDirKey, chainConfigDir)
configFile := setupConfigJSON(t, root, configJSON)
dirToCreate := filepath.Join(root, test.structure)
require.NoError(os.MkdirAll(dirToCreate, 0o700))
for key, value := range test.file {
setupFile(t, dirToCreate, key, value)
}
v := setupViper(configFile)
// Parse config
require.Equal(chainConfigDir, v.GetString(ChainConfigDirKey))
// don't read with getConfigFromViper since it's very slow.
chainConfigs, err := getChainConfigs(v)
require.ErrorIs(err, test.expectedErr)
if test.expected == nil {
require.Nil(chainConfigs)
} else {
equalChainConfigs(t, test.expected, chainConfigs)
}
})
}
}
func TestSetChainConfigDefaultDir(t *testing.T) {
require := require.New(t)
root := t.TempDir()
// changes internal package variable, since using defaultDir (under user home) is risky.
defaultChainConfigDir = filepath.Join(root, "cdir")
configFilePath := setupConfigJSON(t, root, "{}")
v := setupViper(configFilePath)
require.Equal(defaultChainConfigDir, v.GetString(ChainConfigDirKey))
chainsDir := filepath.Join(defaultChainConfigDir, "C")
setupFile(t, chainsDir, chainConfigFileName+chainConfigFilenameExtension, "helloworld")
chainConfigs, err := getChainConfigs(v)
require.NoError(err)
expected := map[string]chains.ChainConfig{"C": {Config: []byte("helloworld"), Upgrade: []byte(nil)}}
equalChainConfigs(t, expected, chainConfigs)
}
func TestGetChainConfigsFromFlags(t *testing.T) {
tests := map[string]struct {
fullConfigs map[string]chains.ChainConfig
expected map[string]chains.ChainConfig
}{
"no chain configs": {
fullConfigs: map[string]chains.ChainConfig{},
expected: map[string]chains.ChainConfig{},
},
"valid chain-id": {
fullConfigs: func() map[string]chains.ChainConfig {
m := map[string]chains.ChainConfig{}
id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
require.NoError(t, err)
m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}
id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
require.NoError(t, err)
m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}
return m
}(),
expected: func() map[string]chains.ChainConfig {
m := map[string]chains.ChainConfig{}
id1, err := ids.FromString("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp")
require.NoError(t, err)
m[id1.String()] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("helloUpgrades")}
id2, err := ids.FromString("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm")
require.NoError(t, err)
m[id2.String()] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}
return m
}(),
},
"valid alias": {
fullConfigs: map[string]chains.ChainConfig{
"C": {Config: []byte("hello"), Upgrade: []byte("upgradess")},
"X": {Config: []byte("world"), Upgrade: []byte(nil)},
},
expected: func() map[string]chains.ChainConfig {
m := map[string]chains.ChainConfig{}
m["C"] = chains.ChainConfig{Config: []byte("hello"), Upgrade: []byte("upgradess")}
m["X"] = chains.ChainConfig{Config: []byte("world"), Upgrade: []byte(nil)}
return m
}(),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
jsonMaps, err := json.Marshal(test.fullConfigs)
require.NoError(err)
encodedFileContent := base64.StdEncoding.EncodeToString(jsonMaps)
// build viper config
v := setupViperFlags()
v.Set(ChainConfigContentKey, encodedFileContent)
// Parse config
chainConfigs, err := getChainConfigs(v)
require.NoError(err)
equalChainConfigs(t, test.expected, chainConfigs)
})
}
}
func TestGetVMAliasesFromFile(t *testing.T) {
tests := map[string]struct {
givenJSON string
expected map[ids.ID][]string
expectedErr error
}{
"wrong vm id": {
givenJSON: `{"wrongVmId": ["vm1","vm2"]}`,
expected: nil,
expectedErr: errUnmarshalling,
},
"vm id": {
givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"],
"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU": ["vm3", "vm4"] }`,
expected: func() map[ids.ID][]string {
m := map[ids.ID][]string{}
id1, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
id2, _ := ids.FromString("Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU")
m[id1] = []string{"vm1", "vm2"}
m[id2] = []string{"vm3", "vm4"}
return m
}(),
expectedErr: nil,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
root := t.TempDir()
aliasPath := filepath.Join(root, "aliases.json")
configJSON := fmt.Sprintf(`{%q: %q}`, VMAliasesFileKey, aliasPath)
configFilePath := setupConfigJSON(t, root, configJSON)
setupFile(t, root, "aliases.json", test.givenJSON)
v := setupViper(configFilePath)
vmAliases, err := getVMAliases(v)
require.ErrorIs(err, test.expectedErr)
require.Equal(test.expected, vmAliases)
})
}
}
func TestGetVMAliasesFromFlag(t *testing.T) {
tests := map[string]struct {
givenJSON string
expected map[ids.ID][]string
expectedErr error
}{
"wrong vm id": {
givenJSON: `{"wrongVmId": ["vm1","vm2"]}`,
expected: nil,
expectedErr: errUnmarshalling,
},
"vm id": {
givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"],
"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU": ["vm3", "vm4"] }`,
expected: func() map[ids.ID][]string {
m := map[ids.ID][]string{}
id1, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
id2, _ := ids.FromString("Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU")
m[id1] = []string{"vm1", "vm2"}
m[id2] = []string{"vm3", "vm4"}
return m
}(),
expectedErr: nil,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
encodedFileContent := base64.StdEncoding.EncodeToString([]byte(test.givenJSON))
// build viper config
v := setupViperFlags()
v.Set(VMAliasesContentKey, encodedFileContent)
vmAliases, err := getVMAliases(v)
require.ErrorIs(err, test.expectedErr)
require.Equal(test.expected, vmAliases)
})
}
}
func TestGetVMAliasesDefaultDir(t *testing.T) {
require := require.New(t)
root := t.TempDir()
// changes internal package variable, since using defaultDir (under user home) is risky.
defaultVMAliasFilePath = filepath.Join(root, "aliases.json")
configFilePath := setupConfigJSON(t, root, "{}")
v := setupViper(configFilePath)
require.Equal(defaultVMAliasFilePath, v.GetString(VMAliasesFileKey))
setupFile(t, root, "aliases.json", `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": ["vm1","vm2"]}`)
vmAliases, err := getVMAliases(v)
require.NoError(err)
expected := map[ids.ID][]string{}
id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
expected[id] = []string{"vm1", "vm2"}
require.Equal(expected, vmAliases)
}
func TestGetVMAliasesDirNotExists(t *testing.T) {
require := require.New(t)
root := t.TempDir()
aliasPath := "/not/exists"
// set it explicitly
configJSON := fmt.Sprintf(`{%q: %q}`, VMAliasesFileKey, aliasPath)
configFilePath := setupConfigJSON(t, root, configJSON)
v := setupViper(configFilePath)
vmAliases, err := getVMAliases(v)
require.ErrorIs(err, errFileDoesNotExist)
require.Nil(vmAliases)
// do not set it explicitly
configJSON = "{}"
configFilePath = setupConfigJSON(t, root, configJSON)
v = setupViper(configFilePath)
vmAliases, err = getVMAliases(v)
require.Nil(vmAliases)
require.NoError(err)
}
func TestGetNetConfigsFromFile(t *testing.T) {
netID, err := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
require.NoError(t, err)
defaultConfigs := map[ids.ID]nets.Config{
netID: getDefaultNetConfig(setupViperFlags()),
}
tests := map[string]struct {
fileName string
givenJSON string
testF func(*require.Assertions, map[ids.ID]nets.Config)
expectedErr error
}{
"wrong config": {
fileName: "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
givenJSON: `thisisnotjson`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Nil(given)
},
expectedErr: errUnmarshalling,
},
"chain is not tracked": {
fileName: "Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU.json",
givenJSON: `{"validatorOnly": true}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Equal(defaultConfigs, given)
},
expectedErr: nil,
},
"default config when incorrect extension used": {
fileName: "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.yaml",
givenJSON: `{"validatorOnly": true}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Equal(defaultConfigs, given)
},
expectedErr: nil,
},
"invalid consensus parameters": {
fileName: "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
givenJSON: `{"consensusParameters":{"k": 111, "alphaPreference":1234} }`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Nil(given)
},
expectedErr: consensusconfig.ErrParametersInvalid,
},
"correct config": {
fileName: "2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i.json",
givenJSON: `{"validatorOnly": true, "consensusParameters":{"k":20, "alphaPreference":15, "alphaConfidence":15} }`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
config, ok := given[id]
require.True(ok)
require.True(config.ValidatorOnly)
require.Equal(15, config.ConsensusParameters.AlphaConfidence)
require.Equal(15, config.ConsensusParameters.AlphaPreference)
require.Equal(20, config.ConsensusParameters.K)
},
expectedErr: nil,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
root := t.TempDir()
chainPath := filepath.Join(root, "chains")
configJSON := fmt.Sprintf(`{%q: %q}`, NetConfigDirKey, chainPath)
configFilePath := setupConfigJSON(t, root, configJSON)
setupFile(t, chainPath, test.fileName, test.givenJSON)
v := setupViper(configFilePath)
chainConfigs, err := getNetConfigs(v, []ids.ID{netID})
require.ErrorIs(err, test.expectedErr)
if test.expectedErr != nil {
return
}
test.testF(require, chainConfigs)
})
}
}
func TestGetNetConfigsFromFlags(t *testing.T) {
netID, err := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
require.NoError(t, err)
defaultConfigs := map[ids.ID]nets.Config{
netID: getDefaultNetConfig(setupViperFlags()),
}
tests := map[string]struct {
givenJSON string
testF func(*require.Assertions, map[ids.ID]nets.Config)
expectedErr error
}{
"default config used when no config provided": {
givenJSON: `{}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Equal(defaultConfigs, given)
},
expectedErr: nil,
},
"entry with no config": {
givenJSON: `{"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i":{"consensusParameters":{"k":20,"alphaPreference":15,"alphaConfidence":15}}}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Len(given, 1)
id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
config, ok := given[id]
require.True(ok)
// should have our specified params
require.Equal(20, config.ConsensusParameters.K)
require.Equal(15, config.ConsensusParameters.AlphaPreference)
require.Equal(15, config.ConsensusParameters.AlphaConfidence)
},
expectedErr: nil,
},
"default config used when chain is not tracked": {
givenJSON: `{"Gmt4fuNsGJAd2PX86LBvycGaBpgCYKbuULdCLZs3SEs1Jx1LU":{"validatorOnly":true}}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Equal(defaultConfigs, given)
},
expectedErr: nil,
},
"invalid consensus parameters": {
givenJSON: `{
"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": {
"consensusParameters": {
"k": 111,
"alphaPreference": 1234
}
}
}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
require.Empty(given)
},
expectedErr: consensusconfig.ErrParametersInvalid,
},
"correct config": {
givenJSON: `{
"2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i": {
"consensusParameters": {
"k": 30,
"alphaPreference": 20,
"alphaConfidence": 25
},
"validatorOnly": true
}
}`,
testF: func(require *require.Assertions, given map[ids.ID]nets.Config) {
id, _ := ids.FromString("2Ctt6eGAeo4MLqTmGa7AdRecuVMPGWEX9wSsCLBYrLhX4a394i")
config, ok := given[id]
require.True(ok)
require.True(config.ValidatorOnly)
require.Equal(20, config.ConsensusParameters.AlphaPreference)
require.Equal(25, config.ConsensusParameters.AlphaConfidence)
require.Equal(30, config.ConsensusParameters.K)
// must still respect defaults (MainnetParameters.MaxOutstandingItems = 1024)
require.Equal(1024, config.ConsensusParameters.MaxOutstandingItems)
},
expectedErr: nil,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
require := require.New(t)
encodedFileContent := base64.StdEncoding.EncodeToString([]byte(test.givenJSON))
// build viper config
v := setupViperFlags()
v.Set(NetConfigContentKey, encodedFileContent)
chainConfigs, err := getNetConfigs(v, []ids.ID{netID})
require.ErrorIs(err, test.expectedErr)
if test.expectedErr != nil {
return
}
test.testF(require, chainConfigs)
})
}
}
// setups config json file and writes content
func setupConfigJSON(t *testing.T, rootPath string, value string) string {
configFilePath := filepath.Join(rootPath, "config.json")
require.NoError(t, os.WriteFile(configFilePath, []byte(value), 0o600))
return configFilePath
}
// setups file creates necessary path and writes value to it.
func setupFile(t *testing.T, path string, fileName string, value string) {
require := require.New(t)
require.NoError(os.MkdirAll(path, 0o700))
filePath := filepath.Join(path, fileName)
require.NoError(os.WriteFile(filePath, []byte(value), 0o600))
}
func setupViperFlags() *viper.Viper {
v := viper.New()
fs := BuildFlagSet()
pflag.Parse()
if err := v.BindPFlags(fs); err != nil {
log.Fatal(err)
}
return v
}
func setupViper(configFilePath string) *viper.Viper {
v := setupViperFlags()
v.SetConfigFile(configFilePath)
if err := v.ReadInConfig(); err != nil {
log.Fatal(err)
}
return v
}
func TestSkipBootstrapConfig(t *testing.T) {
v := viper.New()
// Test default values
v.SetDefault(SkipBootstrapKey, false)
v.SetDefault(EnableAutominingKey, false)
config, err := getBootstrapConfig(v, 1)
require.NoError(t, err)
require.False(t, config.SkipBootstrap)
require.False(t, config.EnableAutomining)
// Test with skip bootstrap enabled
v.Set(SkipBootstrapKey, true)
config, err = getBootstrapConfig(v, 1)
require.NoError(t, err)
require.True(t, config.SkipBootstrap)
require.False(t, config.EnableAutomining)
// Test with automining enabled
v.Set(EnableAutominingKey, true)
config, err = getBootstrapConfig(v, 1)
require.NoError(t, err)
require.True(t, config.SkipBootstrap)
require.True(t, config.EnableAutomining)
// Test with both disabled
v.Set(SkipBootstrapKey, false)
v.Set(EnableAutominingKey, false)
config, err = getBootstrapConfig(v, 1)
require.NoError(t, err)
require.False(t, config.SkipBootstrap)
require.False(t, config.EnableAutomining)
}
func TestDevModeFlags(t *testing.T) {
tests := []struct {
name string
skipBootstrap bool
enableAutomining bool
expected bool
}{
{
name: "both enabled",
skipBootstrap: true,
enableAutomining: true,
expected: true,
},
{
name: "only skip bootstrap",
skipBootstrap: true,
enableAutomining: false,
expected: true,
},
{
name: "only automining",
skipBootstrap: false,
enableAutomining: true,
expected: true,
},
{
name: "both disabled",
skipBootstrap: false,
enableAutomining: false,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := viper.New()
v.Set(SkipBootstrapKey, tt.skipBootstrap)
v.Set(EnableAutominingKey, tt.enableAutomining)
config, err := getBootstrapConfig(v, 1)
require.NoError(t, err)
require.Equal(t, tt.skipBootstrap, config.SkipBootstrap)
require.Equal(t, tt.enableAutomining, config.EnableAutomining)
// Check that at least one dev mode flag is set
devModeEnabled := config.SkipBootstrap || config.EnableAutomining
require.Equal(t, tt.expected, devModeEnabled)
})
}
}
// TestResolveUTXOAssetID_FromSovereignGenesis covers the canonical
// behaviour the sovereign-L1 fix relies on: when the loaded platform
// genesis bakes an X-Chain, resolveUTXOAssetID returns the runtime asset
// ID encoded IN the genesis (matches what FromConfig produces, matches
// what the running X-Chain reports via platform.getStakingAssetID).
//
// Critically: NOT constants.UTXOAssetIDFor(networkID). That value is
// network-id-keyed and would silently collide between two sovereign L1s
// sharing a primary-network ID.
func TestResolveUTXOAssetID_FromSovereignGenesis(t *testing.T) {
require := require.New(t)
cfg := builder.GetConfig(constants.LocalID)
require.NotNil(cfg)
require.NotEmpty(cfg.XChainGenesis, "fixture must bake X-Chain")
genesisBytes, expectedID, err := builder.FromConfig(cfg)
require.NoError(err)
require.NotEqual(ids.Empty, expectedID)
gotID, err := resolveUTXOAssetID(constants.LocalID, genesisBytes)
require.NoError(err)
require.Equal(expectedID, gotID,
"resolveUTXOAssetID must agree with FromConfig on the genesis-derived asset ID")
}
// TestResolveUTXOAssetID_POnlyFallback verifies that when the platform
// genesis bakes no X-Chain (P-only mode), resolveUTXOAssetID falls back
// to constants.UTXOAssetIDFor(networkID). That value is unused at
// runtime (no X-Chain to mint on) but keeps the existing nodeConfig
// shape consistent for downstream consumers.
func TestResolveUTXOAssetID_POnlyFallback(t *testing.T) {
require := require.New(t)
pOnly := &pchaingenesis.Genesis{Chains: nil}
pOnlyBytes, err := pOnly.Bytes()
require.NoError(err)
gotID, err := resolveUTXOAssetID(42, pOnlyBytes)
require.NoError(err)
require.Equal(constants.UTXOAssetIDFor(42), gotID,
"P-only must fall through to UTXOAssetIDFor(networkID)")
}
// TestResolveUTXOAssetID_Malformed asserts that bad genesis bytes surface
// an error rather than silently returning ids.Empty (which would
// reintroduce the UTXOAssetIDFor fallback and defeat the fix on
// sovereign L1s where the fallback value is wrong).
func TestResolveUTXOAssetID_Malformed(t *testing.T) {
require := require.New(t)
_, err := resolveUTXOAssetID(1, []byte{0xff, 0xfe, 0xfd, 0xfc})
require.Error(err)
}