github.com/iotexproject/iotex-core@v1.14.1-rc1/config/config.go (about) 1 // Copyright (c) 2024 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package config 7 8 import ( 9 "os" 10 "strings" 11 "time" 12 13 "github.com/pkg/errors" 14 uconfig "go.uber.org/config" 15 16 "github.com/iotexproject/iotex-core/actpool" 17 "github.com/iotexproject/iotex-core/api" 18 "github.com/iotexproject/iotex-core/blockchain" 19 "github.com/iotexproject/iotex-core/blockchain/genesis" 20 "github.com/iotexproject/iotex-core/blockindex" 21 "github.com/iotexproject/iotex-core/blocksync" 22 "github.com/iotexproject/iotex-core/consensus" 23 "github.com/iotexproject/iotex-core/consensus/consensusfsm" 24 "github.com/iotexproject/iotex-core/db" 25 "github.com/iotexproject/iotex-core/dispatcher" 26 "github.com/iotexproject/iotex-core/nodeinfo" 27 "github.com/iotexproject/iotex-core/p2p" 28 "github.com/iotexproject/iotex-core/pkg/log" 29 ) 30 31 // IMPORTANT: to define a config, add a field or a new config type to the existing config types. In addition, provide 32 // the default value in Default var. 33 34 const ( 35 // RollDPoSScheme means randomized delegated proof of stake 36 RollDPoSScheme = "ROLLDPOS" 37 // StandaloneScheme means that the node creates a block periodically regardless of others (if there is any) 38 StandaloneScheme = "STANDALONE" 39 // NOOPScheme means that the node does not create only block 40 NOOPScheme = "NOOP" 41 ) 42 43 const ( 44 // GatewayPlugin is the plugin of accepting user API requests and serving blockchain data to users 45 GatewayPlugin = iota 46 ) 47 48 type strs []string 49 50 func (ss *strs) String() string { 51 return strings.Join(*ss, ",") 52 } 53 54 func (ss *strs) Set(str string) error { 55 *ss = append(*ss, str) 56 return nil 57 } 58 59 // Dardanelles consensus config 60 var ( 61 // Default is the default config 62 Default = Config{ 63 Plugins: make(map[int]interface{}), 64 SubLogs: make(map[string]log.GlobalConfig), 65 Network: p2p.DefaultConfig, 66 Chain: blockchain.DefaultConfig, 67 ActPool: actpool.DefaultConfig, 68 Consensus: consensus.DefaultConfig, 69 DardanellesUpgrade: consensusfsm.DefaultDardanellesUpgradeConfig, 70 BlockSync: blocksync.DefaultConfig, 71 Dispatcher: dispatcher.DefaultConfig, 72 API: api.DefaultConfig, 73 System: System{ 74 Active: true, 75 HeartbeatInterval: 10 * time.Second, 76 HTTPStatsPort: 8080, 77 HTTPAdminPort: 0, 78 StartSubChainInterval: 10 * time.Second, 79 SystemLogDBPath: "/var/log", 80 }, 81 DB: db.DefaultConfig, 82 Indexer: blockindex.DefaultConfig, 83 Genesis: genesis.Default, 84 NodeInfo: nodeinfo.DefaultConfig, 85 } 86 87 // ErrInvalidCfg indicates the invalid config value 88 ErrInvalidCfg = errors.New("invalid config value") 89 90 // Validates is the collection config validation functions 91 Validates = []Validate{ 92 ValidateRollDPoS, 93 ValidateArchiveMode, 94 ValidateDispatcher, 95 ValidateAPI, 96 ValidateActPool, 97 ValidateForkHeights, 98 } 99 ) 100 101 // Network is the config struct for network package 102 type ( 103 104 // System is the system config 105 System struct { 106 // Active is the status of the node. True means active and false means stand-by 107 Active bool `yaml:"active"` 108 HeartbeatInterval time.Duration `yaml:"heartbeatInterval"` 109 // HTTPProfilingPort is the port number to access golang performance profiling data of a blockchain node. It is 110 // 0 by default, meaning performance profiling has been disabled 111 HTTPAdminPort int `yaml:"httpAdminPort"` 112 HTTPStatsPort int `yaml:"httpStatsPort"` 113 StartSubChainInterval time.Duration `yaml:"startSubChainInterval"` 114 SystemLogDBPath string `yaml:"systemLogDBPath"` 115 MptrieLogPath string `yaml:"mptrieLogPath"` 116 } 117 118 // Config is the root config struct, each package's config should be put as its sub struct 119 Config struct { 120 Plugins map[int]interface{} `ymal:"plugins"` 121 Network p2p.Config `yaml:"network"` 122 Chain blockchain.Config `yaml:"chain"` 123 ActPool actpool.Config `yaml:"actPool"` 124 Consensus consensus.Config `yaml:"consensus"` 125 DardanellesUpgrade consensusfsm.DardanellesUpgrade `yaml:"dardanellesUpgrade"` 126 BlockSync blocksync.Config `yaml:"blockSync"` 127 Dispatcher dispatcher.Config `yaml:"dispatcher"` 128 API api.Config `yaml:"api"` 129 System System `yaml:"system"` 130 DB db.Config `yaml:"db"` 131 Indexer blockindex.Config `yaml:"indexer"` 132 Log log.GlobalConfig `yaml:"log"` 133 SubLogs map[string]log.GlobalConfig `yaml:"subLogs"` 134 Genesis genesis.Genesis `yaml:"genesis"` 135 NodeInfo nodeinfo.Config `yaml:"nodeinfo"` 136 } 137 138 // Validate is the interface of validating the config 139 Validate func(Config) error 140 ) 141 142 // New creates a config instance. It first loads the default configs. If the config path is not empty, it will read from 143 // the file and override the default configs. By default, it will apply all validation functions. To bypass validation, 144 // use DoNotValidate instead. 145 func New(configPaths []string, _plugins []string, validates ...Validate) (Config, error) { 146 opts := make([]uconfig.YAMLOption, 0) 147 opts = append(opts, uconfig.Static(Default)) 148 opts = append(opts, uconfig.Expand(os.LookupEnv)) 149 for _, path := range configPaths { 150 if path != "" { 151 opts = append(opts, uconfig.File(path)) 152 } 153 } 154 yaml, err := uconfig.NewYAML(opts...) 155 if err != nil { 156 return Config{}, errors.Wrap(err, "failed to init config") 157 } 158 159 var cfg Config 160 if err := yaml.Get(uconfig.Root).Populate(&cfg); err != nil { 161 return Config{}, errors.Wrap(err, "failed to unmarshal YAML config to struct") 162 } 163 164 if err := cfg.Chain.SetProducerPrivKey(); err != nil { 165 return Config{}, errors.Wrap(err, "failed to set producer private key") 166 } 167 168 // set network master key to private key 169 if cfg.Network.MasterKey == "" { 170 cfg.Network.MasterKey = cfg.Chain.ProducerPrivKey 171 } 172 173 // set plugins 174 for _, plugin := range _plugins { 175 switch strings.ToLower(plugin) { 176 case "gateway": 177 cfg.Plugins[GatewayPlugin] = nil 178 default: 179 return Config{}, errors.Errorf("Plugin %s is not supported", plugin) 180 } 181 } 182 183 // By default, the config needs to pass all the validation 184 if len(validates) == 0 { 185 validates = Validates 186 } 187 for _, validate := range validates { 188 if err := validate(cfg); err != nil { 189 return Config{}, errors.Wrap(err, "failed to validate config") 190 } 191 } 192 return cfg, nil 193 } 194 195 // NewSub create config for sub chain. 196 func NewSub(configPaths []string, validates ...Validate) (Config, error) { 197 opts := make([]uconfig.YAMLOption, 0) 198 opts = append(opts, uconfig.Static(Default)) 199 opts = append(opts, uconfig.Expand(os.LookupEnv)) 200 for _, path := range configPaths { 201 if path != "" { 202 opts = append(opts, uconfig.File(path)) 203 } 204 } 205 yaml, err := uconfig.NewYAML(opts...) 206 if err != nil { 207 return Config{}, errors.Wrap(err, "failed to init config") 208 } 209 210 var cfg Config 211 if err := yaml.Get(uconfig.Root).Populate(&cfg); err != nil { 212 return Config{}, errors.Wrap(err, "failed to unmarshal YAML config to struct") 213 } 214 215 // By default, the config needs to pass all the validation 216 if len(validates) == 0 { 217 validates = Validates 218 } 219 for _, validate := range validates { 220 if err := validate(cfg); err != nil { 221 return Config{}, errors.Wrap(err, "failed to validate config") 222 } 223 } 224 return cfg, nil 225 } 226 227 // ValidateDispatcher validates the dispatcher configs 228 func ValidateDispatcher(cfg Config) error { 229 if cfg.Dispatcher.ActionChanSize <= 0 || cfg.Dispatcher.BlockChanSize <= 0 || cfg.Dispatcher.BlockSyncChanSize <= 0 { 230 return errors.Wrap(ErrInvalidCfg, "dispatcher chan size should be greater than 0") 231 } 232 233 if cfg.Dispatcher.ProcessSyncRequestInterval < 0 { 234 return errors.Wrap(ErrInvalidCfg, "dispatcher processSyncRequestInterval should not be less than 0") 235 } 236 return nil 237 } 238 239 // ValidateRollDPoS validates the roll-DPoS configs 240 func ValidateRollDPoS(cfg Config) error { 241 if cfg.Consensus.Scheme != RollDPoSScheme { 242 return nil 243 } 244 rollDPoS := cfg.Consensus.RollDPoS 245 fsm := rollDPoS.FSM 246 if fsm.EventChanSize <= 0 { 247 return errors.Wrap(ErrInvalidCfg, "roll-DPoS event chan size should be greater than 0") 248 } 249 return nil 250 } 251 252 // ValidateArchiveMode validates the state factory setting 253 func ValidateArchiveMode(cfg Config) error { 254 if !cfg.Chain.EnableArchiveMode || !cfg.Chain.EnableTrielessStateDB { 255 return nil 256 } 257 258 return errors.Wrap(ErrInvalidCfg, "Archive mode is incompatible with trieless state DB") 259 } 260 261 // ValidateAPI validates the api configs 262 func ValidateAPI(cfg Config) error { 263 if cfg.API.TpsWindow <= 0 { 264 return errors.Wrap(ErrInvalidCfg, "tps window is not a positive integer when the api is enabled") 265 } 266 return nil 267 } 268 269 // ValidateActPool validates the given config 270 func ValidateActPool(cfg Config) error { 271 maxNumActPerPool := cfg.ActPool.MaxNumActsPerPool 272 maxNumActPerAcct := cfg.ActPool.MaxNumActsPerAcct 273 if maxNumActPerPool <= 0 || maxNumActPerAcct <= 0 { 274 return errors.Wrap( 275 ErrInvalidCfg, 276 "maximum number of actions per pool or per account cannot be zero or negative", 277 ) 278 } 279 if maxNumActPerPool < maxNumActPerAcct { 280 return errors.Wrap( 281 ErrInvalidCfg, 282 "maximum number of actions per pool cannot be less than maximum number of actions per account", 283 ) 284 } 285 return nil 286 } 287 288 // ValidateForkHeights validates the forked heights 289 func ValidateForkHeights(cfg Config) error { 290 hu := cfg.Genesis 291 switch { 292 case hu.PacificBlockHeight > hu.AleutianBlockHeight: 293 return errors.Wrap(ErrInvalidCfg, "Pacific is heigher than Aleutian") 294 case hu.AleutianBlockHeight > hu.BeringBlockHeight: 295 return errors.Wrap(ErrInvalidCfg, "Aleutian is heigher than Bering") 296 case hu.BeringBlockHeight > hu.CookBlockHeight: 297 return errors.Wrap(ErrInvalidCfg, "Bering is heigher than Cook") 298 case hu.CookBlockHeight > hu.DardanellesBlockHeight: 299 return errors.Wrap(ErrInvalidCfg, "Cook is heigher than Dardanelles") 300 case hu.DardanellesBlockHeight > hu.DaytonaBlockHeight: 301 return errors.Wrap(ErrInvalidCfg, "Dardanelles is heigher than Daytona") 302 case hu.DaytonaBlockHeight > hu.EasterBlockHeight: 303 return errors.Wrap(ErrInvalidCfg, "Daytona is heigher than Easter") 304 case hu.EasterBlockHeight > hu.FbkMigrationBlockHeight: 305 return errors.Wrap(ErrInvalidCfg, "Easter is heigher than FairbankMigration") 306 case hu.FbkMigrationBlockHeight > hu.FairbankBlockHeight: 307 return errors.Wrap(ErrInvalidCfg, "FairbankMigration is heigher than Fairbank") 308 case hu.FairbankBlockHeight > hu.GreenlandBlockHeight: 309 return errors.Wrap(ErrInvalidCfg, "Fairbank is heigher than Greenland") 310 case hu.GreenlandBlockHeight > hu.IcelandBlockHeight: 311 return errors.Wrap(ErrInvalidCfg, "Greenland is heigher than Iceland") 312 case hu.IcelandBlockHeight > hu.JutlandBlockHeight: 313 return errors.Wrap(ErrInvalidCfg, "Iceland is heigher than Jutland") 314 case hu.JutlandBlockHeight > hu.KamchatkaBlockHeight: 315 return errors.Wrap(ErrInvalidCfg, "Jutland is heigher than Kamchatka") 316 case hu.KamchatkaBlockHeight > hu.LordHoweBlockHeight: 317 return errors.Wrap(ErrInvalidCfg, "Kamchatka is heigher than LordHowe") 318 case hu.LordHoweBlockHeight > hu.MidwayBlockHeight: 319 return errors.Wrap(ErrInvalidCfg, "LordHowe is heigher than Midway") 320 case hu.MidwayBlockHeight > hu.NewfoundlandBlockHeight: 321 return errors.Wrap(ErrInvalidCfg, "Midway is heigher than Newfoundland") 322 case hu.NewfoundlandBlockHeight > hu.OkhotskBlockHeight: 323 return errors.Wrap(ErrInvalidCfg, "Newfoundland is heigher than Okhotsk") 324 case hu.OkhotskBlockHeight > hu.PalauBlockHeight: 325 return errors.Wrap(ErrInvalidCfg, "Okhotsk is heigher than Palau") 326 case hu.PalauBlockHeight > hu.QuebecBlockHeight: 327 return errors.Wrap(ErrInvalidCfg, "Palau is heigher than Quebec") 328 case hu.QuebecBlockHeight > hu.RedseaBlockHeight: 329 return errors.Wrap(ErrInvalidCfg, "Quebec is heigher than Redsea") 330 case hu.RedseaBlockHeight > hu.SumatraBlockHeight: 331 return errors.Wrap(ErrInvalidCfg, "Redsea is heigher than Sumatra") 332 case hu.SumatraBlockHeight > hu.TsunamiBlockHeight: 333 return errors.Wrap(ErrInvalidCfg, "Sumatra is heigher than Tsunami") 334 } 335 return nil 336 } 337 338 // DoNotValidate validates the given config 339 func DoNotValidate(cfg Config) error { return nil }