mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
clean: squash history (binaries stripped via filter-repo)
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
+1351
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user