clean: squash history (binaries stripped via filter-repo)

This commit is contained in:
Hanzo AI
2026-04-13 03:45:21 -07:00
commit f6e63d60b2
2029 changed files with 465257 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"fmt"
"strings"
)
// ChainDatabaseConfig holds per-chain database configuration
type ChainDatabaseConfig struct {
// Default database type for all chains
DefaultType string
// Per-chain overrides
PChainDBType string
XChainDBType string
CChainDBType string
// Additional net configurations can be added here
// NetDBTypes map[ids.ID]string
}
// GetDatabaseType returns the database type for a specific chain
func (c *ChainDatabaseConfig) GetDatabaseType(chainAlias string) string {
switch strings.ToUpper(chainAlias) {
case "P":
if c.PChainDBType != "" {
return c.PChainDBType
}
case "X":
if c.XChainDBType != "" {
return c.XChainDBType
}
case "C":
if c.CChainDBType != "" {
return c.CChainDBType
}
}
// Return default if no specific override
return c.DefaultType
}
// Validate ensures all database types are valid
func (c *ChainDatabaseConfig) Validate() error {
validTypes := map[string]bool{
"pebbledb": true,
"zapdb": true,
"memdb": true,
}
// Check default type
if !validTypes[c.DefaultType] {
return fmt.Errorf("invalid default database type: %s", c.DefaultType)
}
// Check chain-specific types
for chain, dbType := range map[string]string{
"P-Chain": c.PChainDBType,
"X-Chain": c.XChainDBType,
"C-Chain": c.CChainDBType,
} {
if dbType != "" && !validTypes[dbType] {
return fmt.Errorf("invalid database type for %s: %s", chain, dbType)
}
}
return nil
}
+2235
View File
File diff suppressed because it is too large Load Diff
+1351
View File
File diff suppressed because it is too large Load Diff
+674
View File
@@ -0,0 +1,674 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"encoding/base64"
"encoding/json"
"fmt"
"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/ids"
"github.com/luxfi/node/chains"
"github.com/luxfi/node/nets"
)
const chainConfigFilenameExtension = ".ex"
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)
require.Equal(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)
require.Equal(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)}}
require.Equal(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)
require.Equal(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": 16,
"alphaConfidence": 20
},
"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(16, config.ConsensusParameters.AlphaPreference)
require.Equal(20, 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)
})
}
}
+453
View File
@@ -0,0 +1,453 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/luxfi/constants"
"github.com/luxfi/genesis/pkg/genesis"
"github.com/luxfi/node/genesis/builder"
"github.com/luxfi/node/trace"
"github.com/luxfi/node/vms/components/gas"
"github.com/luxfi/node/vms/proposervm"
compression "github.com/luxfi/compress"
"github.com/luxfi/net/dynamicip"
"github.com/luxfi/sys/ulimit"
consensusconfig "github.com/luxfi/consensus/config"
)
const (
DefaultHTTPPort = 9630
DefaultStakingPort = 9631
LuxNodeDataDirVar = "LUXD_DATA_DIR"
defaultUnexpandedDataDir = "$" + LuxNodeDataDirVar
DefaultProcessContextFilename = "process.json"
)
var (
// [defaultUnexpandedDataDir] will be expanded when reading the flags
defaultDataDir = filepath.Join("$HOME", ".lux")
defaultDBDir = filepath.Join(defaultUnexpandedDataDir, "db")
defaultLogDir = filepath.Join(defaultUnexpandedDataDir, "logs")
defaultProfileDir = filepath.Join(defaultUnexpandedDataDir, "profiles")
defaultStakingPath = filepath.Join(defaultUnexpandedDataDir, "staking")
defaultStakingTLSKeyPath = filepath.Join(defaultStakingPath, "staker.key")
defaultStakingCertPath = filepath.Join(defaultStakingPath, "staker.crt")
defaultStakingSignerKeyPath = filepath.Join(defaultStakingPath, "signer.key")
defaultConfigDir = filepath.Join(defaultUnexpandedDataDir, "configs")
defaultChainConfigDir = filepath.Join(defaultConfigDir, "chains")
defaultVMConfigDir = filepath.Join(defaultConfigDir, "vms")
defaultVMAliasFilePath = filepath.Join(defaultVMConfigDir, "aliases.json")
defaultChainAliasFilePath = filepath.Join(defaultChainConfigDir, "aliases.json")
defaultNetConfigDir = filepath.Join(defaultConfigDir, "chains")
defaultPluginDir = filepath.Join(defaultUnexpandedDataDir, "plugins", "current")
defaultChainDataDir = filepath.Join(defaultUnexpandedDataDir, "chainData")
defaultProcessContextPath = filepath.Join(defaultUnexpandedDataDir, DefaultProcessContextFilename)
)
func deprecateFlags(fs *pflag.FlagSet) error {
for key, message := range deprecatedKeys {
if err := fs.MarkDeprecated(key, message); err != nil {
return err
}
}
return nil
}
func addProcessFlags(fs *pflag.FlagSet) {
// If true, print the version and quit.
fs.Bool(VersionKey, false, "If true, print version and quit")
fs.Bool(VersionJSONKey, false, "If true, print version in JSON format and quit")
}
func addNodeFlags(fs *pflag.FlagSet) {
// Development mode
fs.Bool(DevModeKey, false, "Enables automine mode: single-node consensus for local testing. Use --network-id for public devnet/testnet/mainnet.")
fs.Bool(DevLightKey, false, "Enables dev mode with low memory settings (--dev + --low-memory). Target: <200MB idle, <500MB under load")
fs.Bool(LowMemoryKey, false, "Enables low memory configuration for resource-constrained environments")
fs.String(MemoryProfileKey, "", "Memory profile: 'low' (<50MB), 'standard' (<100MB, default), 'max' (~512MB)")
fs.String(ConfigProfileKey, "", "Load a predefined configuration profile (e.g., 'dev-light', 'production')")
// Low memory database tuning
fs.Uint64(DBCacheSizeKey, 0, "Database cache size in bytes (0 = use defaults based on mode)")
fs.Uint64(DBMemtableSizeKey, 0, "Database memtable size in bytes (0 = use defaults based on mode)")
fs.Uint64(StateCacheSizeKey, 0, "State cache size in bytes (0 = use defaults based on mode)")
fs.Uint64(BlockCacheSizeKey, 0, "Block cache size in bytes (0 = use defaults based on mode)")
fs.Bool(DisableBloomFiltersKey, false, "Disable bloom filters for small datasets (saves memory)")
fs.Bool(LazyChainLoadingKey, false, "Load chain data on first request instead of at startup")
fs.Bool(SingleValidatorModeKey, false, "Run with a single validator (minimal memory for local dev)")
// Home directory
fs.String(DataDirKey, defaultDataDir, "Sets the base data directory where default sub-directories will be placed unless otherwise specified.")
// System
fs.Uint64(FdLimitKey, ulimit.DefaultFDLimit, "Attempts to raise the process file descriptor limit to at least this value and error if the value is above the system max")
// Plugin directory
fs.String(PluginDirKey, defaultPluginDir, "Path to the plugin directory")
// Config File
fs.String(ConfigFileKey, "", fmt.Sprintf("Specifies a config file. Ignored if %s is specified", ConfigContentKey))
fs.String(ConfigContentKey, "", "Specifies base64 encoded config content")
fs.String(ConfigContentTypeKey, "json", "Specifies the format of the base64 encoded config content. Available values: 'json', 'yaml', 'toml'")
// Genesis
fs.String(GenesisFileKey, "", fmt.Sprintf("Specifies a genesis config file path. Ignored when running standard networks or if %s is specified",
GenesisFileContentKey))
fs.String(GenesisFileContentKey, "", "Specifies base64 encoded genesis content")
fs.String(GenesisDBKey, "", "Path to existing genesis database for replay. Cannot be used with genesis-file or genesis-file-content")
fs.String(GenesisDBTypeKey, "zapdb", "Database type to use for genesis database. Must be one of {pebbledb, zapdb}")
fs.Uint64(GenesisBlockLimitKey, 0, "Limit number of blocks to replay during genesis (0 = all blocks)")
fs.Bool(AllowCustomGenesisKey, true, "Allow custom genesis for mainnet/testnet (default: true for development, set false for production)")
fs.Bool(AllowGenesisUpdateKey, false, "Allow updating stored genesis hash when genesis changes (use for adding new primary network chains)")
// Upgrade
fs.String(UpgradeFileKey, "", fmt.Sprintf("Specifies an upgrade config file path. Ignored when running standard networks or if %s is specified",
UpgradeFileContentKey))
fs.String(UpgradeFileContentKey, "", "Specifies base64 encoded upgrade content")
// Network ID
fs.String(NetworkNameKey, constants.MainnetName, "Network ID this node will connect to")
// fs.Bool(MainnetKey, false, "Connect to Lux mainnet (network ID 96369)")
// fs.Bool(TestnetKey, false, "Connect to Lux testnet (network ID 96368)")
// fs.Bool(LocalnetKey, false, "Connect to local network (network ID 1337)")
// LP flagging
fs.IntSlice(LPSupportKey, nil, "LPs to support adoption")
fs.IntSlice(LPObjectKey, nil, "LPs to object adoption")
// LUX fees:
// Validator fees:
fs.Uint64(ValidatorFeesCapacityKey, uint64(builder.LocalValidatorFeeConfig.Capacity), "Maximum number of validators")
fs.Uint64(ValidatorFeesTargetKey, uint64(builder.LocalValidatorFeeConfig.Target), "Target number of validators")
fs.Uint64(ValidatorFeesMinPriceKey, uint64(builder.LocalValidatorFeeConfig.MinPrice), "Minimum validator price in nLUX per second")
fs.Uint64(ValidatorFeesExcessConversionConstantKey, uint64(builder.LocalValidatorFeeConfig.ExcessConversionConstant), "Constant to convert validator excess price")
// Dynamic fees:
fs.Uint64(DynamicFeesBandwidthWeightKey, builder.LocalDynamicFeeConfig.Weights[gas.Bandwidth], "Complexity multiplier used to convert Bandwidth into Gas")
fs.Uint64(DynamicFeesDBReadWeightKey, builder.LocalDynamicFeeConfig.Weights[gas.DBRead], "Complexity multiplier used to convert DB Reads into Gas")
fs.Uint64(DynamicFeesDBWriteWeightKey, builder.LocalDynamicFeeConfig.Weights[gas.DBWrite], "Complexity multiplier used to convert DB Writes into Gas")
fs.Uint64(DynamicFeesComputeWeightKey, builder.LocalDynamicFeeConfig.Weights[gas.Compute], "Complexity multiplier used to convert Compute into Gas")
fs.Uint64(DynamicFeesMaxGasCapacityKey, uint64(builder.LocalDynamicFeeConfig.MaxCapacity), "Maximum amount of Gas the chain is allowed to store for future use")
fs.Uint64(DynamicFeesMaxGasPerSecondKey, uint64(builder.LocalDynamicFeeConfig.MaxPerSecond), "Rate at which Gas is stored for future use")
fs.Uint64(DynamicFeesTargetGasPerSecondKey, uint64(builder.LocalDynamicFeeConfig.TargetPerSecond), "Target rate of Gas usage")
fs.Uint64(DynamicFeesMinGasPriceKey, uint64(builder.LocalDynamicFeeConfig.MinPrice), "Minimum Gas price")
fs.Uint64(DynamicFeesExcessConversionConstantKey, uint64(builder.LocalDynamicFeeConfig.ExcessConversionConstant), "Constant to convert excess Gas to the Gas price")
// Static fees:
fs.Uint64(TxFeeKey, genesis.LocalParams.TxFee, "Transaction fee, in nLUX")
fs.Uint64(CreateAssetTxFeeKey, genesis.LocalParams.CreateAssetTxFee, "Transaction fee, in nLUX, for transactions that create new assets")
// Database
fs.String(DBTypeKey, "zapdb", "Default database type to use for all chains. Must be one of {zapdb, pebbledb, memdb}")
fs.Bool(DBReadOnlyKey, false, "If true, database writes are to memory and never persisted. May still initialize database directory/files on disk if they don't exist")
fs.String(DBPathKey, defaultDBDir, "Path to database directory")
fs.String(DBConfigFileKey, "", fmt.Sprintf("Path to database config file. Ignored if %s is specified", DBConfigContentKey))
fs.String(DBConfigContentKey, "", "Specifies base64 encoded database config content")
// Per-chain database configuration
fs.String(PChainDBTypeKey, "", "Database type for P-Chain. If not specified, uses default db-type")
fs.String(XChainDBTypeKey, "", "Database type for X-Chain. If not specified, uses default db-type")
fs.String(CChainDBTypeKey, "", "Database type for C-Chain. If not specified, uses default db-type")
// Logging
fs.String(LogsDirKey, defaultLogDir, "Logging directory for Lux")
fs.String(LogLevelKey, "info", "The log level. Should be one of {verbo, debug, trace, info, warn, error, fatal, off}")
fs.String(LogDisplayLevelKey, "", "The log display level. If left blank, will inherit the value of log-level. Otherwise, should be one of {verbo, debug, trace, info, warn, error, fatal, off}")
// fs.String(LogFormatKey, "auto", "Format to use for log output. Should be one of {auto, json, terminal}")
fs.Uint(LogRotaterMaxSizeKey, 8, "The maximum file size in megabytes of the log file before it gets rotated.")
fs.Uint(LogRotaterMaxFilesKey, 7, "The maximum number of old log files to retain. 0 means retain all old log files.")
fs.Uint(LogRotaterMaxAgeKey, 0, "The maximum number of days to retain old log files based on the timestamp encoded in their filename. 0 means retain all old log files.")
fs.Bool(LogRotaterCompressEnabledKey, false, "Enables the compression of rotated log files through gzip.")
fs.Bool(LogDisableDisplayPluginLogsKey, false, "Disables displaying plugin logs in stdout.")
// Peer List Gossip
fs.Uint(NetworkPeerListNumValidatorIPsKey, constants.DefaultNetworkPeerListNumValidatorIPs, "Number of validator IPs to gossip to other nodes")
fs.Duration(NetworkPeerListPullGossipFreqKey, constants.DefaultNetworkPeerListPullGossipFreq, "Frequency to request peers from other nodes")
fs.Duration(NetworkPeerListBloomResetFreqKey, constants.DefaultNetworkPeerListBloomResetFreq, "Frequency to recalculate the bloom filter used to request new peers from other nodes")
// Public IP Resolution
fs.String(PublicIPKey, "", "Public IP of this node for P2P communication")
fs.Duration(PublicIPResolutionFreqKey, 5*time.Minute, "Frequency at which this node resolves/updates its public IP and renew NAT mappings, if applicable")
fs.String(PublicIPResolutionServiceKey, "", fmt.Sprintf("Only acceptable values are %q, %q or %q. When provided, the node will use that service to periodically resolve/update its public IP", dynamicip.OpenDNSName, dynamicip.IFConfigCoName, dynamicip.IFConfigMeName))
// Inbound Connection Throttling
fs.Duration(NetworkInboundConnUpgradeThrottlerCooldownKey, constants.DefaultInboundConnUpgradeThrottlerCooldown, "Upgrade an inbound connection from a given IP at most once per this duration. If 0, don't rate-limit inbound connection upgrades")
fs.Float64(NetworkInboundThrottlerMaxConnsPerSecKey, constants.DefaultInboundThrottlerMaxConnsPerSec, "Max number of inbound connections to accept (from all peers) per second")
// Outbound Connection Throttling
fs.Uint(NetworkOutboundConnectionThrottlingRpsKey, constants.DefaultOutboundConnectionThrottlingRps, "Make at most this number of outgoing peer connection attempts per second")
fs.Duration(NetworkOutboundConnectionTimeoutKey, constants.DefaultOutboundConnectionTimeout, "Timeout when dialing a peer")
// Timeouts
fs.Duration(NetworkInitialTimeoutKey, constants.DefaultNetworkInitialTimeout, "Initial timeout value of the adaptive timeout manager")
fs.Duration(NetworkMinimumTimeoutKey, constants.DefaultNetworkMinimumTimeout, "Minimum timeout value of the adaptive timeout manager")
fs.Duration(NetworkMaximumTimeoutKey, constants.DefaultNetworkMaximumTimeout, "Maximum timeout value of the adaptive timeout manager")
fs.Duration(NetworkMaximumInboundTimeoutKey, constants.DefaultNetworkMaximumInboundTimeout, "Maximum timeout value of an inbound message. Defines duration within which an incoming message must be fulfilled. Incoming messages containing deadline higher than this value will be overridden with this value.")
fs.Duration(NetworkTimeoutHalflifeKey, constants.DefaultNetworkTimeoutHalflife, "Halflife of average network response time. Higher value --> network timeout is less volatile. Can't be 0")
fs.Float64(NetworkTimeoutCoefficientKey, constants.DefaultNetworkTimeoutCoefficient, "Multiplied by average network response time to get the network timeout. Must be >= 1")
fs.Duration(NetworkReadHandshakeTimeoutKey, constants.DefaultNetworkReadHandshakeTimeout, "Timeout value for reading handshake messages")
fs.Duration(NetworkPingTimeoutKey, constants.DefaultPingPongTimeout, "Timeout value for Ping-Pong with a peer")
fs.Duration(NetworkPingFrequencyKey, constants.DefaultPingFrequency, "Frequency of pinging other peers")
fs.Duration(NetworkNoIngressValidatorConnectionsGracePeriodKey, constants.DefaultNoIngressValidatorConnectionGracePeriod, "Time after which nodes are expected to be connected to us if we are a primary network validator, otherwise a health check fails")
fs.String(NetworkCompressionTypeKey, constants.DefaultNetworkCompressionType.String(), fmt.Sprintf("Compression type for outbound messages. Must be one of [%s, %s]", compression.TypeZstd, compression.TypeNone))
fs.Duration(NetworkMaxClockDifferenceKey, constants.DefaultNetworkMaxClockDifference, "Max allowed clock difference value between this node and peers")
// Allow private IPs by default for local development and testing.
// Set to false to prohibit for production deployments.
fs.Bool(NetworkAllowPrivateIPsKey, true, "Allows the node to initiate outbound connection attempts to peers with private IPs. Default is true. Set to false to prohibit for security-sensitive production deployments")
fs.Bool(NetworkRequireValidatorToConnectKey, constants.DefaultNetworkRequireValidatorToConnect, "If true, this node will only maintain a connection with another node if this node is a validator, the other node is a validator, or the other node is a beacon")
fs.Uint(NetworkPeerReadBufferSizeKey, constants.DefaultNetworkPeerReadBufferSize, "Size, in bytes, of the buffer that we read peer messages into (there is one buffer per peer)")
fs.Uint(NetworkPeerWriteBufferSizeKey, constants.DefaultNetworkPeerWriteBufferSize, "Size, in bytes, of the buffer that we write peer messages into (there is one buffer per peer)")
fs.Bool(NetworkTCPProxyEnabledKey, constants.DefaultNetworkTCPProxyEnabled, "Require all P2P connections to be initiated with a TCP proxy header")
// The PROXY protocol specification recommends setting this value to be at
// least 3 seconds to cover a TCP retransmit.
// Ref: https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt
// Specifying a timeout of 0 will actually result in a timeout of 200ms, but
// a timeout of 0 should generally not be provided.
fs.Duration(NetworkTCPProxyReadTimeoutKey, constants.DefaultNetworkTCPProxyReadTimeout, "Maximum duration to wait for a TCP proxy header")
fs.String(NetworkTLSKeyLogFileKey, "", "TLS key log file path. Should only be specified for debugging")
// Benchlist
fs.Int(BenchlistFailThresholdKey, constants.DefaultBenchlistFailThreshold, "Number of consecutive failed queries before benchlisting a node")
fs.Duration(BenchlistDurationKey, constants.DefaultBenchlistDuration, "Max amount of time a peer is benchlisted after surpassing the threshold")
fs.Duration(BenchlistMinFailingDurationKey, constants.DefaultBenchlistMinFailingDuration, "Minimum amount of time messages to a peer must be failing before the peer is benched")
// Router
fs.Uint(ConsensusAppConcurrencyKey, constants.DefaultConsensusAppConcurrency, "Maximum number of goroutines to use when handling App messages on a chain")
fs.Duration(ConsensusShutdownTimeoutKey, constants.DefaultConsensusShutdownTimeout, "Timeout before killing an unresponsive chain")
fs.Duration(ConsensusFrontierPollFrequencyKey, constants.DefaultFrontierPollFrequency, "Frequency of polling for new consensus frontiers")
// Inbound Throttling
fs.Uint64(InboundThrottlerAtLargeAllocSizeKey, constants.DefaultInboundThrottlerAtLargeAllocSize, "Size, in bytes, of at-large byte allocation in inbound message throttler")
fs.Uint64(InboundThrottlerVdrAllocSizeKey, constants.DefaultInboundThrottlerVdrAllocSize, "Size, in bytes, of validator byte allocation in inbound message throttler")
fs.Uint64(InboundThrottlerNodeMaxAtLargeBytesKey, constants.DefaultInboundThrottlerNodeMaxAtLargeBytes, "Max number of bytes a node can take from the inbound message throttler's at-large allocation. Must be at least the max message size")
fs.Uint64(InboundThrottlerMaxProcessingMsgsPerNodeKey, constants.DefaultInboundThrottlerMaxProcessingMsgsPerNode, "Max number of messages currently processing from a given node")
fs.Uint64(InboundThrottlerBandwidthRefillRateKey, constants.DefaultInboundThrottlerBandwidthRefillRate, "Max average inbound bandwidth usage of a peer, in bytes per second. See BandwidthThrottler")
fs.Uint64(InboundThrottlerBandwidthMaxBurstSizeKey, constants.DefaultInboundThrottlerBandwidthMaxBurstSize, "Max inbound bandwidth a node can use at once. Must be at least the max message size. See BandwidthThrottler")
fs.Duration(InboundThrottlerCPUMaxRecheckDelayKey, constants.DefaultInboundThrottlerCPUMaxRecheckDelay, "In the CPU-based network throttler, check at least this often whether the node's CPU usage has fallen to an acceptable level")
fs.Duration(InboundThrottlerDiskMaxRecheckDelayKey, constants.DefaultInboundThrottlerDiskMaxRecheckDelay, "In the disk-based network throttler, check at least this often whether the node's disk usage has fallen to an acceptable level")
// Outbound Throttling
fs.Uint64(OutboundThrottlerAtLargeAllocSizeKey, constants.DefaultOutboundThrottlerAtLargeAllocSize, "Size, in bytes, of at-large byte allocation in outbound message throttler")
fs.Uint64(OutboundThrottlerVdrAllocSizeKey, constants.DefaultOutboundThrottlerVdrAllocSize, "Size, in bytes, of validator byte allocation in outbound message throttler")
fs.Uint64(OutboundThrottlerNodeMaxAtLargeBytesKey, constants.DefaultOutboundThrottlerNodeMaxAtLargeBytes, "Max number of bytes a node can take from the outbound message throttler's at-large allocation. Must be at least the max message size")
// HTTP APIs
fs.String(HTTPHostKey, "127.0.0.1", "Address of the HTTP server. If the address is empty or a literal unspecified IP address, the server will bind on all available unicast and anycast IP addresses of the local system")
fs.Uint(HTTPPortKey, DefaultHTTPPort, "Port of the HTTP server. If the port is 0 a port number is automatically chosen")
fs.Bool(HTTPSEnabledKey, false, "Upgrade the HTTP server to HTTPs")
fs.String(HTTPSKeyFileKey, "", fmt.Sprintf("TLS private key file for the HTTPs server. Ignored if %s is specified", HTTPSKeyContentKey))
fs.String(HTTPSKeyContentKey, "", "Specifies base64 encoded TLS private key for the HTTPs server")
fs.String(HTTPSCertFileKey, "", fmt.Sprintf("TLS certificate file for the HTTPs server. Ignored if %s is specified", HTTPSCertContentKey))
fs.String(HTTPSCertContentKey, "", "Specifies base64 encoded TLS certificate for the HTTPs server")
fs.String(HTTPAllowedOrigins, "*", "Origins to allow on the HTTP port. Defaults to * which allows all origins. Example: https://*.lux.network https://*.lux-test.network")
fs.StringSlice(HTTPAllowedHostsKey, []string{"localhost"}, "List of acceptable host names in API requests. Provide the wildcard ('*') to accept requests from all hosts. API requests where the Host field is empty or an IP address will always be accepted. An API call whose HTTP Host field isn't acceptable will receive a 403 error code")
fs.Duration(HTTPShutdownWaitKey, 0, "Duration to wait after receiving SIGTERM or SIGINT before initiating shutdown. The /health endpoint will return unhealthy during this duration")
fs.Duration(HTTPShutdownTimeoutKey, 10*time.Second, "Maximum duration to wait for existing connections to complete during node shutdown")
fs.Duration(HTTPReadTimeoutKey, 3600*time.Second, "Maximum duration for reading the entire request, including the body. A zero or negative value means there will be no timeout")
fs.Duration(HTTPReadHeaderTimeoutKey, 30*time.Second, fmt.Sprintf("Maximum duration to read request headers. The connection's read deadline is reset after reading the headers. If %s is zero, the value of %s is used. If both are zero, there is no timeout.", HTTPReadHeaderTimeoutKey, HTTPReadTimeoutKey))
fs.Duration(HTTPWriteTimeoutKey, 3600*time.Second, "Maximum duration before timing out writes of the response. It is reset whenever a new request's header is read. A zero or negative value means there will be no timeout.")
fs.Duration(HTTPIdleTimeoutKey, 120*time.Second, fmt.Sprintf("Maximum duration to wait for the next request when keep-alives are enabled. If %s is zero, the value of %s is used. If both are zero, there is no timeout.", HTTPIdleTimeoutKey, HTTPReadTimeoutKey))
// Enable/Disable APIs
fs.Bool(AdminAPIEnabledKey, false, "If true, this node exposes the Admin API")
fs.Bool(InfoAPIEnabledKey, true, "If true, this node exposes the Info API")
fs.Bool(KeystoreAPIEnabledKey, false, "If true, this node exposes the Keystore API")
fs.Bool(MetricsAPIEnabledKey, true, "If true, this node exposes the Metrics API")
fs.Bool(HealthAPIEnabledKey, true, "If true, this node exposes the Health API")
// Health Checks
fs.Duration(HealthCheckFreqKey, 30*time.Second, "Time between health checks")
fs.Duration(HealthCheckAveragerHalflifeKey, constants.DefaultHealthCheckAveragerHalflife, "Halflife of averager when calculating a running average in a health check")
// Network Layer Health
fs.Duration(NetworkHealthMaxTimeSinceMsgSentKey, constants.DefaultNetworkHealthMaxTimeSinceMsgSent, "Network layer returns unhealthy if haven't sent a message for at least this much time")
fs.Duration(NetworkHealthMaxTimeSinceMsgReceivedKey, constants.DefaultNetworkHealthMaxTimeSinceMsgReceived, "Network layer returns unhealthy if haven't received a message for at least this much time")
fs.Float64(NetworkHealthMaxPortionSendQueueFillKey, constants.DefaultNetworkHealthMaxPortionSendQueueFill, "Network layer returns unhealthy if more than this portion of the pending send queue is full")
fs.Uint(NetworkHealthMinPeersKey, constants.DefaultNetworkHealthMinPeers, "Network layer returns unhealthy if connected to less than this many peers")
fs.Float64(NetworkHealthMaxSendFailRateKey, constants.DefaultNetworkHealthMaxSendFailRate, "Network layer reports unhealthy if more than this portion of attempted message sends fail")
// Router Health
fs.Float64(RouterHealthMaxDropRateKey, 1, "Node reports unhealthy if the router drops more than this portion of messages")
fs.Uint(RouterHealthMaxOutstandingRequestsKey, 1024, "Node reports unhealthy if there are more than this many outstanding consensus requests (Get, PullQuery, etc.) over all chains")
fs.Duration(NetworkHealthMaxOutstandingDurationKey, 5*time.Minute, "Node reports unhealthy if there has been a request outstanding for this duration")
// Staking
fs.String(StakingHostKey, "", "Address of the consensus server. If the address is empty or a literal unspecified IP address, the server will bind on all available unicast and anycast IP addresses of the local system") // Bind to all interfaces by default.
fs.Uint(StakingPortKey, DefaultStakingPort, "Port of the consensus server. If the port is 0 a port number is automatically chosen")
fs.Bool(StakingEphemeralCertEnabledKey, false, "If true, the node uses an ephemeral staking TLS key and certificate, and has an ephemeral node ID")
fs.String(StakingTLSKeyPathKey, defaultStakingTLSKeyPath, fmt.Sprintf("Path to the TLS private key for staking. Ignored if %s is specified", StakingTLSKeyContentKey))
fs.String(StakingTLSKeyContentKey, "", "Specifies base64 encoded TLS private key for staking")
fs.String(StakingCertPathKey, defaultStakingCertPath, fmt.Sprintf("Path to the TLS certificate for staking. Ignored if %s is specified", StakingCertContentKey))
fs.String(StakingCertContentKey, "", "Specifies base64 encoded TLS certificate for staking")
fs.Bool(StakingEphemeralSignerEnabledKey, false, "If true, the node uses an ephemeral staking signer key")
fs.String(StakingSignerKeyPathKey, defaultStakingSignerKeyPath, fmt.Sprintf("Path to the signer private key for staking. Ignored if %s is specified", StakingSignerKeyContentKey))
fs.String(StakingSignerKeyContentKey, "", "Specifies base64 encoded signer private key for staking")
fs.String(StakingKMSEndpointKey, "", "KMS endpoint for staking key retrieval (e.g., https://kms.dev.lux.network)")
fs.String(StakingKMSSecretPathKey, "", "KMS secret path for staking keys (e.g., /staking/liquid-devnet/node-0)")
fs.String(StakingKMSTokenKey, "", "KMS auth token for staking key retrieval")
fs.Bool(SybilProtectionEnabledKey, true, "Enables sybil protection. If enabled, Network TLS is required")
fs.Uint64(SybilProtectionDisabledWeightKey, 100, "Weight to provide to each peer when sybil protection is disabled")
fs.Bool(PartialSyncPrimaryNetworkKey, false, "Only sync the P-chain on the Primary Network. If the node is a Primary Network validator, it will report unhealthy")
// Uptime Requirement
fs.Float64(UptimeRequirementKey, genesis.LocalParams.UptimeRequirement, "Fraction of time a validator must be online to receive rewards")
// Minimum Stake required to validate the Primary Network
fs.Uint64(MinValidatorStakeKey, genesis.LocalParams.MinValidatorStake, "Minimum stake, in nLUX, required to validate the primary network")
// Maximum Stake that can be staked and delegated to a validator on the Primary Network
fs.Uint64(MaxValidatorStakeKey, genesis.LocalParams.MaxValidatorStake, "Maximum stake, in nLUX, that can be placed on a validator on the primary network")
// Minimum Stake that can be delegated on the Primary Network
fs.Uint64(MinDelegatorStakeKey, genesis.LocalParams.MinDelegatorStake, "Minimum stake, in nLUX, that can be delegated on the primary network")
fs.Uint64(MinDelegatorFeeKey, uint64(genesis.LocalParams.MinDelegationFee), "Minimum delegation fee, in the range [0, 1000000], that can be charged for delegation on the primary network")
// Minimum Stake Duration
fs.Duration(MinStakeDurationKey, time.Duration(genesis.LocalParams.MinStakeDuration)*time.Second, "Minimum staking duration")
// Maximum Stake Duration
fs.Duration(MaxStakeDurationKey, time.Duration(genesis.LocalParams.MaxStakeDuration)*time.Second, "Maximum staking duration")
// Stake Reward Configs
fs.Uint64(StakeMaxConsumptionRateKey, genesis.LocalParams.RewardConfig.MaxConsumptionRate, "Maximum consumption rate of the remaining tokens to mint in the staking function")
fs.Uint64(StakeMinConsumptionRateKey, genesis.LocalParams.RewardConfig.MinConsumptionRate, "Minimum consumption rate of the remaining tokens to mint in the staking function")
fs.Duration(StakeMintingPeriodKey, time.Duration(genesis.LocalParams.RewardConfig.MintingPeriod)*time.Second, "Consumption period of the staking function")
fs.Uint64(StakeSupplyCapKey, genesis.LocalParams.RewardConfig.SupplyCap, "Supply cap of the staking function")
// Chain tracking
fs.String(TrackChainsKey, "", "Comma-separated list of chain IDs to track. A node tracking chains will sync those chains and track validator uptimes. Use --track-all-chains for development networks")
fs.Bool(TrackAllChainsKey, false, "If true, track all chains automatically. Useful for development and testing networks where you want to sync all deployed chains without specifying each one")
// State syncing
fs.String(StateSyncIPsKey, "", "Comma separated list of state sync peer ips to connect to. Example: 127.0.0.1:9630,127.0.0.1:9631")
fs.String(StateSyncIDsKey, "", "Comma separated list of state sync peer ids to connect to. Example: NodeID-JR4dVmy6ffUGAKCBDkyCbeZbyHQBeDsET,NodeID-8CrVPQZ4VSqgL8zTdvL14G8HqAfrBr4z")
// Bootstrapping
fs.String(BootstrapNodesKey, "", "Comma separated list of bootstrap node endpoints. NodeID is discovered from the peer's staking certificate during TLS handshake. Example: 0.mainnet.luxno.de:9631,1.mainnet.luxno.de:9631")
fs.String(BootstrapIPsKey, "", "[Deprecated: use --bootstrap-nodes] Comma separated list of bootstrap peer ips to connect to. Example: 127.0.0.1:9630,127.0.0.1:9631")
fs.String(BootstrapIDsKey, "", "[Deprecated: use --bootstrap-nodes] Comma separated list of bootstrap peer ids to connect to. Example: NodeID-JR4dVmy6ffUGAKCBDkyCbeZbyHQBeDsET,NodeID-8CrVPQZ4VSqgL8zTdvL14G8HqAfrBr4z")
fs.Bool(SkipBootstrapKey, false, "If true, skip the bootstrapping phase and start processing immediately")
fs.Bool(EnableAutominingKey, false, "If true, enable automining in POA mode")
fs.Duration(BootstrapBeaconConnectionTimeoutKey, time.Minute, "Timeout before emitting a warn log when connecting to bootstrapping beacons")
fs.Duration(BootstrapMaxTimeGetAncestorsKey, 50*time.Millisecond, "Max Time to spend fetching a container and its ancestors when responding to a GetAncestors")
fs.Uint(BootstrapAncestorsMaxContainersSentKey, 2000, "Max number of containers in an Ancestors message sent by this node")
fs.Uint(BootstrapAncestorsMaxContainersReceivedKey, 2000, "This node reads at most this many containers from an incoming Ancestors message")
// Consensus - use defaults from consensus config package
defaultParams := consensusconfig.DefaultParams()
fs.Int(ConsensusSampleSizeKey, defaultParams.K, "Number of nodes to query for each network poll")
fs.Int(ConsensusQuorumSizeKey, defaultParams.AlphaConfidence, "Threshold of nodes required to update this node's preference and increase its confidence in a network poll")
fs.Int(ConsensusPreferenceQuorumSizeKey, defaultParams.AlphaPreference, fmt.Sprintf("Threshold of nodes required to update this node's preference in a network poll. Ignored if %s is provided", ConsensusQuorumSizeKey))
fs.Int(ConsensusConfidenceQuorumSizeKey, defaultParams.AlphaConfidence, fmt.Sprintf("Threshold of nodes required to increase this node's confidence in a network poll. Ignored if %s is provided", ConsensusQuorumSizeKey))
fs.Int(ConsensusCommitThresholdKey, int(defaultParams.Beta), "Beta value to use for consensus")
fs.Int(ConsensusConcurrentRepollsKey, defaultParams.ConcurrentRepolls, "Minimum number of concurrent polls for finalizing consensus")
fs.Int(ConsensusOptimalProcessingKey, defaultParams.OptimalProcessing, "Optimal number of processing containers in consensus")
fs.Int(ConsensusMaxProcessingKey, defaultParams.MaxOutstandingItems, "Maximum number of processing items to be considered healthy")
fs.Duration(ConsensusMaxTimeProcessingKey, defaultParams.MaxItemProcessingTime, "Maximum amount of time an item should be processing and still be healthy")
// ProposerVM
fs.Bool(ProposerVMUseCurrentHeightKey, false, "Have the ProposerVM always report the last accepted P-chain block height")
fs.Duration(ProposerVMMinBlockDelayKey, proposervm.DefaultMinBlockDelay, "Minimum delay to enforce when building a chain++ block for the primary network chains and the default minimum delay for chains")
// Metrics
fs.Bool(MeterVMsEnabledKey, true, "Enable Meter VMs to track VM performance with more granularity")
fs.Duration(UptimeMetricFreqKey, 30*time.Second, "Frequency of renewing this node's average uptime metric")
// Indexer
fs.Bool(IndexEnabledKey, false, "If true, index all accepted containers and transactions and expose them via an API")
fs.Bool(IndexAllowIncompleteKey, false, "If true, allow running the node in such a way that could cause an index to miss transactions. Ignored if index is disabled")
// Config Directories
fs.String(ChainConfigDirKey, defaultChainConfigDir, fmt.Sprintf("Chain specific configurations parent directory. Ignored if %s is specified", ChainConfigContentKey))
fs.String(ChainConfigContentKey, "", "Specifies base64 encoded chains configurations")
fs.String(NetConfigDirKey, defaultNetConfigDir, fmt.Sprintf("Net specific configurations parent directory. Ignored if %s is specified", NetConfigContentKey))
fs.String(NetConfigContentKey, "", "Specifies base64 encoded chains configurations")
// Chain Data Directory
fs.String(ChainDataDirKey, defaultChainDataDir, "Chain specific data directory")
fs.String(ImportChainDataKey, "", "Path to import blockchain data from another chain into C-Chain")
// Profiles
fs.String(ProfileDirKey, defaultProfileDir, "Path to the profile directory")
fs.Bool(ProfileContinuousEnabledKey, false, "Whether the app should continuously produce performance profiles")
fs.Duration(ProfileContinuousFreqKey, 15*time.Minute, "How frequently to rotate performance profiles")
fs.Int(ProfileContinuousMaxFilesKey, 5, "Maximum number of historical profiles to keep")
// Aliasing
fs.String(VMAliasesFileKey, defaultVMAliasFilePath, fmt.Sprintf("Specifies a JSON file that maps vmIDs with custom aliases. Ignored if %s is specified", VMAliasesContentKey))
fs.String(VMAliasesContentKey, "", "Specifies base64 encoded maps vmIDs with custom aliases")
fs.String(ChainAliasesFileKey, defaultChainAliasFilePath, fmt.Sprintf("Specifies a JSON file that maps blockchainIDs with custom aliases. Ignored if %s is specified", ChainConfigContentKey))
fs.String(ChainAliasesContentKey, "", "Specifies base64 encoded map from blockchainID to custom aliases")
// Delays
fs.Duration(NetworkInitialReconnectDelayKey, constants.DefaultNetworkInitialReconnectDelay, "Initial delay duration must be waited before attempting to reconnect a peer")
fs.Duration(NetworkMaxReconnectDelayKey, constants.DefaultNetworkMaxReconnectDelay, "Maximum delay duration must be waited before attempting to reconnect a peer")
// System resource trackers
fs.Duration(SystemTrackerFrequencyKey, 500*time.Millisecond, "Frequency to check the real system usage of tracked processes. More frequent checks --> usage metrics are more accurate, but more expensive to track")
fs.Duration(SystemTrackerProcessingHalflifeKey, 15*time.Second, "Halflife to use for the processing requests tracker. Larger halflife --> usage metrics change more slowly")
fs.Duration(SystemTrackerCPUHalflifeKey, 15*time.Second, "Halflife to use for the cpu tracker. Larger halflife --> cpu usage metrics change more slowly")
fs.Duration(SystemTrackerDiskHalflifeKey, time.Minute, "Halflife to use for the disk tracker. Larger halflife --> disk usage metrics change more slowly")
fs.Uint64(SystemTrackerRequiredAvailableDiskSpaceKey, constants.GiB/2, "Minimum number of available bytes on disk, under which the node will shutdown.")
fs.Uint64(SystemTrackerWarningThresholdAvailableDiskSpaceKey, constants.GiB, fmt.Sprintf("Warning threshold for the number of available bytes on disk, under which the node will be considered unhealthy. Must be >= [%s]", SystemTrackerRequiredAvailableDiskSpaceKey))
// CPU management
fs.Float64(CPUVdrAllocKey, float64(runtime.NumCPU()), "Maximum number of CPUs to allocate for use by validators. Value should be in range [0, total core count]")
fs.Float64(CPUMaxNonVdrUsageKey, .8*float64(runtime.NumCPU()), "Number of CPUs that if fully utilized, will rate limit all non-validators. Value should be in range [0, total core count]")
fs.Float64(CPUMaxNonVdrNodeUsageKey, float64(runtime.NumCPU())/8, "Maximum number of CPUs that a non-validator can utilize. Value should be in range [0, total core count]")
// Disk management
fs.Float64(DiskVdrAllocKey, 1000*constants.GiB, "Maximum number of disk reads/writes per second to allocate for use by validators. Must be > 0")
fs.Float64(DiskMaxNonVdrUsageKey, 1000*constants.GiB, "Number of disk reads/writes per second that, if fully utilized, will rate limit all non-validators. Must be >= 0")
fs.Float64(DiskMaxNonVdrNodeUsageKey, 1000*constants.GiB, "Maximum number of disk reads/writes per second that a non-validator can utilize. Must be >= 0")
// Opentelemetry tracing
fs.String(TracingExporterTypeKey, trace.Disabled.String(), fmt.Sprintf("Type of exporter to use for tracing. Options are [%s, %s, %s]", trace.Disabled, trace.GRPC, trace.HTTP))
fs.String(TracingEndpointKey, "", "The endpoint to send trace data to. If unspecified, the default endpoint will be used; depending on the exporter type")
fs.Bool(TracingInsecureKey, true, "If true, don't use TLS when sending trace data")
fs.Float64(TracingSampleRateKey, 0.1, "The fraction of traces to sample. If >= 1, always sample. If <= 0, never sample")
fs.StringToString(TracingHeadersKey, map[string]string{}, "The headers to provide the trace indexer")
fs.String(ProcessContextFileKey, defaultProcessContextPath, "The path to write process context to (including PID, API URI, and staking address).")
// POA Mode
fs.Bool(POAModeEnabledKey, false, "Enable Proof of Authority mode for chains")
fs.Bool(POASingleNodeModeKey, false, "Enable single node POA mode (no consensus required)")
fs.Duration(POAMinBlockTimeKey, 1*time.Second, "Minimum time between blocks in POA mode")
fs.StringSlice(POAAuthorizedNodesKey, nil, "List of authorized nodes for POA mode")
// GPU Acceleration
fs.Bool(GPUEnabledKey, true, "Enable GPU acceleration for cryptographic operations (auto-detects Metal on macOS, CUDA on Linux)")
fs.String(GPUBackendKey, "auto", "GPU backend to use. Options: auto (auto-detect), metal (macOS), cuda (Linux), cpu (fallback)")
fs.Int(GPUDeviceKey, 0, "GPU device index to use when multiple GPUs are available")
fs.String(GPULogLevelKey, "warn", "GPU subsystem log level. Options: debug, info, warn, error")
// Force flags
fs.Bool(ForceIgnoreChecksumKey, false, "Force ignore checksum validation errors (use with caution)")
}
// BuildFlagSet returns a complete set of flags for node
func BuildFlagSet() *pflag.FlagSet {
fs := pflag.NewFlagSet(constants.AppName, pflag.ContinueOnError)
addProcessFlags(fs)
addNodeFlags(fs)
return fs
}
// getExpandedArg gets the string in viper corresponding to [key] and expands
// any variables using the OS env. If the [LuxNodeDataDirVar] var is used,
// we expand the value of the variable with the string in viper corresponding to
// [DataDirKey].
func getExpandedArg(v *viper.Viper, key string) string {
return os.Expand(
v.GetString(key),
func(strVar string) string {
if strVar == LuxNodeDataDirVar {
return os.ExpandEnv(v.GetString(DataDirKey))
}
return os.Getenv(strVar)
},
)
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"flag"
"path/filepath"
)
// ZapDB flags for C-Chain
type ZapDBConfig struct {
Enable bool
DataDir string
EnableAncient bool
AncientDir string
ReadOnly bool
SharedAncient bool
FreezeThreshold uint64
}
// AddZapDBFlags adds ZapDB-related flags to the flag set
func AddZapDBFlags(fs *flag.FlagSet) *ZapDBConfig {
config := &ZapDBConfig{}
fs.BoolVar(&config.Enable, "cchain-badger", false,
"Enable ZapDB for C-Chain instead of default database")
fs.StringVar(&config.DataDir, "cchain-badger-dir", "",
"ZapDB data directory (default: <datadir>/cchain-badger)")
fs.BoolVar(&config.EnableAncient, "cchain-ancient", false,
"Enable ancient store for historical blockchain data")
fs.StringVar(&config.AncientDir, "cchain-ancient-dir", "",
"Ancient store directory (default: <badger-dir>/ancient)")
fs.BoolVar(&config.ReadOnly, "cchain-ancient-readonly", false,
"Open ancient store in read-only mode (allows sharing)")
fs.BoolVar(&config.SharedAncient, "cchain-ancient-shared", false,
"Enable shared access to ancient store (requires readonly)")
fs.Uint64Var(&config.FreezeThreshold, "cchain-freeze-threshold", 90000,
"Number of recent blocks to keep in main DB before freezing to ancient")
return config
}
// Validate validates the ZapDB configuration
func (c *ZapDBConfig) Validate(dataDir string) error {
// Set defaults
if c.Enable && c.DataDir == "" {
c.DataDir = filepath.Join(dataDir, "cchain-badger")
}
if c.EnableAncient && c.AncientDir == "" {
c.AncientDir = filepath.Join(c.DataDir, "ancient")
}
// Shared ancient requires read-only
if c.SharedAncient && !c.ReadOnly {
c.ReadOnly = true
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"fmt"
"runtime"
"sync"
)
// GPUConfig holds GPU acceleration configuration.
type GPUConfig struct {
// Enabled controls whether GPU acceleration is used
Enabled bool
// Backend specifies which GPU backend to use: "auto", "metal", "cuda", "cpu"
Backend string
// DeviceIndex specifies which GPU device to use when multiple are available
DeviceIndex int
// LogLevel sets the GPU subsystem log level: "debug", "info", "warn", "error"
LogLevel string
}
// DefaultGPUConfig returns the default GPU configuration.
func DefaultGPUConfig() GPUConfig {
return GPUConfig{
Enabled: true,
Backend: "auto",
DeviceIndex: 0,
LogLevel: "warn",
}
}
// Validate checks that the GPU configuration is valid.
func (c GPUConfig) Validate() error {
switch c.Backend {
case "auto", "metal", "cuda", "cpu":
// Valid backends
default:
return fmt.Errorf("invalid GPU backend %q: must be auto, metal, cuda, or cpu", c.Backend)
}
// Validate backend is supported on current platform
if c.Backend == "metal" && runtime.GOOS != "darwin" {
return fmt.Errorf("metal backend is only supported on macOS")
}
if c.Backend == "cuda" && runtime.GOOS == "darwin" {
return fmt.Errorf("cuda backend is not supported on macOS")
}
if c.DeviceIndex < 0 {
return fmt.Errorf("GPU device index must be non-negative")
}
switch c.LogLevel {
case "debug", "info", "warn", "error":
// Valid log levels
default:
return fmt.Errorf("invalid GPU log level %q: must be debug, info, warn, or error", c.LogLevel)
}
return nil
}
// ResolveBackend returns the actual backend to use based on configuration.
// If Backend is "auto", it detects the best available backend.
func (c GPUConfig) ResolveBackend() string {
if !c.Enabled {
return "cpu"
}
if c.Backend != "auto" {
return c.Backend
}
// Auto-detect based on platform
switch runtime.GOOS {
case "darwin":
return "metal"
case "linux":
return "cuda"
default:
return "cpu"
}
}
// Global GPU configuration (set during node initialization)
var (
globalGPUConfig GPUConfig
globalGPUConfigOnce sync.Once
globalGPUConfigSet bool
)
// SetGlobalGPUConfig sets the global GPU configuration.
// This should be called once during node initialization before any GPU accelerators are created.
func SetGlobalGPUConfig(cfg GPUConfig) error {
var setErr error
globalGPUConfigOnce.Do(func() {
if err := cfg.Validate(); err != nil {
setErr = err
return
}
globalGPUConfig = cfg
globalGPUConfigSet = true
})
return setErr
}
// GetGlobalGPUConfig returns the global GPU configuration.
// If not set, returns the default configuration.
func GetGlobalGPUConfig() GPUConfig {
if !globalGPUConfigSet {
return DefaultGPUConfig()
}
return globalGPUConfig
}
// IsGPUEnabled returns whether GPU acceleration is enabled globally.
func IsGPUEnabled() bool {
return GetGlobalGPUConfig().Enabled
}
// GPUBackend returns the configured GPU backend.
func GPUBackend() string {
return GetGlobalGPUConfig().ResolveBackend()
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import "time"
// MainnetParameters contains mainnet consensus parameters
var MainnetParameters = struct {
K int
AlphaPreference int
AlphaConfidence int
Beta int
ConcurrentPolls int
OptimalProcessing int
MaxOutstandingItems int
MaxItemProcessingTime time.Duration
}{
K: 20,
AlphaPreference: 15,
AlphaConfidence: 15,
Beta: 20,
ConcurrentPolls: 4,
OptimalProcessing: 10,
MaxOutstandingItems: 1024,
MaxItemProcessingTime: 2 * time.Minute,
}
// RouterHealthConfig contains configuration for router health checks
type RouterHealthConfig struct {
MaxTimeSinceMsgReceived time.Duration
MaxTimeSinceMsgSent time.Duration
MaxPortionSendQueueFull float64
MinConnectedPeers uint
ReadTimeout time.Duration
WriteTimeout time.Duration
MaxSendFailRate float64
MaxDropRate float64
MaxOutstandingRequests int
MaxOutstandingDuration time.Duration
MaxRunTimeRequests time.Duration
MaxDropRateHalflife time.Duration
}
// BenchlistConfig contains configuration for benchlisting
type BenchlistConfig struct {
Deprecated bool
Duration time.Duration
MinFailingDuration time.Duration
Threshold int
MaxPortion float64
FailThreshold int
}
// PrismParameters contains prism protocol parameters
type PrismParameters struct {
NumParents int
NumNodes int
AlphaPreference int
AlphaConfidence int
K int
MaxOutstandingItems int
}
+268
View File
@@ -0,0 +1,268 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
// the HTTPWriteTimeoutKey was moved here so that it would not generate the
// false-positive linter error "G101: Potential hardcoded credentials" when running golangci-lint.
const HTTPWriteTimeoutKey = "http-write-timeout" // #nosec G101
const (
DataDirKey = "data-dir"
ConfigFileKey = "config-file"
ConfigContentKey = "config-file-content"
ConfigContentTypeKey = "config-file-content-type"
VersionKey = "version"
VersionJSONKey = "version-json"
GenesisFileKey = "genesis-file"
GenesisFileContentKey = "genesis-file-content"
GenesisRawBytesKey = "genesis-raw-bytes"
GenesisDBKey = "genesis-db"
GenesisDBTypeKey = "genesis-db-type"
GenesisBlockLimitKey = "genesis-block-limit"
AllowCustomGenesisKey = "allow-custom-genesis"
AllowGenesisUpdateKey = "allow-genesis-update"
UpgradeFileKey = "upgrade-file"
UpgradeFileContentKey = "upgrade-file-content"
NetworkNameKey = "network-id"
MainnetKey = "mainnet"
TestnetKey = "testnet"
LocalnetKey = "localnet"
DynamicFeesBandwidthWeightKey = "dynamic-fees-bandwidth-weight"
DynamicFeesDBReadWeightKey = "dynamic-fees-db-read-weight"
DynamicFeesDBWriteWeightKey = "dynamic-fees-db-write-weight"
DynamicFeesComputeWeightKey = "dynamic-fees-compute-weight"
DynamicFeesMaxGasCapacityKey = "dynamic-fees-max-gas-capacity"
DynamicFeesMaxGasPerSecondKey = "dynamic-fees-max-gas-per-second"
DynamicFeesTargetGasPerSecondKey = "dynamic-fees-target-gas-per-second"
DynamicFeesMinGasPriceKey = "dynamic-fees-min-gas-price"
DynamicFeesExcessConversionConstantKey = "dynamic-fees-excess-conversion-constant"
ValidatorFeesCapacityKey = "validator-fees-capacity"
ValidatorFeesTargetKey = "validator-fees-target"
ValidatorFeesMinPriceKey = "validator-fees-min-price"
ValidatorFeesExcessConversionConstantKey = "validator-fees-excess-conversion-constant"
TxFeeKey = "tx-fee"
CreateAssetTxFeeKey = "create-asset-tx-fee"
UptimeRequirementKey = "uptime-requirement"
MinValidatorStakeKey = "min-validator-stake"
MaxValidatorStakeKey = "max-validator-stake"
MinDelegatorStakeKey = "min-delegator-stake"
MinDelegatorFeeKey = "min-delegation-fee"
MinStakeDurationKey = "min-stake-duration"
MaxStakeDurationKey = "max-stake-duration"
StakeMaxConsumptionRateKey = "stake-max-consumption-rate"
StakeMinConsumptionRateKey = "stake-min-consumption-rate"
StakeMintingPeriodKey = "stake-minting-period"
StakeSupplyCapKey = "stake-supply-cap"
DBTypeKey = "db-type"
DBReadOnlyKey = "db-read-only"
DBPathKey = "db-dir"
DBConfigFileKey = "db-config-file"
DBConfigContentKey = "db-config-file-content"
PChainDBTypeKey = "p-chain-db-type"
XChainDBTypeKey = "x-chain-db-type"
CChainDBTypeKey = "c-chain-db-type"
PublicIPKey = "public-ip"
PublicIPResolutionFreqKey = "public-ip-resolution-frequency"
PublicIPResolutionServiceKey = "public-ip-resolution-service"
HTTPHostKey = "http-host"
HTTPPortKey = "http-port"
HTTPSEnabledKey = "http-tls-enabled"
HTTPSKeyFileKey = "http-tls-key-file"
HTTPSKeyContentKey = "http-tls-key-file-content"
HTTPSCertFileKey = "http-tls-cert-file"
HTTPSCertContentKey = "http-tls-cert-file-content"
HTTPAllowedOrigins = "http-allowed-origins"
HTTPAllowedHostsKey = "http-allowed-hosts"
HTTPShutdownTimeoutKey = "http-shutdown-timeout"
HTTPShutdownWaitKey = "http-shutdown-wait"
HTTPReadTimeoutKey = "http-read-timeout"
HTTPReadHeaderTimeoutKey = "http-read-header-timeout"
HTTPIdleTimeoutKey = "http-idle-timeout"
StateSyncIPsKey = "state-sync-ips"
StateSyncIDsKey = "state-sync-ids"
BootstrapNodesKey = "bootstrap-nodes"
BootstrapIPsKey = "bootstrap-ips"
BootstrapIDsKey = "bootstrap-ids"
SkipBootstrapKey = "skip-bootstrap"
EnableAutominingKey = "enable-automining"
StakingHostKey = "staking-host"
StakingPortKey = "staking-port"
StakingEphemeralCertEnabledKey = "staking-ephemeral-cert-enabled"
StakingTLSKeyPathKey = "staking-tls-key-file"
StakingTLSKeyContentKey = "staking-tls-key-file-content"
StakingCertPathKey = "staking-tls-cert-file"
StakingCertContentKey = "staking-tls-cert-file-content"
StakingEphemeralSignerEnabledKey = "staking-ephemeral-signer-enabled"
StakingSignerKeyPathKey = "staking-signer-key-file"
StakingSignerKeyContentKey = "staking-signer-key-file-content"
StakingKMSEndpointKey = "staking-kms-endpoint"
StakingKMSSecretPathKey = "staking-kms-secret-path"
StakingKMSTokenKey = "staking-kms-token"
SybilProtectionEnabledKey = "sybil-protection-enabled"
SybilProtectionDisabledWeightKey = "sybil-protection-disabled-weight"
NetworkInitialTimeoutKey = "network-initial-timeout"
NetworkMinimumTimeoutKey = "network-minimum-timeout"
NetworkMaximumTimeoutKey = "network-maximum-timeout"
NetworkMaximumInboundTimeoutKey = "network-maximum-inbound-timeout"
NetworkTimeoutHalflifeKey = "network-timeout-halflife"
NetworkTimeoutCoefficientKey = "network-timeout-coefficient"
NetworkHealthMinPeersKey = "network-health-min-conn-peers"
NetworkHealthMaxTimeSinceMsgReceivedKey = "network-health-max-time-since-msg-received"
NetworkHealthMaxTimeSinceMsgSentKey = "network-health-max-time-since-msg-sent"
NetworkHealthMaxPortionSendQueueFillKey = "network-health-max-portion-send-queue-full"
NetworkHealthMaxSendFailRateKey = "network-health-max-send-fail-rate"
NetworkHealthMaxOutstandingDurationKey = "network-health-max-outstanding-request-duration"
NetworkPeerListNumValidatorIPsKey = "network-peer-list-num-validator-ips"
NetworkPeerListPullGossipFreqKey = "network-peer-list-pull-gossip-frequency"
NetworkPeerListBloomResetFreqKey = "network-peer-list-bloom-reset-frequency"
NetworkInitialReconnectDelayKey = "network-initial-reconnect-delay"
NetworkReadHandshakeTimeoutKey = "network-read-handshake-timeout"
NetworkPingTimeoutKey = "network-ping-timeout"
NetworkPingFrequencyKey = "network-ping-frequency"
NetworkMaxReconnectDelayKey = "network-max-reconnect-delay"
NetworkCompressionTypeKey = "network-compression-type"
NetworkMaxClockDifferenceKey = "network-max-clock-difference"
NetworkAllowPrivateIPsKey = "network-allow-private-ips"
NetworkRequireValidatorToConnectKey = "network-require-validator-to-connect"
NetworkPeerReadBufferSizeKey = "network-peer-read-buffer-size"
NetworkPeerWriteBufferSizeKey = "network-peer-write-buffer-size"
NetworkTCPProxyEnabledKey = "network-tcp-proxy-enabled"
NetworkTCPProxyReadTimeoutKey = "network-tcp-proxy-read-timeout"
NetworkTLSKeyLogFileKey = "network-tls-key-log-file-unsafe"
NetworkInboundConnUpgradeThrottlerCooldownKey = "network-inbound-connection-throttling-cooldown"
NetworkInboundThrottlerMaxConnsPerSecKey = "network-inbound-connection-throttling-max-conns-per-sec"
NetworkOutboundConnectionThrottlingRpsKey = "network-outbound-connection-throttling-rps"
NetworkOutboundConnectionTimeoutKey = "network-outbound-connection-timeout"
NetworkNoIngressValidatorConnectionsGracePeriodKey = "network-no-ingress-connections-grace-period"
BenchlistFailThresholdKey = "benchlist-fail-threshold"
BenchlistDurationKey = "benchlist-duration"
BenchlistMinFailingDurationKey = "benchlist-min-failing-duration"
LogsDirKey = "log-dir"
LogLevelKey = "log-level"
LogDisplayLevelKey = "log-display-level"
LogFormatKey = "log-format"
LogRotaterMaxSizeKey = "log-rotater-max-size"
LogRotaterMaxFilesKey = "log-rotater-max-files"
LogRotaterMaxAgeKey = "log-rotater-max-age"
LogRotaterCompressEnabledKey = "log-rotater-compress-enabled"
LogDisableDisplayPluginLogsKey = "log-disable-display-plugin-logs"
ConsensusSampleSizeKey = "consensus-sample-size"
ConsensusQuorumSizeKey = "consensus-quorum-size"
ConsensusPreferenceQuorumSizeKey = "consensus-preference-quorum-size"
ConsensusConfidenceQuorumSizeKey = "consensus-confidence-quorum-size"
ConsensusCommitThresholdKey = "consensus-commit-threshold"
ConsensusConcurrentRepollsKey = "consensus-concurrent-repolls"
ConsensusOptimalProcessingKey = "consensus-optimal-processing"
ConsensusMaxProcessingKey = "consensus-max-processing"
ConsensusMaxTimeProcessingKey = "consensus-max-time-processing"
PartialSyncPrimaryNetworkKey = "partial-sync-primary-network"
TrackChainsKey = "track-chains"
TrackAllChainsKey = "track-all-chains"
AdminAPIEnabledKey = "api-admin-enabled"
InfoAPIEnabledKey = "api-info-enabled"
KeystoreAPIEnabledKey = "api-keystore-enabled"
MetricsAPIEnabledKey = "api-metrics-enabled"
HealthAPIEnabledKey = "api-health-enabled"
MeterVMsEnabledKey = "meter-vms-enabled"
ConsensusAppConcurrencyKey = "consensus-app-concurrency"
ConsensusShutdownTimeoutKey = "consensus-shutdown-timeout"
ConsensusFrontierPollFrequencyKey = "consensus-frontier-poll-frequency"
ProposerVMUseCurrentHeightKey = "proposervm-use-current-height"
ProposerVMMinBlockDelayKey = "proposervm-min-block-delay"
FdLimitKey = "fd-limit"
IndexEnabledKey = "index-enabled"
IndexAllowIncompleteKey = "index-allow-incomplete"
RouterHealthMaxDropRateKey = "router-health-max-drop-rate"
RouterHealthMaxOutstandingRequestsKey = "router-health-max-outstanding-requests"
HealthCheckFreqKey = "health-check-frequency"
HealthCheckAveragerHalflifeKey = "health-check-averager-halflife"
PluginDirKey = "plugin-dir"
BootstrapBeaconConnectionTimeoutKey = "bootstrap-beacon-connection-timeout"
BootstrapMaxTimeGetAncestorsKey = "bootstrap-max-time-get-ancestors"
BootstrapAncestorsMaxContainersSentKey = "bootstrap-ancestors-max-containers-sent"
BootstrapAncestorsMaxContainersReceivedKey = "bootstrap-ancestors-max-containers-received"
ChainDataDirKey = "chain-data-dir"
ChainConfigDirKey = "chain-config-dir"
ChainConfigContentKey = "chain-config-content"
ImportChainDataKey = "import-chain-data"
NetConfigDirKey = "net-config-dir"
NetConfigContentKey = "net-config-content"
ProfileDirKey = "profile-dir"
ProfileContinuousEnabledKey = "profile-continuous-enabled"
ProfileContinuousFreqKey = "profile-continuous-freq"
ProfileContinuousMaxFilesKey = "profile-continuous-max-files"
InboundThrottlerAtLargeAllocSizeKey = "throttler-inbound-at-large-alloc-size"
InboundThrottlerVdrAllocSizeKey = "throttler-inbound-validator-alloc-size"
InboundThrottlerNodeMaxAtLargeBytesKey = "throttler-inbound-node-max-at-large-bytes"
InboundThrottlerMaxProcessingMsgsPerNodeKey = "throttler-inbound-node-max-processing-msgs"
InboundThrottlerBandwidthRefillRateKey = "throttler-inbound-bandwidth-refill-rate"
InboundThrottlerBandwidthMaxBurstSizeKey = "throttler-inbound-bandwidth-max-burst-size"
InboundThrottlerCPUMaxRecheckDelayKey = "throttler-inbound-cpu-max-recheck-delay"
InboundThrottlerDiskMaxRecheckDelayKey = "throttler-inbound-disk-max-recheck-delay"
CPUVdrAllocKey = "throttler-inbound-cpu-validator-alloc"
CPUMaxNonVdrUsageKey = "throttler-inbound-cpu-max-non-validator-usage"
CPUMaxNonVdrNodeUsageKey = "throttler-inbound-cpu-max-non-validator-node-usage"
SystemTrackerFrequencyKey = "system-tracker-frequency"
SystemTrackerProcessingHalflifeKey = "system-tracker-processing-halflife"
SystemTrackerCPUHalflifeKey = "system-tracker-cpu-halflife"
SystemTrackerDiskHalflifeKey = "system-tracker-disk-halflife"
SystemTrackerRequiredAvailableDiskSpaceKey = "system-tracker-disk-required-available-space"
SystemTrackerWarningThresholdAvailableDiskSpaceKey = "system-tracker-disk-warning-threshold-available-space"
DiskVdrAllocKey = "throttler-inbound-disk-validator-alloc"
DiskMaxNonVdrUsageKey = "throttler-inbound-disk-max-non-validator-usage"
DiskMaxNonVdrNodeUsageKey = "throttler-inbound-disk-max-non-validator-node-usage"
OutboundThrottlerAtLargeAllocSizeKey = "throttler-outbound-at-large-alloc-size"
OutboundThrottlerVdrAllocSizeKey = "throttler-outbound-validator-alloc-size"
OutboundThrottlerNodeMaxAtLargeBytesKey = "throttler-outbound-node-max-at-large-bytes"
UptimeMetricFreqKey = "uptime-metric-freq"
VMAliasesFileKey = "vm-aliases-file"
VMAliasesContentKey = "vm-aliases-file-content"
ChainAliasesFileKey = "chain-aliases-file"
ChainAliasesContentKey = "chain-aliases-file-content"
TracingEndpointKey = "tracing-endpoint"
TracingInsecureKey = "tracing-insecure"
TracingSampleRateKey = "tracing-sample-rate"
TracingExporterTypeKey = "tracing-exporter-type"
TracingHeadersKey = "tracing-headers"
ProcessContextFileKey = "process-context-file"
// Development and LP Keys
DevModeKey = "automine"
LPSupportKey = "lp-support"
LPObjectKey = "lp-object"
// GPU Acceleration Keys
GPUEnabledKey = "gpu-enabled"
GPUBackendKey = "gpu-backend"
GPUDeviceKey = "gpu-device"
GPULogLevelKey = "gpu-log-level"
// POA Mode Keys
POAModeEnabledKey = "poa-mode-enabled"
POASingleNodeModeKey = "poa-single-node-mode"
POAMinBlockTimeKey = "poa-min-block-time"
POAAuthorizedNodesKey = "poa-authorized-nodes"
// Force flags
ForceIgnoreChecksumKey = "force-ignore-checksum"
// Low Memory / Dev Light Mode Keys
LowMemoryKey = "low-memory"
MemoryProfileKey = "memory-profile"
DevLightKey = "dev-light"
ConfigProfileKey = "config-profile"
DBCacheSizeKey = "db-cache-size"
DBMemtableSizeKey = "db-memtable-size"
StateCacheSizeKey = "state-cache-size"
BlockCacheSizeKey = "block-cache-size"
DisableBloomFiltersKey = "disable-bloom-filters"
LazyChainLoadingKey = "lazy-chain-loading"
SingleValidatorModeKey = "single-validator-mode"
// VM Transport Keys
VMTransportKey = "vm-transport"
VMTransportTimeoutKey = "vm-transport-timeout"
)
+260
View File
@@ -0,0 +1,260 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"embed"
"encoding/json"
"fmt"
"github.com/spf13/viper"
)
//go:embed profiles/*.json
var profilesFS embed.FS
// LowMemoryConfig holds memory-related configuration for resource-constrained environments.
// Default mode uses standard production settings.
// Low memory mode targets <200MB idle, <500MB under load.
type LowMemoryConfig struct {
// Enabled indicates low memory mode is active
Enabled bool `json:"enabled"`
// DBCacheSize is the database cache size in bytes
// Default: 64MB, Low memory: 8MB
DBCacheSize uint64 `json:"dbCacheSize"`
// DBMemtableSize is the memtable size in bytes
// Default: 64MB, Low memory: 8MB
DBMemtableSize uint64 `json:"dbMemtableSize"`
// StateCacheSize is the state cache size in bytes
// Default: 256MB, Low memory: 16MB
StateCacheSize uint64 `json:"stateCacheSize"`
// BlockCacheSize is the block cache size in bytes
// Default: 128MB, Low memory: 8MB
BlockCacheSize uint64 `json:"blockCacheSize"`
// DisableBloomFilters disables bloom filters to save memory
DisableBloomFilters bool `json:"disableBloomFilters"`
// LazyChainLoading defers chain loading until first request
LazyChainLoading bool `json:"lazyChainLoading"`
// SingleValidatorMode runs with a single validator
SingleValidatorMode bool `json:"singleValidatorMode"`
}
// Memory size constants
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
// Standard settings - target <100MB per node (new default)
DefaultDBCacheSize = 16 * MB
DefaultDBMemtableSize = 16 * MB
DefaultStateCacheSize = 32 * MB
DefaultBlockCacheSize = 32 * MB
// Low memory settings - target <50MB per node
LowMemDBCacheSize = 4 * MB
LowMemDBMemtableSize = 4 * MB
LowMemStateCacheSize = 8 * MB
LowMemBlockCacheSize = 4 * MB
// Max memory settings - production/high-performance
MaxMemDBCacheSize = 64 * MB
MaxMemDBMemtableSize = 64 * MB
MaxMemStateCacheSize = 256 * MB
MaxMemBlockCacheSize = 128 * MB
)
// MemoryProfile defines memory usage profiles.
type MemoryProfile string
const (
// MemoryProfileLow targets <50MB per node - for very resource-constrained environments
MemoryProfileLow MemoryProfile = "low"
// MemoryProfileStandard is the default, targets <100MB per node
MemoryProfileStandard MemoryProfile = "standard"
// MemoryProfileMax is for production/high-performance, ~512MB per node
MemoryProfileMax MemoryProfile = "max"
)
// DefaultLowMemoryConfig returns the default configuration (standard profile).
func DefaultLowMemoryConfig() LowMemoryConfig {
return LowMemoryConfig{
Enabled: false,
DBCacheSize: DefaultDBCacheSize,
DBMemtableSize: DefaultDBMemtableSize,
StateCacheSize: DefaultStateCacheSize,
BlockCacheSize: DefaultBlockCacheSize,
DisableBloomFilters: false,
LazyChainLoading: false,
SingleValidatorMode: false,
}
}
// NewLowMemoryConfig returns a configuration for low memory profile (<50MB).
func NewLowMemoryConfig() LowMemoryConfig {
return LowMemoryConfig{
Enabled: true,
DBCacheSize: LowMemDBCacheSize,
DBMemtableSize: LowMemDBMemtableSize,
StateCacheSize: LowMemStateCacheSize,
BlockCacheSize: LowMemBlockCacheSize,
DisableBloomFilters: true,
LazyChainLoading: true,
SingleValidatorMode: true,
}
}
// NewMaxMemoryConfig returns a configuration for max memory profile (~512MB).
func NewMaxMemoryConfig() LowMemoryConfig {
return LowMemoryConfig{
Enabled: false,
DBCacheSize: MaxMemDBCacheSize,
DBMemtableSize: MaxMemDBMemtableSize,
StateCacheSize: MaxMemStateCacheSize,
BlockCacheSize: MaxMemBlockCacheSize,
DisableBloomFilters: false,
LazyChainLoading: false,
SingleValidatorMode: false,
}
}
// GetMemoryConfigForProfile returns config for the given memory profile.
func GetMemoryConfigForProfile(profile MemoryProfile) LowMemoryConfig {
switch profile {
case MemoryProfileLow:
return NewLowMemoryConfig()
case MemoryProfileMax:
return NewMaxMemoryConfig()
default:
return DefaultLowMemoryConfig()
}
}
// GetLowMemoryConfig builds a LowMemoryConfig from viper settings.
func GetLowMemoryConfig(v *viper.Viper) LowMemoryConfig {
// Check for explicit memory profile first
profileStr := v.GetString(MemoryProfileKey)
if profileStr != "" {
profile := MemoryProfile(profileStr)
config := GetMemoryConfigForProfile(profile)
config.Enabled = profile == MemoryProfileLow
return applyOverrides(v, config)
}
// Fallback to legacy --low-memory flag
lowMemEnabled := v.GetBool(LowMemoryKey) || v.GetBool(DevLightKey)
config := DefaultLowMemoryConfig()
if lowMemEnabled {
config = NewLowMemoryConfig()
}
return applyOverrides(v, config)
}
// applyOverrides applies explicit flag overrides to the config.
func applyOverrides(v *viper.Viper, config LowMemoryConfig) LowMemoryConfig {
// Allow explicit flag overrides
if v.IsSet(DBCacheSizeKey) {
config.DBCacheSize = v.GetUint64(DBCacheSizeKey)
}
if v.IsSet(DBMemtableSizeKey) {
config.DBMemtableSize = v.GetUint64(DBMemtableSizeKey)
}
if v.IsSet(StateCacheSizeKey) {
config.StateCacheSize = v.GetUint64(StateCacheSizeKey)
}
if v.IsSet(BlockCacheSizeKey) {
config.BlockCacheSize = v.GetUint64(BlockCacheSizeKey)
}
if v.IsSet(DisableBloomFiltersKey) {
config.DisableBloomFilters = v.GetBool(DisableBloomFiltersKey)
}
if v.IsSet(LazyChainLoadingKey) {
config.LazyChainLoading = v.GetBool(LazyChainLoadingKey)
}
if v.IsSet(SingleValidatorModeKey) {
config.SingleValidatorMode = v.GetBool(SingleValidatorModeKey)
}
return config
}
// LoadConfigProfile loads a predefined configuration profile from embedded files.
// Returns the profile as a map for merging with viper.
func LoadConfigProfile(name string) (map[string]interface{}, error) {
if name == "" {
return nil, nil
}
filename := "profiles/" + name + ".json"
data, err := profilesFS.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to load profile %q: %w", name, err)
}
var profile map[string]interface{}
if err := json.Unmarshal(data, &profile); err != nil {
return nil, fmt.Errorf("failed to parse profile %q: %w", name, err)
}
// Remove comment fields
delete(profile, "_comment")
delete(profile, "_targets")
return profile, nil
}
// ApplyConfigProfile applies a configuration profile to viper.
// Profile values are set with lower priority than command-line flags.
func ApplyConfigProfile(v *viper.Viper, profile map[string]interface{}) {
for key, value := range profile {
if !v.IsSet(key) {
v.Set(key, value)
}
}
}
// ApplyDevLightMode applies --dev-light settings to viper.
// This is equivalent to --dev --low-memory.
func ApplyDevLightMode(v *viper.Viper) error {
// Load the dev-light profile
profile, err := LoadConfigProfile("dev-light")
if err != nil {
return err
}
// Apply profile settings (won't override explicitly set flags)
ApplyConfigProfile(v, profile)
// Ensure dev mode is enabled
v.Set(DevModeKey, true)
v.Set(LowMemoryKey, true)
return nil
}
// EstimatedMemoryUsage returns an estimate of memory usage based on config.
func (c LowMemoryConfig) EstimatedMemoryUsage() (idle, load uint64) {
// Base memory overhead (runtime, goroutines, etc.)
baseOverhead := uint64(50 * MB)
// Idle usage: caches are mostly empty
idle = baseOverhead + c.DBCacheSize/4 + c.StateCacheSize/4
// Load usage: caches fill up
load = baseOverhead + c.DBCacheSize + c.DBMemtableSize + c.StateCacheSize + c.BlockCacheSize
// Add overhead for network buffers if not in single validator mode
if !c.SingleValidatorMode {
load += 50 * MB // Network overhead for multi-validator
}
return idle, load
}
+298
View File
@@ -0,0 +1,298 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"encoding/json"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestDefaultLowMemoryConfig(t *testing.T) {
config := DefaultLowMemoryConfig()
require.False(t, config.Enabled)
require.Equal(t, uint64(DefaultDBCacheSize), config.DBCacheSize)
require.Equal(t, uint64(DefaultDBMemtableSize), config.DBMemtableSize)
require.Equal(t, uint64(DefaultStateCacheSize), config.StateCacheSize)
require.Equal(t, uint64(DefaultBlockCacheSize), config.BlockCacheSize)
require.False(t, config.DisableBloomFilters)
require.False(t, config.LazyChainLoading)
require.False(t, config.SingleValidatorMode)
}
func TestNewLowMemoryConfig(t *testing.T) {
config := NewLowMemoryConfig()
require.True(t, config.Enabled)
require.Equal(t, uint64(LowMemDBCacheSize), config.DBCacheSize)
require.Equal(t, uint64(LowMemDBMemtableSize), config.DBMemtableSize)
require.Equal(t, uint64(LowMemStateCacheSize), config.StateCacheSize)
require.Equal(t, uint64(LowMemBlockCacheSize), config.BlockCacheSize)
require.True(t, config.DisableBloomFilters)
require.True(t, config.LazyChainLoading)
require.True(t, config.SingleValidatorMode)
}
func TestGetLowMemoryConfig_Default(t *testing.T) {
v := viper.New()
config := GetLowMemoryConfig(v)
require.False(t, config.Enabled)
require.Equal(t, uint64(DefaultDBCacheSize), config.DBCacheSize)
}
func TestGetLowMemoryConfig_LowMemoryEnabled(t *testing.T) {
v := viper.New()
v.Set(LowMemoryKey, true)
config := GetLowMemoryConfig(v)
require.True(t, config.Enabled)
require.Equal(t, uint64(LowMemDBCacheSize), config.DBCacheSize)
require.True(t, config.LazyChainLoading)
}
func TestGetLowMemoryConfig_DevLightEnabled(t *testing.T) {
v := viper.New()
v.Set(DevLightKey, true)
config := GetLowMemoryConfig(v)
require.True(t, config.Enabled)
require.Equal(t, uint64(LowMemDBCacheSize), config.DBCacheSize)
}
func TestGetLowMemoryConfig_ExplicitOverrides(t *testing.T) {
v := viper.New()
v.Set(LowMemoryKey, true)
v.Set(DBCacheSizeKey, uint64(32*MB))
v.Set(StateCacheSizeKey, uint64(64*MB))
config := GetLowMemoryConfig(v)
require.True(t, config.Enabled)
require.Equal(t, uint64(32*MB), config.DBCacheSize)
require.Equal(t, uint64(64*MB), config.StateCacheSize)
// Non-overridden values use low memory defaults
require.Equal(t, uint64(LowMemDBMemtableSize), config.DBMemtableSize)
}
func TestLoadConfigProfile_DevLight(t *testing.T) {
profile, err := LoadConfigProfile("dev-light")
require.NoError(t, err)
require.NotNil(t, profile)
// Check expected keys are present
require.Contains(t, profile, "dev")
require.Contains(t, profile, "low-memory")
require.Contains(t, profile, "db-cache-size")
// Check comment fields are removed
require.NotContains(t, profile, "_comment")
require.NotContains(t, profile, "_targets")
}
func TestLoadConfigProfile_NonExistent(t *testing.T) {
profile, err := LoadConfigProfile("non-existent-profile")
require.Error(t, err)
require.Nil(t, profile)
}
func TestLoadConfigProfile_Empty(t *testing.T) {
profile, err := LoadConfigProfile("")
require.NoError(t, err)
require.Nil(t, profile)
}
func TestApplyConfigProfile(t *testing.T) {
v := viper.New()
v.Set("existing-key", "existing-value")
profile := map[string]interface{}{
"existing-key": "profile-value",
"new-key": "profile-new-value",
}
ApplyConfigProfile(v, profile)
// Existing key should not be overwritten
require.Equal(t, "existing-value", v.GetString("existing-key"))
// New key should be added
require.Equal(t, "profile-new-value", v.GetString("new-key"))
}
func TestEstimatedMemoryUsage(t *testing.T) {
tests := []struct {
name string
config LowMemoryConfig
expectedIdle uint64
expectedLoad uint64
singleValBonus uint64
}{
{
name: "default config",
config: DefaultLowMemoryConfig(),
},
{
name: "low memory config",
config: NewLowMemoryConfig(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
idle, load := tt.config.EstimatedMemoryUsage()
// Basic sanity checks
require.Greater(t, idle, uint64(0))
require.Greater(t, load, uint64(0))
require.Greater(t, load, idle)
// Low memory mode should have significantly lower memory usage
if tt.config.Enabled {
require.Less(t, idle, uint64(100*MB), "idle memory should be <100MB in low memory mode")
require.Less(t, load, uint64(200*MB), "load memory should be <200MB in low memory mode")
}
})
}
}
func TestMemorySizeConstants(t *testing.T) {
// Constants are untyped, so compare as int
require.Equal(t, 1024, KB)
require.Equal(t, 1024*1024, MB)
require.Equal(t, 1024*1024*1024, GB)
// Verify low < standard < max for all settings
require.Less(t, LowMemDBCacheSize, DefaultDBCacheSize)
require.Less(t, LowMemDBMemtableSize, DefaultDBMemtableSize)
require.Less(t, LowMemStateCacheSize, DefaultStateCacheSize)
require.Less(t, LowMemBlockCacheSize, DefaultBlockCacheSize)
require.Less(t, DefaultDBCacheSize, MaxMemDBCacheSize)
require.Less(t, DefaultDBMemtableSize, MaxMemDBMemtableSize)
require.Less(t, DefaultStateCacheSize, MaxMemStateCacheSize)
require.Less(t, DefaultBlockCacheSize, MaxMemBlockCacheSize)
}
func TestMemoryProfile(t *testing.T) {
// Test low profile
lowConfig := GetMemoryConfigForProfile(MemoryProfileLow)
require.Equal(t, uint64(LowMemDBCacheSize), lowConfig.DBCacheSize)
require.True(t, lowConfig.DisableBloomFilters)
// Test standard profile
stdConfig := GetMemoryConfigForProfile(MemoryProfileStandard)
require.Equal(t, uint64(DefaultDBCacheSize), stdConfig.DBCacheSize)
require.False(t, stdConfig.DisableBloomFilters)
// Test max profile
maxConfig := GetMemoryConfigForProfile(MemoryProfileMax)
require.Equal(t, uint64(MaxMemDBCacheSize), maxConfig.DBCacheSize)
require.False(t, maxConfig.DisableBloomFilters)
}
func TestGetLowMemoryConfig_MemoryProfile(t *testing.T) {
// Test --memory-profile=low
v := viper.New()
v.Set(MemoryProfileKey, "low")
config := GetLowMemoryConfig(v)
require.True(t, config.Enabled)
require.Equal(t, uint64(LowMemDBCacheSize), config.DBCacheSize)
// Test --memory-profile=standard
v = viper.New()
v.Set(MemoryProfileKey, "standard")
config = GetLowMemoryConfig(v)
require.False(t, config.Enabled)
require.Equal(t, uint64(DefaultDBCacheSize), config.DBCacheSize)
// Test --memory-profile=max
v = viper.New()
v.Set(MemoryProfileKey, "max")
config = GetLowMemoryConfig(v)
require.False(t, config.Enabled)
require.Equal(t, uint64(MaxMemDBCacheSize), config.DBCacheSize)
}
func TestBuildDatabaseConfigBytes_Disabled(t *testing.T) {
v := viper.New()
// Low memory not enabled and no explicit DB settings
configBytes, err := buildDatabaseConfigBytes(v)
require.NoError(t, err)
require.Nil(t, configBytes)
}
func TestBuildDatabaseConfigBytes_LowMemoryEnabled(t *testing.T) {
v := viper.New()
v.Set(LowMemoryKey, true)
configBytes, err := buildDatabaseConfigBytes(v)
require.NoError(t, err)
require.NotNil(t, configBytes)
// Verify JSON can be parsed and has expected structure
var cfg map[string]interface{}
err = json.Unmarshal(configBytes, &cfg)
require.NoError(t, err)
// Check that reduced memory settings are present
require.Contains(t, cfg, "memTableSize")
require.Contains(t, cfg, "blockCacheSize")
require.Contains(t, cfg, "indexCacheSize")
require.Contains(t, cfg, "numMemtables")
require.Contains(t, cfg, "numCompactors")
require.Contains(t, cfg, "bloomFalsePositive")
// Verify values are low memory settings
memTableSize := int64(cfg["memTableSize"].(float64))
require.Equal(t, int64(LowMemDBMemtableSize), memTableSize)
blockCacheSize := int64(cfg["blockCacheSize"].(float64))
require.Equal(t, int64(LowMemDBCacheSize), blockCacheSize)
numMemtables := int(cfg["numMemtables"].(float64))
require.Equal(t, 2, numMemtables) // Reduced from default 5
}
func TestBuildDatabaseConfigBytes_BloomFiltersDisabled(t *testing.T) {
v := viper.New()
v.Set(LowMemoryKey, true)
v.Set(DisableBloomFiltersKey, true)
configBytes, err := buildDatabaseConfigBytes(v)
require.NoError(t, err)
require.NotNil(t, configBytes)
var cfg map[string]interface{}
err = json.Unmarshal(configBytes, &cfg)
require.NoError(t, err)
// Bloom false positive should be 1.0 (effectively disabled)
bloomFP := cfg["bloomFalsePositive"].(float64)
require.Equal(t, 1.0, bloomFP)
}
func TestBuildDatabaseConfigBytes_ExplicitOverrides(t *testing.T) {
v := viper.New()
v.Set(DBCacheSizeKey, uint64(16*MB))
v.Set(DBMemtableSizeKey, uint64(16*MB))
configBytes, err := buildDatabaseConfigBytes(v)
require.NoError(t, err)
require.NotNil(t, configBytes)
var cfg map[string]interface{}
err = json.Unmarshal(configBytes, &cfg)
require.NoError(t, err)
// Verify explicit overrides are used
blockCacheSize := int64(cfg["blockCacheSize"].(float64))
require.Equal(t, int64(16*MB), blockCacheSize)
memTableSize := int64(cfg["memTableSize"].(float64))
require.Equal(t, int64(16*MB), memTableSize)
}
+242
View File
@@ -0,0 +1,242 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package node
import (
"crypto/tls"
"net/netip"
"time"
"github.com/luxfi/ids"
"github.com/luxfi/node/server/http"
"github.com/luxfi/node/benchlist"
"github.com/luxfi/node/chains"
"github.com/luxfi/node/genesis/builder"
"github.com/luxfi/node/network"
// "github.com/luxfi/consensus/core/router" // Unused
"github.com/luxfi/crypto/bls"
"github.com/luxfi/node/nets"
"github.com/luxfi/node/network/tracker"
"github.com/luxfi/node/upgrade"
"github.com/luxfi/node/trace"
// "github.com/luxfi/log" // Unused
"github.com/luxfi/math/set"
"github.com/luxfi/timer"
"github.com/luxfi/node/utils/profiler"
)
type APIIndexerConfig struct {
IndexAPIEnabled bool `json:"indexAPIEnabled"`
IndexAllowIncomplete bool `json:"indexAllowIncomplete"`
}
type HTTPConfig struct {
server.HTTPConfig
APIConfig `json:"apiConfig"`
HTTPHost string `json:"httpHost"`
HTTPPort uint16 `json:"httpPort"`
HTTPSEnabled bool `json:"httpsEnabled"`
HTTPSKey []byte `json:"-"`
HTTPSCert []byte `json:"-"`
HTTPAllowedOrigins []string `json:"httpAllowedOrigins"`
HTTPAllowedHosts []string `json:"httpAllowedHosts"`
ShutdownTimeout time.Duration `json:"shutdownTimeout"`
ShutdownWait time.Duration `json:"shutdownWait"`
}
type APIConfig struct {
APIIndexerConfig `json:"indexerConfig"`
// Enable/Disable APIs
AdminAPIEnabled bool `json:"adminAPIEnabled"`
InfoAPIEnabled bool `json:"infoAPIEnabled"`
KeystoreAPIEnabled bool `json:"keystoreAPIEnabled"`
MetricsAPIEnabled bool `json:"metricsAPIEnabled"`
HealthAPIEnabled bool `json:"healthAPIEnabled"`
}
type IPConfig struct {
PublicIP string `json:"publicIP"`
PublicIPResolutionService string `json:"publicIPResolutionService"`
PublicIPResolutionFreq time.Duration `json:"publicIPResolutionFreq"`
// The host portion of the address to listen on. The port to
// listen on will be sourced from IPPort.
//
// - If empty, listen on all interfaces (both ipv4 and ipv6).
// - If populated, listen only on the specified address.
ListenHost string `json:"listenHost"`
ListenPort uint16 `json:"listenPort"`
}
type StakingConfig struct {
builder.StakingConfig
SybilProtectionEnabled bool `json:"sybilProtectionEnabled"`
PartialSyncPrimaryNetwork bool `json:"partialSyncPrimaryNetwork"`
StakingTLSCert tls.Certificate `json:"-"`
StakingSigningKey bls.Signer `json:"-"`
SybilProtectionDisabledWeight uint64 `json:"sybilProtectionDisabledWeight"`
// not accessed but used for logging
StakingKeyPath string `json:"stakingKeyPath"`
StakingCertPath string `json:"stakingCertPath"`
StakingSignerPath string `json:"stakingSignerPath"`
}
type StateSyncConfig struct {
StateSyncIDs []ids.NodeID `json:"stateSyncIDs"`
StateSyncIPs []netip.AddrPort `json:"stateSyncIPs"`
}
type BootstrapConfig struct {
// Timeout before emitting a warn log when connecting to bootstrapping beacons
BootstrapBeaconConnectionTimeout time.Duration `json:"bootstrapBeaconConnectionTimeout"`
// Max number of containers in an ancestors message sent by this node.
BootstrapAncestorsMaxContainersSent int `json:"bootstrapAncestorsMaxContainersSent"`
// This node will only consider the first [AncestorsMaxContainersReceived]
// containers in an ancestors message it receives.
BootstrapAncestorsMaxContainersReceived int `json:"bootstrapAncestorsMaxContainersReceived"`
// Max time to spend fetching a container and its
// ancestors while responding to a GetAncestors message
BootstrapMaxTimeGetAncestors time.Duration `json:"bootstrapMaxTimeGetAncestors"`
Bootstrappers []builder.Bootstrapper `json:"bootstrappers"`
// Skip bootstrapping and start processing immediately
SkipBootstrap bool `json:"skipBootstrap"`
// Enable automining in POA mode
EnableAutomining bool `json:"enableAutomining"`
}
type DatabaseConfig struct {
// If true, all writes are to memory and are discarded at node shutdown.
ReadOnly bool `json:"readOnly"`
// Path to database
Path string `json:"path"`
// Name of the database type to use
Name string `json:"name"`
// Path to config file
Config []byte `json:"-"`
}
// Config contains all of the configurations of a Lux node.
type Config struct {
HTTPConfig `json:"httpConfig"`
IPConfig `json:"ipConfig"`
StakingConfig `json:"stakingConfig"`
builder.TxFeeConfig `json:"txFeeConfig"`
StateSyncConfig `json:"stateSyncConfig"`
BootstrapConfig `json:"bootstrapConfig"`
DatabaseConfig `json:"databaseConfig"`
UpgradeConfig upgrade.Config `json:"upgradeConfig"`
// Genesis information
GenesisBytes []byte `json:"-"`
LuxAssetID ids.ID `json:"xAssetID"`
AllowGenesisUpdate bool `json:"allowGenesisUpdate,omitempty"`
// ID of the network this node should connect to
NetworkID uint32 `json:"networkID"`
// Health
HealthCheckFreq time.Duration `json:"healthCheckFreq"`
// Network configuration
NetworkConfig network.Config `json:"networkConfig"`
AdaptiveTimeoutConfig timer.AdaptiveTimeoutConfig `json:"adaptiveTimeoutConfig"`
BenchlistConfig benchlist.Config `json:"benchlistConfig"`
ProfilerConfig profiler.Config `json:"profilerConfig"`
// LoggingConfig logging.Config `json:"loggingConfig"` // logging package not available
PluginDir string `json:"pluginDir"`
// File Descriptor Limit
FdLimit uint64 `json:"fdLimit"`
// Metrics
MeterVMEnabled bool `json:"meterVMEnabled"`
// RouterHealthConfig router.HealthConfig `json:"routerHealthConfig"` // router.HealthConfig not available
ConsensusShutdownTimeout time.Duration `json:"consensusShutdownTimeout"`
// Poll for new frontiers every [FrontierPollFrequency]
FrontierPollFrequency time.Duration `json:"consensusGossipFreq"`
// ConsensusAppConcurrency defines the maximum number of goroutines to
// handle App messages per chain.
ConsensusAppConcurrency int `json:"consensusAppConcurrency"`
// must initialize successfully or the node shuts down. Default: ["P","Q"].
TrackedChains set.Set[ids.ID] `json:"trackedChains"`
TrackAllChains bool `json:"trackAllChains"`
NetConfigs map[ids.ID]nets.Config `json:"chainConfigs"`
ChainConfigs map[string]chains.ChainConfig `json:"-"`
ChainAliases map[ids.ID][]string `json:"chainAliases"`
VMAliases map[ids.ID][]string `json:"vmAliases"`
// Halflife to use for the processing requests tracker.
// Larger halflife --> usage metrics change more slowly.
SystemTrackerProcessingHalflife time.Duration `json:"systemTrackerProcessingHalflife"`
// Frequency to check the real resource usage of tracked processes.
// More frequent checks --> usage metrics are more accurate, but more
// expensive to track
SystemTrackerFrequency time.Duration `json:"systemTrackerFrequency"`
// Halflife to use for the cpu tracker.
// Larger halflife --> cpu usage metrics change more slowly.
SystemTrackerCPUHalflife time.Duration `json:"systemTrackerCPUHalflife"`
// Halflife to use for the disk tracker.
// Larger halflife --> disk usage metrics change more slowly.
SystemTrackerDiskHalflife time.Duration `json:"systemTrackerDiskHalflife"`
CPUTargeterConfig tracker.TargeterConfig `json:"cpuTargeterConfig"`
DiskTargeterConfig tracker.TargeterConfig `json:"diskTargeterConfig"`
RequiredAvailableDiskSpace uint64 `json:"requiredAvailableDiskSpace"`
WarningThresholdAvailableDiskSpace uint64 `json:"warningThresholdAvailableDiskSpace"`
TraceConfig trace.Config `json:"traceConfig"`
// See comment on [UseCurrentHeight] in platformvm.Config
UseCurrentHeight bool `json:"useCurrentHeight"`
// ProvidedFlags contains all the flags set by the user
ProvidedFlags map[string]interface{} `json:"-"`
// ChainDataDir is the root path for per-chain directories where VMs can
// write arbitrary data.
ChainDataDir string `json:"chainDataDir"`
// Path to write process context to (including PID, API URI, and
// staking address).
ProcessContextFilePath string `json:"processContextFilePath"`
// Low Memory Configuration
LowMemoryEnabled bool `json:"lowMemoryEnabled"`
DBCacheSize uint64 `json:"dbCacheSize"`
DBMemtableSize uint64 `json:"dbMemtableSize"`
StateCacheSize uint64 `json:"stateCacheSize"`
BlockCacheSize uint64 `json:"blockCacheSize"`
DisableBloomFilters bool `json:"disableBloomFilters"`
LazyChainLoading bool `json:"lazyChainLoading"`
SingleValidatorMode bool `json:"singleValidatorMode"`
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package node
import "net/netip"
type ProcessContext struct {
// The process id of the node
PID int `json:"pid"`
// URI to access the node API
// Format: [https|http]://[host]:[port]
URI string `json:"uri"`
// Address other nodes can use to communicate with this node
StakingAddress netip.AddrPort `json:"stakingAddress"`
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package node
import (
"encoding/json"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
)
func TestProcessContext(t *testing.T) {
tests := []struct {
name string
context ProcessContext
expected string
}{
{
name: "ipv4 loopback",
context: ProcessContext{
PID: 1,
URI: "http://localhost:9650",
StakingAddress: netip.AddrPortFrom(
netip.AddrFrom4([4]byte{127, 0, 0, 1}),
9651,
),
},
expected: `{
"pid": 1,
"uri": "http://localhost:9650",
"stakingAddress": "127.0.0.1:9651"
}`,
},
{
name: "ipv6 loopback",
context: ProcessContext{
PID: 1,
URI: "http://localhost:9650",
StakingAddress: netip.AddrPortFrom(
netip.IPv6Loopback(),
9651,
),
},
expected: `{
"pid": 1,
"uri": "http://localhost:9650",
"stakingAddress": "[::1]:9651"
}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
contextJSON, err := json.MarshalIndent(test.context, "", "\t")
require.NoError(err)
require.JSONEq(test.expected, string(contextJSON))
})
}
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import "testing"
func TestPass(t *testing.T) {
// Stub test to ensure package passes
t.Log("Test passes")
}
+46
View File
@@ -0,0 +1,46 @@
{
"_comment": "Low memory profile for local development. Target: <50MB per node",
"_targets": {
"memory_per_node": "50MB"
},
"dev": true,
"low-memory": true,
"single-validator-mode": true,
"lazy-chain-loading": true,
"db-cache-size": 4194304,
"db-memtable-size": 4194304,
"state-cache-size": 8388608,
"block-cache-size": 4194304,
"disable-bloom-filters": true,
"sybil-protection-enabled": false,
"sybil-protection-disabled-weight": 100,
"skip-bootstrap": true,
"enable-automining": true,
"poa-single-node-mode": true,
"network-health-min-conn-peers": 0,
"consensus-sample-size": 1,
"consensus-quorum-size": 1,
"network-peer-read-buffer-size": 2048,
"network-peer-write-buffer-size": 2048,
"throttler-inbound-at-large-alloc-size": 1048576,
"throttler-inbound-validator-alloc-size": 1048576,
"throttler-inbound-node-max-at-large-bytes": 524288,
"throttler-inbound-bandwidth-max-burst-size": 524288,
"throttler-outbound-at-large-alloc-size": 1048576,
"throttler-outbound-validator-alloc-size": 1048576,
"throttler-outbound-node-max-at-large-bytes": 524288,
"consensus-app-concurrency": 1,
"log-level": "info",
"log-rotater-max-size": 2,
"log-rotater-max-files": 2,
"fd-limit": 512
}
+36
View File
@@ -0,0 +1,36 @@
{
"_comment": "Low memory profile. Target: <50MB per node",
"_targets": {
"memory_per_node": "50MB"
},
"low-memory": true,
"single-validator-mode": true,
"lazy-chain-loading": true,
"db-cache-size": 4194304,
"db-memtable-size": 4194304,
"state-cache-size": 8388608,
"block-cache-size": 4194304,
"disable-bloom-filters": true,
"network-peer-read-buffer-size": 2048,
"network-peer-write-buffer-size": 2048,
"throttler-inbound-at-large-alloc-size": 1048576,
"throttler-inbound-validator-alloc-size": 1048576,
"throttler-inbound-node-max-at-large-bytes": 524288,
"throttler-inbound-bandwidth-max-burst-size": 524288,
"throttler-outbound-at-large-alloc-size": 1048576,
"throttler-outbound-validator-alloc-size": 1048576,
"throttler-outbound-node-max-at-large-bytes": 524288,
"consensus-app-concurrency": 1,
"log-level": "info",
"log-rotater-max-size": 2,
"log-rotater-max-files": 2,
"fd-limit": 512
}
+31
View File
@@ -0,0 +1,31 @@
{
"_comment": "Max memory profile for production/high-performance. Target: ~512MB per node",
"_targets": {
"memory_per_node": "512MB"
},
"db-cache-size": 67108864,
"db-memtable-size": 67108864,
"state-cache-size": 268435456,
"block-cache-size": 134217728,
"network-peer-read-buffer-size": 16384,
"network-peer-write-buffer-size": 16384,
"throttler-inbound-at-large-alloc-size": 16777216,
"throttler-inbound-validator-alloc-size": 16777216,
"throttler-inbound-node-max-at-large-bytes": 8388608,
"throttler-inbound-bandwidth-max-burst-size": 8388608,
"throttler-outbound-at-large-alloc-size": 16777216,
"throttler-outbound-validator-alloc-size": 16777216,
"throttler-outbound-node-max-at-large-bytes": 8388608,
"consensus-app-concurrency": 4,
"log-level": "info",
"log-rotater-max-size": 100,
"log-rotater-max-files": 10,
"fd-limit": 65536
}
+31
View File
@@ -0,0 +1,31 @@
{
"_comment": "Standard memory profile (default). Target: <100MB per node",
"_targets": {
"memory_per_node": "100MB"
},
"db-cache-size": 16777216,
"db-memtable-size": 16777216,
"state-cache-size": 33554432,
"block-cache-size": 33554432,
"network-peer-read-buffer-size": 4096,
"network-peer-write-buffer-size": 4096,
"throttler-inbound-at-large-alloc-size": 4194304,
"throttler-inbound-validator-alloc-size": 4194304,
"throttler-inbound-node-max-at-large-bytes": 2097152,
"throttler-inbound-bandwidth-max-burst-size": 2097152,
"throttler-outbound-at-large-alloc-size": 4194304,
"throttler-outbound-validator-alloc-size": 4194304,
"throttler-outbound-node-max-at-large-bytes": 2097152,
"consensus-app-concurrency": 2,
"log-level": "info",
"log-rotater-max-size": 8,
"log-rotater-max-files": 5,
"fd-limit": 2048
}
+1709
View File
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package spec provides a machine-readable specification of all luxd configuration flags.
// This is the single source of truth for configuration - all consumers (CLI, SDK, netrunner)
// should derive their flag knowledge from this spec.
package spec
import (
"encoding/json"
"time"
)
// FlagType represents the type of a configuration flag.
type FlagType string
const (
TypeBool FlagType = "bool"
TypeInt FlagType = "int"
TypeUint FlagType = "uint"
TypeUint64 FlagType = "uint64"
TypeFloat64 FlagType = "float64"
TypeDuration FlagType = "duration"
TypeString FlagType = "string"
TypeStringSlice FlagType = "string-slice"
TypeIntSlice FlagType = "int-slice"
TypeStringToString FlagType = "string-to-string"
)
// Category groups related configuration flags.
type Category string
const (
CategoryProcess Category = "process"
CategoryNode Category = "node"
CategoryDatabase Category = "database"
CategoryNetwork Category = "network"
CategoryConsensus Category = "consensus"
CategoryStaking Category = "staking"
CategoryHTTP Category = "http"
CategoryAPI Category = "api"
CategoryHealth Category = "health"
CategoryLogging Category = "logging"
CategoryThrottler Category = "throttler"
CategorySystem Category = "system"
CategoryBootstrap Category = "bootstrap"
CategoryChain Category = "chain"
CategoryProfile Category = "profile"
CategoryMetrics Category = "metrics"
CategoryGenesis Category = "genesis"
CategoryFees Category = "fees"
CategoryIndex Category = "index"
CategoryTracing Category = "tracing"
CategoryPOA Category = "poa"
CategoryDev Category = "dev"
)
// FlagSpec describes a single configuration flag.
type FlagSpec struct {
// Key is the flag name (e.g., "network-id", "log-level")
Key string `json:"key"`
// Type is the flag's data type
Type FlagType `json:"type"`
// Default is the default value (nil if no default)
Default interface{} `json:"default,omitempty"`
// Description is the human-readable documentation
Description string `json:"description"`
// Category groups related flags for organization
Category Category `json:"category"`
// Deprecated indicates if this flag is deprecated
Deprecated bool `json:"deprecated,omitempty"`
// DeprecatedMessage explains what to use instead
DeprecatedMessage string `json:"deprecated_message,omitempty"`
// ReplacedBy is the key of the replacement flag (if deprecated)
ReplacedBy string `json:"replaced_by,omitempty"`
// Required indicates if this flag must be explicitly set
Required bool `json:"required,omitempty"`
// Sensitive indicates if the value should be masked in logs
Sensitive bool `json:"sensitive,omitempty"`
// Constraints contains validation rules
Constraints *Constraints `json:"constraints,omitempty"`
// Since indicates when this flag was introduced (version string)
Since string `json:"since,omitempty"`
}
// Constraints defines validation rules for a flag.
type Constraints struct {
// Min is the minimum value (for numeric types)
Min interface{} `json:"min,omitempty"`
// Max is the maximum value (for numeric types)
Max interface{} `json:"max,omitempty"`
// Enum lists allowed values (for string types)
Enum []string `json:"enum,omitempty"`
// Pattern is a regex pattern (for string types)
Pattern string `json:"pattern,omitempty"`
// RequiredWith lists flags that must also be set
RequiredWith []string `json:"required_with,omitempty"`
// ConflictsWith lists flags that cannot be set together
ConflictsWith []string `json:"conflicts_with,omitempty"`
}
// ConfigSpec is the complete specification of all luxd configuration flags.
type ConfigSpec struct {
// Version is the spec version (semver)
Version string `json:"version"`
// NodeVersion is the luxd version this spec was generated from
NodeVersion string `json:"node_version"`
// GeneratedAt is when this spec was generated (RFC3339)
GeneratedAt string `json:"generated_at"`
// Flags contains all flag specifications
Flags []FlagSpec `json:"flags"`
// Categories lists all categories with descriptions
Categories map[Category]string `json:"categories"`
}
// Spec returns the complete configuration specification.
// This is the single source of truth for all luxd flags.
func Spec() *ConfigSpec {
return &ConfigSpec{
Version: "1.0.0",
NodeVersion: "1.22.18",
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
Flags: allFlags(),
Categories: categoryDescriptions(),
}
}
// categoryDescriptions returns descriptions for all categories.
func categoryDescriptions() map[Category]string {
return map[Category]string{
CategoryProcess: "Process-level flags (version, help)",
CategoryNode: "Node configuration (data directory, plugins)",
CategoryDatabase: "Database configuration (type, path, per-chain)",
CategoryNetwork: "P2P networking (timeouts, throttling, peers)",
CategoryConsensus: "Consensus parameters (sample size, quorum, thresholds)",
CategoryStaking: "Staking configuration (keys, sybil protection)",
CategoryHTTP: "HTTP server configuration (host, port, TLS)",
CategoryAPI: "API enablement (admin, info, keystore, metrics)",
CategoryHealth: "Health check configuration (frequency, thresholds)",
CategoryLogging: "Logging configuration (level, format, rotation)",
CategoryThrottler: "Rate limiting and resource throttling",
CategorySystem: "System resource tracking (CPU, disk, memory)",
CategoryBootstrap: "Bootstrap and state sync configuration",
CategoryChain: "Chain-specific configuration directories",
CategoryProfile: "Performance profiling configuration",
CategoryMetrics: "Metrics collection and reporting",
CategoryGenesis: "Genesis file and upgrade configuration",
CategoryFees: "Transaction fee configuration",
CategoryIndex: "Indexer configuration",
CategoryTracing: "OpenTelemetry tracing configuration",
CategoryPOA: "Proof of Authority mode configuration",
CategoryDev: "Development and testing flags",
}
}
// GetFlag returns the spec for a specific flag, or nil if not found.
func (s *ConfigSpec) GetFlag(key string) *FlagSpec {
for i := range s.Flags {
if s.Flags[i].Key == key {
return &s.Flags[i]
}
}
return nil
}
// FlagsByCategory returns all flags in a specific category.
func (s *ConfigSpec) FlagsByCategory(cat Category) []FlagSpec {
var result []FlagSpec
for _, f := range s.Flags {
if f.Category == cat {
result = append(result, f)
}
}
return result
}
// DeprecatedFlags returns all deprecated flags.
func (s *ConfigSpec) DeprecatedFlags() []FlagSpec {
var result []FlagSpec
for _, f := range s.Flags {
if f.Deprecated {
result = append(result, f)
}
}
return result
}
// JSON returns the spec as formatted JSON.
func (s *ConfigSpec) JSON() ([]byte, error) {
return json.MarshalIndent(s, "", " ")
}
// ValidateValue checks if a value is valid for a flag.
func (f *FlagSpec) ValidateValue(value interface{}) error {
// Type checking and constraint validation would go here
// For now, return nil (valid)
return nil
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spec
import (
"encoding/json"
"testing"
)
func TestSpec(t *testing.T) {
s := Spec()
if s == nil {
t.Fatal("Spec() returned nil")
}
if s.Version == "" {
t.Error("spec version is empty")
}
if len(s.Flags) == 0 {
t.Error("spec has no flags")
}
if len(s.Categories) == 0 {
t.Error("spec has no categories")
}
}
func TestSpecHasExpectedFlags(t *testing.T) {
s := Spec()
// Check some key flags exist
expectedFlags := []string{
"network-id",
"log-level",
"http-port",
"db-type",
"consensus-sample-size",
"staking-port",
"api-admin-enabled",
}
for _, key := range expectedFlags {
if f := s.GetFlag(key); f == nil {
t.Errorf("expected flag %q not found", key)
}
}
}
func TestGetFlag(t *testing.T) {
s := Spec()
// Test existing flag
f := s.GetFlag("network-id")
if f == nil {
t.Fatal("GetFlag returned nil for network-id")
}
if f.Key != "network-id" {
t.Errorf("got key %q, want network-id", f.Key)
}
if f.Type != TypeString {
t.Errorf("got type %q, want %q", f.Type, TypeString)
}
if f.Category != CategoryNetwork {
t.Errorf("got category %q, want %q", f.Category, CategoryNetwork)
}
// Test non-existent flag
f = s.GetFlag("nonexistent-flag-xyz")
if f != nil {
t.Error("GetFlag should return nil for non-existent flag")
}
}
func TestFlagsByCategory(t *testing.T) {
s := Spec()
categories := []Category{
CategoryConsensus,
CategoryNetwork,
CategoryHTTP,
CategoryLogging,
CategoryStaking,
}
for _, cat := range categories {
flags := s.FlagsByCategory(cat)
if len(flags) == 0 {
t.Errorf("category %q has no flags", cat)
}
for _, f := range flags {
if f.Category != cat {
t.Errorf("flag %q has category %q, expected %q", f.Key, f.Category, cat)
}
}
}
}
func TestCategoryDescriptions(t *testing.T) {
s := Spec()
// All categories used in flags should have descriptions
usedCategories := make(map[Category]bool)
for _, f := range s.Flags {
usedCategories[f.Category] = true
}
for cat := range usedCategories {
if desc, ok := s.Categories[cat]; !ok {
t.Errorf("category %q has no description", cat)
} else if desc == "" {
t.Errorf("category %q has empty description", cat)
}
}
}
func TestSpecJSON(t *testing.T) {
s := Spec()
data, err := s.JSON()
if err != nil {
t.Fatalf("JSON() failed: %v", err)
}
// Verify it's valid JSON by unmarshaling
var parsed ConfigSpec
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
}
if parsed.Version != s.Version {
t.Errorf("version mismatch: got %q, want %q", parsed.Version, s.Version)
}
if len(parsed.Flags) != len(s.Flags) {
t.Errorf("flags count mismatch: got %d, want %d", len(parsed.Flags), len(s.Flags))
}
}
func TestFlagTypes(t *testing.T) {
s := Spec()
validTypes := map[FlagType]bool{
TypeBool: true,
TypeInt: true,
TypeUint: true,
TypeUint64: true,
TypeFloat64: true,
TypeDuration: true,
TypeString: true,
TypeStringSlice: true,
TypeIntSlice: true,
TypeStringToString: true,
}
for _, f := range s.Flags {
if !validTypes[f.Type] {
t.Errorf("flag %q has invalid type %q", f.Key, f.Type)
}
}
}
func TestFlagCount(t *testing.T) {
s := Spec()
// We expect a substantial number of flags
minExpected := 100
if len(s.Flags) < minExpected {
t.Errorf("expected at least %d flags, got %d", minExpected, len(s.Flags))
}
t.Logf("Total flags: %d", len(s.Flags))
// Log counts by category
counts := make(map[Category]int)
for _, f := range s.Flags {
counts[f.Category]++
}
for cat, count := range counts {
t.Logf(" %s: %d flags", cat, count)
}
}
func TestNoEmptyDescriptions(t *testing.T) {
s := Spec()
for _, f := range s.Flags {
if f.Description == "" {
t.Errorf("flag %q has empty description", f.Key)
}
}
}
func TestNoDuplicateKeys(t *testing.T) {
s := Spec()
seen := make(map[string]bool)
for _, f := range s.Flags {
if seen[f.Key] {
t.Errorf("duplicate flag key: %q", f.Key)
}
seen[f.Key] = true
}
}
+184
View File
@@ -0,0 +1,184 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
// TokenomicsConfig defines the token economics for the Lux Network
type TokenomicsConfig struct {
// Total supply: 2T tokens
TotalSupply uint64
// Airdrop configuration for legacy token holders
AirdropConfig AirdropConfig
// Staking requirements
StakingConfig StakingConfig
// Chain-specific allocations
ChainAllocations map[string]uint64
}
// AirdropConfig defines airdrop parameters
type AirdropConfig struct {
Enabled bool
SnapshotDate string
ConversionRatio float64 // Legacy token to LUX conversion ratio
VestingPeriod uint64 // in seconds
ClaimPeriod uint64 // in seconds
}
// StakingConfig defines staking parameters
type StakingConfig struct {
// Minimum stake: 1M LUX for validators
MinimumValidatorStake uint64
// NFT staking tiers
NFTStakingEnabled bool
NFTTiers []NFTTier
// Delegation parameters
MinimumDelegatorStake uint64
MaxDelegationRatio uint32
// Chain-specific validator requirements
ChainValidatorRequirements map[string]ChainStakeRequirement
// Combined staking (NFT + delegation + staked) to reach minimum
AllowCombinedStaking bool
}
// NFTTier represents different validator NFT tiers
type NFTTier struct {
Name string
RequiredLUX uint64 // Base LUX requirement with NFT
StakingMultiplier uint32 // Reward multiplier percentage
MaxValidators uint32 // Max validators in this tier
}
// ChainStakeRequirement defines chain-specific staking requirements
type ChainStakeRequirement struct {
ChainID string
MinimumStake uint64
RequiresSpecialAccess bool // e.g., B-chain bridge validators
AccessRequirements string // Description of special requirements
}
// DefaultTokenomicsConfig returns the default tokenomics configuration
func DefaultTokenomicsConfig() *TokenomicsConfig {
return &TokenomicsConfig{
// 10 Billion total supply (with 9 decimals = 10^19 units)
TotalSupply: 10_000_000_000_000_000_000, // 10B tokens with 9 decimals
AirdropConfig: AirdropConfig{
Enabled: true,
SnapshotDate: "2025-01-01T00:00:00Z",
ConversionRatio: 1000.0, // 1 legacy token = 1000 LUX
VestingPeriod: 365 * 24 * 60 * 60, // 1 year
ClaimPeriod: 90 * 24 * 60 * 60, // 90 days to claim
},
StakingConfig: StakingConfig{
// 1M LUX minimum for validators (can be combined)
MinimumValidatorStake: 1_000_000_000_000_000, // 1M tokens with 9 decimals
NFTStakingEnabled: true,
NFTTiers: []NFTTier{
{
Name: "Genesis",
RequiredLUX: 500_000_000_000_000, // 500K LUX with Genesis NFT
StakingMultiplier: 200, // 2x rewards
MaxValidators: 100,
},
{
Name: "Pioneer",
RequiredLUX: 750_000_000_000_000, // 750K LUX with Pioneer NFT
StakingMultiplier: 150, // 1.5x rewards
MaxValidators: 500,
},
{
Name: "Standard",
RequiredLUX: 1_000_000_000_000_000, // 1M LUX standard
StakingMultiplier: 100, // 1x rewards
MaxValidators: 0, // Unlimited
},
},
// Delegation minimum: 25K LUX
MinimumDelegatorStake: 25_000 * 1e9,
MaxDelegationRatio: 10, // 10x validator stake
// Allow combined staking (NFT value + delegated + staked = 1M+)
AllowCombinedStaking: true,
// Chain-specific requirements
ChainValidatorRequirements: map[string]ChainStakeRequirement{
"P": {
ChainID: "P",
MinimumStake: 1_000_000 * 1e9, // 1M LUX standard
},
"C": {
ChainID: "C",
MinimumStake: 1_000_000 * 1e9, // 1M LUX standard
},
"X": {
ChainID: "X",
MinimumStake: 1_000_000 * 1e9, // 1M LUX standard
},
"A": {
ChainID: "A",
MinimumStake: 1_000_000 * 1e9, // 1M LUX for AI chain
},
"B": {
ChainID: "B",
MinimumStake: 100_000_000 * 1e9, // 100M LUX for bridge validators
RequiresSpecialAccess: true,
AccessRequirements: "Bridge validator requires 100M LUX stake and KYC verification",
},
"Z": {
ChainID: "Z",
MinimumStake: 1_000_000 * 1e9, // 1M LUX standard
},
},
},
// Chain allocations (percentage of total supply)
ChainAllocations: map[string]uint64{
"P-Chain": 1_500_000_000_000_000_000, // 1.5B for staking/governance (15%)
"X-Chain": 2_000_000_000_000_000_000, // 2B for exchanges/liquidity (20%)
"C-Chain": 3_000_000_000_000_000_000, // 3B for smart contracts/DeFi (30%)
"A-Chain": 1_500_000_000_000_000_000, // 1.5B for attestation operations (15%)
"B-Chain": 1_000_000_000_000_000_000, // 1B for bridge liquidity (10%)
"Z-Chain": 1_000_000_000_000_000_000, // 1B for privacy/ZK operations (10%)
},
}
}
// GetMinimumStake returns the minimum stake based on NFT ownership
func (c *TokenomicsConfig) GetMinimumStake(hasNFT bool, nftTier string) uint64 {
if !hasNFT || !c.StakingConfig.NFTStakingEnabled {
return c.StakingConfig.MinimumValidatorStake
}
for _, tier := range c.StakingConfig.NFTTiers {
if tier.Name == nftTier {
return tier.RequiredLUX
}
}
return c.StakingConfig.MinimumValidatorStake
}
// GetStakingMultiplier returns the reward multiplier for a given NFT tier
func (c *TokenomicsConfig) GetStakingMultiplier(nftTier string) uint32 {
if !c.StakingConfig.NFTStakingEnabled {
return 100 // Default 1x multiplier
}
for _, tier := range c.StakingConfig.NFTTiers {
if tier.Name == nftTier {
return tier.StakingMultiplier
}
}
return 100 // Default 1x multiplier
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"os"
"strings"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const EnvPrefix = "luxd"
var DashesToUnderscores = strings.NewReplacer("-", "_")
func EnvVarName(prefix string, key string) string {
// e.g. MY_PREFIX, network-id -> MY_PREFIX_NETWORK_ID
return strings.ToUpper(prefix + "_" + DashesToUnderscores.Replace(key))
}
// BuildViper returns the viper environment from parsing config file from
// default search paths and any parsed command line flags
func BuildViper(fs *pflag.FlagSet, args []string) (*viper.Viper, error) {
if err := deprecateFlags(fs); err != nil {
return nil, err
}
if err := fs.Parse(args); err != nil {
return nil, err
}
v := viper.New()
v.AutomaticEnv()
v.SetEnvKeyReplacer(DashesToUnderscores)
v.SetEnvPrefix(EnvPrefix)
if err := v.BindPFlags(fs); err != nil {
return nil, err
}
// load node configs from flags or file, depending on which flags are set
switch {
case v.IsSet(ConfigContentKey):
configContentB64 := v.GetString(ConfigContentKey)
configBytes, err := base64.StdEncoding.DecodeString(configContentB64)
if err != nil {
return nil, fmt.Errorf("unable to decode base64 content: %w", err)
}
v.SetConfigType(v.GetString(ConfigContentTypeKey))
if err := v.ReadConfig(bytes.NewBuffer(configBytes)); err != nil {
return nil, err
}
case v.IsSet(ConfigFileKey):
filename := getExpandedArg(v, ConfigFileKey)
v.SetConfigFile(filename)
if err := v.ReadInConfig(); err != nil {
return nil, err
}
}
// Config deprecations must be after v.ReadInConfig
deprecateConfigs(v, os.Stdout)
return v, nil
}
func deprecateConfigs(v *viper.Viper, output io.Writer) {
for key, message := range deprecatedKeys {
if v.InConfig(key) {
fmt.Fprintf(output, "Config %s has been deprecated, %s\n", key, message)
}
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"xchain": {
"transport": {
"type": "hybrid",
"description": "X-Chain uses hybrid transport: gRPC (default) + QZMQ (DEX operations)",
"grpc": {
"enabled": true,
"port": 9090,
"maxMessageSize": 104857600,
"tls": {
"enabled": true,
"certFile": "/path/to/cert.pem",
"keyFile": "/path/to/key.pem"
},
"usedFor": [
"consensus",
"blockPropagation",
"stateSync",
"regularTransactions",
"peerDiscovery"
]
},
"qzmq": {
"enabled": true,
"description": "QZMQ is enabled ONLY for X-Chain DEX operations",
"consensusPort": 5000,
"dexPort": 6000,
"security": {
"mode": "conservative",
"quantumEnabled": true,
"keyRotation": "5m",
"algorithms": {
"kem": "ML-KEM-768",
"signature": "ML-DSA-87",
"aead": "AES-256-GCM",
"hash": "SHA-384"
}
},
"usedFor": [
"dexOrders",
"dexTrades",
"marketData",
"settlement",
"orderbook"
]
},
"networkPipes": {
"description": "Both gRPC and QZMQ share the same network infrastructure",
"listenAddr": "0.0.0.0",
"advertiseAddr": "auto",
"p2pPort": 9651,
"rpcPort": 9630,
"maxConnections": 256,
"bandwidthLimit": 1000,
"comment": "All traffic flows through the same network pipes, protocols are selected based on message type"
},
"routing": {
"description": "Message routing rules determine which protocol to use",
"rules": [
{
"messageType": "consensus.*",
"protocol": "grpc",
"reason": "Consensus remains on gRPC for compatibility"
},
{
"messageType": "dex.order.*",
"protocol": "qzmq",
"reason": "DEX orders need quantum security"
},
{
"messageType": "dex.trade.*",
"protocol": "qzmq",
"reason": "Trade execution requires quantum-secure settlement"
},
{
"messageType": "dex.marketdata.*",
"protocol": "qzmq",
"reason": "Market data distribution via secure multicast"
},
{
"messageType": "block.*",
"protocol": "grpc",
"reason": "Block propagation uses existing gRPC infrastructure"
},
{
"messageType": "tx.*",
"protocol": "grpc",
"reason": "Regular transactions use standard gRPC"
}
]
}
},
"dex": {
"enabled": true,
"quantumSecure": true,
"markets": [
"LUX-USDC",
"LUX-USDT",
"LUX-ETH",
"LUX-BTC"
],
"orderTypes": [
"limit",
"market",
"stop",
"stopLimit"
]
}
},
"otherChains": {
"qchain": {
"transport": "grpc",
"description": "Q-Chain uses only gRPC (default)"
},
"cchain": {
"transport": "grpc",
"description": "C-Chain (EVM) uses only gRPC (default)"
}
},
"migration": {
"description": "QZMQ can be enabled/disabled without disrupting the network",
"steps": [
"1. Deploy with qzmq.enabled=false (gRPC only)",
"2. Test QZMQ on testnet",
"3. Enable QZMQ for specific validators",
"4. Gradually roll out to all X-Chain validators",
"5. DEX operations automatically use QZMQ when available"
],
"fallback": "If QZMQ is unavailable, all operations fall back to gRPC"
}
}