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 }