github.com/iotexproject/iotex-core@v1.14.1-rc1/config/config_test.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  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/iotexproject/go-pkgs/crypto"
    16  	"github.com/pkg/errors"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    20  )
    21  
    22  const (
    23  	overwritePath = "_overwritePath"
    24  	secretPath    = "_secretPath"
    25  	subChainPath  = "_subChainPath"
    26  )
    27  
    28  var (
    29  	_overwritePath string
    30  	_secretPath    string
    31  	_subChainPath  string
    32  )
    33  
    34  func makePathAndWriteFile(cfgStr, flagForPath string) (err error) {
    35  	switch flagForPath {
    36  	case overwritePath:
    37  		_overwritePath = filepath.Join(os.TempDir(), "config.yaml")
    38  		err = os.WriteFile(_overwritePath, []byte(cfgStr), 0666)
    39  	case secretPath:
    40  		_secretPath = filepath.Join(os.TempDir(), "secret.yaml")
    41  		err = os.WriteFile(_secretPath, []byte(cfgStr), 0666)
    42  	case subChainPath:
    43  		_subChainPath = filepath.Join(os.TempDir(), "config.yaml")
    44  		err = os.WriteFile(_subChainPath, []byte(cfgStr), 0666)
    45  	}
    46  	return err
    47  }
    48  
    49  func resetPathValues(t *testing.T, flagForPath []string) {
    50  	for _, pathValue := range flagForPath {
    51  		switch pathValue {
    52  		case overwritePath:
    53  			err := os.Remove(_overwritePath)
    54  			_overwritePath = ""
    55  			require.NoError(t, err)
    56  		case secretPath:
    57  			err := os.Remove(_secretPath)
    58  			_secretPath = ""
    59  			require.NoError(t, err)
    60  		case subChainPath:
    61  			err := os.Remove(_subChainPath)
    62  			_subChainPath = ""
    63  			require.NoError(t, err)
    64  		}
    65  	}
    66  }
    67  
    68  func resetPathValuesWithLookupEnv(t *testing.T, oldEnv string, oldExist bool, flagForPath string) {
    69  	switch flagForPath {
    70  	case overwritePath:
    71  		err := os.Remove(_overwritePath)
    72  		require.NoError(t, err)
    73  		_overwritePath = ""
    74  		if oldExist {
    75  			err = os.Setenv("IOTEX_TEST_NODE_TYPE", oldEnv)
    76  		} else {
    77  			err = os.Unsetenv("IOTEX_TEST_NODE_TYPE")
    78  		}
    79  		require.NoError(t, err)
    80  	case subChainPath:
    81  		err := os.Remove(_subChainPath)
    82  		require.NoError(t, err)
    83  		_subChainPath = ""
    84  		if oldExist {
    85  			err = os.Setenv("IOTEX_TEST_NODE_TYPE", oldEnv)
    86  		} else {
    87  			err = os.Unsetenv("IOTEX_TEST_NODE_TYPE")
    88  		}
    89  		require.NoError(t, err)
    90  	}
    91  }
    92  
    93  func generateProducerPrivKey() (crypto.PrivateKey, string, error) {
    94  	sk, err := crypto.GenerateKey()
    95  	cfgStr := fmt.Sprintf(`
    96  chain:
    97      producerPrivKey: "%s"
    98  `,
    99  		sk.HexString(),
   100  	)
   101  	return sk, cfgStr, err
   102  }
   103  
   104  func TestStrs_String(t *testing.T) {
   105  	ss := strs{"test"}
   106  	str := "TEST"
   107  	require.Nil(t, ss.Set(str))
   108  }
   109  
   110  func TestNewDefaultConfig(t *testing.T) {
   111  	cfg, err := New([]string{}, []string{})
   112  	require.NoError(t, err)
   113  	genesis.SetGenesisTimestamp(cfg.Genesis.Timestamp)
   114  	require.Equal(t, cfg.Genesis.Timestamp, genesis.Timestamp())
   115  }
   116  
   117  func TestNewConfigWithoutValidation(t *testing.T) {
   118  	cfg, err := New([]string{}, []string{}, DoNotValidate)
   119  	require.NoError(t, err)
   120  	require.NotNil(t, cfg)
   121  	exp := Default
   122  	exp.Network.MasterKey = cfg.Chain.ProducerPrivKey
   123  	require.Equal(t, exp, cfg)
   124  }
   125  
   126  func TestNewConfigWithWrongConfigPath(t *testing.T) {
   127  	cfg, err := New([]string{"wrong_path", ""}, []string{})
   128  	require.Error(t, err)
   129  	require.Equal(t, Config{}, cfg)
   130  	if strings.Contains(err.Error(),
   131  		"open wrong_path: The system cannot find the file specified") == false { // for Windows
   132  		require.Contains(t, err.Error(), "open wrong_path: no such file or directory")
   133  	}
   134  }
   135  
   136  func TestNewConfigWithPlugins(t *testing.T) {
   137  	_plugins := strs{
   138  		"gateway",
   139  	}
   140  	cfg, err := New([]string{}, _plugins)
   141  
   142  	require.Nil(t, cfg.Plugins[GatewayPlugin])
   143  	require.NoError(t, err)
   144  
   145  	_plugins = strs{
   146  		"trick",
   147  	}
   148  
   149  	cfg, err = New([]string{}, _plugins)
   150  
   151  	require.Equal(t, Config{}, cfg)
   152  	require.Error(t, err)
   153  
   154  	defer func() {
   155  		_plugins = nil
   156  	}()
   157  }
   158  
   159  func TestNewConfigWithOverride(t *testing.T) {
   160  	sk, cfgStr, err := generateProducerPrivKey()
   161  	require.NoError(t, err)
   162  
   163  	require.NoError(t, makePathAndWriteFile(cfgStr, "_overwritePath"))
   164  
   165  	defer resetPathValues(t, []string{"_overwritePath"})
   166  
   167  	cfg, err := New([]string{_overwritePath, ""}, []string{})
   168  	require.NoError(t, err)
   169  	require.NotNil(t, cfg)
   170  	require.Equal(t, sk.HexString(), cfg.Chain.ProducerPrivKey)
   171  }
   172  
   173  func TestNewConfigWithSecret(t *testing.T) {
   174  	sk, cfgStr, err := generateProducerPrivKey()
   175  	require.NoError(t, err)
   176  
   177  	require.NoError(t, makePathAndWriteFile(cfgStr, "_overwritePath"))
   178  
   179  	require.NoError(t, makePathAndWriteFile(cfgStr, "_secretPath"))
   180  
   181  	defer resetPathValues(t, []string{"_overwritePath", "_secretPath"})
   182  
   183  	cfg, err := New([]string{_overwritePath, _secretPath}, []string{})
   184  	require.NoError(t, err)
   185  	require.NotNil(t, cfg)
   186  	require.Equal(t, sk.HexString(), cfg.Chain.ProducerPrivKey)
   187  }
   188  
   189  func TestNewConfigWithLookupEnv(t *testing.T) {
   190  	oldEnv, oldExist := os.LookupEnv("IOTEX_TEST_NODE_TYPE")
   191  
   192  	_, cfgStr, err := generateProducerPrivKey()
   193  	require.NoError(t, err)
   194  	require.NoError(t, makePathAndWriteFile(cfgStr, "_overwritePath"))
   195  
   196  	defer resetPathValuesWithLookupEnv(t, oldEnv, oldExist, "_overwritePath")
   197  
   198  	cfg, err := New([]string{_overwritePath, ""}, []string{})
   199  	require.NoError(t, err)
   200  	require.NotNil(t, cfg)
   201  
   202  	err = os.Unsetenv("IOTEX_TEST_NODE_TYPE")
   203  	require.NoError(t, err)
   204  
   205  	cfg, err = New([]string{_overwritePath, ""}, []string{})
   206  	require.NoError(t, err)
   207  	require.NotNil(t, cfg)
   208  }
   209  
   210  func TestValidateDispatcher(t *testing.T) {
   211  	cfg := Default
   212  	require.NoError(t, ValidateDispatcher(cfg))
   213  	cfg.Dispatcher.ActionChanSize = 0
   214  	err := ValidateDispatcher(cfg)
   215  	require.Error(t, err)
   216  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   217  	require.True(
   218  		t,
   219  		strings.Contains(err.Error(), "dispatcher chan size should be greater than 0"),
   220  	)
   221  	cfg.Dispatcher.ActionChanSize = 100
   222  	cfg.Dispatcher.BlockChanSize = 0
   223  	err = ValidateDispatcher(cfg)
   224  	require.Error(t, err)
   225  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   226  	require.True(
   227  		t,
   228  		strings.Contains(err.Error(), "dispatcher chan size should be greater than 0"),
   229  	)
   230  	cfg.Dispatcher.BlockChanSize = 100
   231  	cfg.Dispatcher.BlockSyncChanSize = 0
   232  	err = ValidateDispatcher(cfg)
   233  	require.Error(t, err)
   234  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   235  	require.True(
   236  		t,
   237  		strings.Contains(err.Error(), "dispatcher chan size should be greater than 0"),
   238  	)
   239  }
   240  
   241  func TestValidateRollDPoS(t *testing.T) {
   242  	cfg := Default
   243  	cfg.Consensus.Scheme = RollDPoSScheme
   244  
   245  	cfg.Consensus.RollDPoS.FSM.EventChanSize = 0
   246  	err := ValidateRollDPoS(cfg)
   247  	require.Error(t, err)
   248  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   249  	require.True(
   250  		t,
   251  		strings.Contains(err.Error(), "roll-DPoS event chan size should be greater than 0"),
   252  	)
   253  }
   254  
   255  func TestValidateArchiveMode(t *testing.T) {
   256  	cfg := Default
   257  	cfg.Chain.EnableArchiveMode = true
   258  	cfg.Chain.EnableTrielessStateDB = true
   259  	require.Error(t, ErrInvalidCfg, errors.Cause(ValidateArchiveMode(cfg)))
   260  	require.EqualError(t, ValidateArchiveMode(cfg), "Archive mode is incompatible with trieless state DB: invalid config value")
   261  	cfg.Chain.EnableArchiveMode = false
   262  	cfg.Chain.EnableTrielessStateDB = true
   263  	require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
   264  	cfg.Chain.EnableArchiveMode = true
   265  	cfg.Chain.EnableTrielessStateDB = false
   266  	require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
   267  	cfg.Chain.EnableArchiveMode = false
   268  	cfg.Chain.EnableTrielessStateDB = false
   269  	require.NoError(t, errors.Cause(ValidateArchiveMode(cfg)))
   270  }
   271  
   272  func TestValidateActPool(t *testing.T) {
   273  	cfg := Default
   274  	cfg.ActPool.MaxNumActsPerAcct = 0
   275  	err := ValidateActPool(cfg)
   276  	require.Error(t, err)
   277  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   278  	require.True(
   279  		t,
   280  		strings.Contains(
   281  			err.Error(),
   282  			"maximum number of actions per pool or per account cannot be zero or negative",
   283  		),
   284  	)
   285  
   286  	cfg.ActPool.MaxNumActsPerAcct = 100
   287  	cfg.ActPool.MaxNumActsPerPool = 0
   288  	err = ValidateActPool(cfg)
   289  	require.Error(t, err)
   290  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   291  	require.True(
   292  		t,
   293  		strings.Contains(
   294  			err.Error(),
   295  			"maximum number of actions per pool or per account cannot be zero or negative",
   296  		),
   297  	)
   298  
   299  	cfg.ActPool.MaxNumActsPerPool = 99
   300  	err = ValidateActPool(cfg)
   301  	require.Error(t, err)
   302  	require.Equal(t, ErrInvalidCfg, errors.Cause(err))
   303  	require.True(
   304  		t,
   305  		strings.Contains(
   306  			err.Error(),
   307  			"maximum number of actions per pool cannot be less than maximum number of actions per account",
   308  		),
   309  	)
   310  }
   311  
   312  func TestValidateForkHeights(t *testing.T) {
   313  	r := require.New(t)
   314  
   315  	tests := []struct {
   316  		fork   string
   317  		err    error
   318  		errMsg string
   319  	}{
   320  		{
   321  			"Pacific", ErrInvalidCfg, "Pacific is heigher than Aleutian",
   322  		},
   323  		{
   324  			"Aleutian", ErrInvalidCfg, "Aleutian is heigher than Bering",
   325  		},
   326  		{
   327  			"Bering", ErrInvalidCfg, "Bering is heigher than Cook",
   328  		},
   329  		{
   330  			"Cook", ErrInvalidCfg, "Cook is heigher than Dardanelles",
   331  		},
   332  		{
   333  			"Dardanelles", ErrInvalidCfg, "Dardanelles is heigher than Daytona",
   334  		},
   335  		{
   336  			"Daytona", ErrInvalidCfg, "Daytona is heigher than Easter",
   337  		},
   338  		{
   339  			"Easter", ErrInvalidCfg, "Easter is heigher than FairbankMigration",
   340  		},
   341  		{
   342  			"FbkMigration", ErrInvalidCfg, "FairbankMigration is heigher than Fairbank",
   343  		},
   344  		{
   345  			"Fairbank", ErrInvalidCfg, "Fairbank is heigher than Greenland",
   346  		},
   347  		{
   348  			"Greenland", ErrInvalidCfg, "Greenland is heigher than Iceland",
   349  		},
   350  		{
   351  			"Iceland", ErrInvalidCfg, "Iceland is heigher than Jutland",
   352  		},
   353  		{
   354  			"Jutland", ErrInvalidCfg, "Jutland is heigher than Kamchatka",
   355  		},
   356  		{
   357  			"Kamchatka", ErrInvalidCfg, "Kamchatka is heigher than LordHowe",
   358  		},
   359  		{
   360  			"LordHowe", ErrInvalidCfg, "LordHowe is heigher than Midway",
   361  		},
   362  		{
   363  			"Midway", ErrInvalidCfg, "Midway is heigher than Newfoundland",
   364  		},
   365  		{
   366  			"Newfoundland", ErrInvalidCfg, "Newfoundland is heigher than Okhotsk",
   367  		},
   368  		{
   369  			"Okhotsk", ErrInvalidCfg, "Okhotsk is heigher than Palau",
   370  		},
   371  		{
   372  			"Palau", ErrInvalidCfg, "Palau is heigher than Quebec",
   373  		},
   374  		{
   375  			"Quebec", ErrInvalidCfg, "Quebec is heigher than Redsea",
   376  		},
   377  		{
   378  			"Redsea", ErrInvalidCfg, "Redsea is heigher than Sumatra",
   379  		},
   380  		{
   381  			"Sumatra", ErrInvalidCfg, "Sumatra is heigher than Tsunami",
   382  		},
   383  		{
   384  			"", nil, "",
   385  		},
   386  	}
   387  
   388  	for _, v := range tests {
   389  		cfg := newTestCfg(v.fork)
   390  		err := ValidateForkHeights(cfg)
   391  		r.Equal(v.err, errors.Cause(err))
   392  		if err != nil {
   393  			r.Contains(err.Error(), v.errMsg)
   394  		}
   395  	}
   396  }
   397  
   398  func newTestCfg(fork string) Config {
   399  	cfg := Default
   400  	switch fork {
   401  	case "Pacific":
   402  		cfg.Genesis.PacificBlockHeight = cfg.Genesis.AleutianBlockHeight + 1
   403  	case "Aleutian":
   404  		cfg.Genesis.AleutianBlockHeight = cfg.Genesis.BeringBlockHeight + 1
   405  	case "Bering":
   406  		cfg.Genesis.BeringBlockHeight = cfg.Genesis.CookBlockHeight + 1
   407  	case "Cook":
   408  		cfg.Genesis.CookBlockHeight = cfg.Genesis.DardanellesBlockHeight + 1
   409  	case "Dardanelles":
   410  		cfg.Genesis.DardanellesBlockHeight = cfg.Genesis.DaytonaBlockHeight + 1
   411  	case "Daytona":
   412  		cfg.Genesis.DaytonaBlockHeight = cfg.Genesis.EasterBlockHeight + 1
   413  	case "Easter":
   414  		cfg.Genesis.EasterBlockHeight = cfg.Genesis.FbkMigrationBlockHeight + 1
   415  	case "FbkMigration":
   416  		cfg.Genesis.FbkMigrationBlockHeight = cfg.Genesis.FairbankBlockHeight + 1
   417  	case "Fairbank":
   418  		cfg.Genesis.FairbankBlockHeight = cfg.Genesis.GreenlandBlockHeight + 1
   419  	case "Greenland":
   420  		cfg.Genesis.GreenlandBlockHeight = cfg.Genesis.IcelandBlockHeight + 1
   421  	case "Iceland":
   422  		cfg.Genesis.IcelandBlockHeight = cfg.Genesis.JutlandBlockHeight + 1
   423  	case "Jutland":
   424  		cfg.Genesis.JutlandBlockHeight = cfg.Genesis.KamchatkaBlockHeight + 1
   425  	case "Kamchatka":
   426  		cfg.Genesis.KamchatkaBlockHeight = cfg.Genesis.LordHoweBlockHeight + 1
   427  	case "LordHowe":
   428  		cfg.Genesis.LordHoweBlockHeight = cfg.Genesis.MidwayBlockHeight + 1
   429  	case "Midway":
   430  		cfg.Genesis.MidwayBlockHeight = cfg.Genesis.NewfoundlandBlockHeight + 1
   431  	case "Newfoundland":
   432  		cfg.Genesis.NewfoundlandBlockHeight = cfg.Genesis.OkhotskBlockHeight + 1
   433  	case "Okhotsk":
   434  		cfg.Genesis.OkhotskBlockHeight = cfg.Genesis.PalauBlockHeight + 1
   435  	case "Palau":
   436  		cfg.Genesis.PalauBlockHeight = cfg.Genesis.QuebecBlockHeight + 1
   437  	case "Quebec":
   438  		cfg.Genesis.QuebecBlockHeight = cfg.Genesis.RedseaBlockHeight + 1
   439  	case "Redsea":
   440  		cfg.Genesis.RedseaBlockHeight = cfg.Genesis.SumatraBlockHeight + 1
   441  	case "Sumatra":
   442  		cfg.Genesis.SumatraBlockHeight = cfg.Genesis.TsunamiBlockHeight + 1
   443  	}
   444  	return cfg
   445  }
   446  
   447  func TestNewSubConfigWithWrongConfigPath(t *testing.T) {
   448  	cfg, err := NewSub([]string{"", "wrong_path"})
   449  	require.Error(t, err)
   450  	require.Equal(t, Config{}, cfg)
   451  	if strings.Contains(err.Error(),
   452  		"open wrong_path: The system cannot find the file specified") == false { // for Windows
   453  		require.Contains(t, err.Error(), "open wrong_path: no such file or directory")
   454  	}
   455  }
   456  
   457  func TestNewSubConfigWithSubChainPath(t *testing.T) {
   458  	sk, cfgStr, err := generateProducerPrivKey()
   459  	require.NoError(t, err)
   460  	require.NoError(t, makePathAndWriteFile(cfgStr, "_subChainPath"))
   461  
   462  	defer resetPathValues(t, []string{"_subChainPath"})
   463  	cfg, err := NewSub([]string{"", _subChainPath})
   464  	require.NoError(t, err)
   465  	require.NotNil(t, cfg)
   466  	require.Equal(t, sk.HexString(), cfg.Chain.ProducerPrivKey)
   467  }
   468  
   469  func TestNewSubConfigWithSecret(t *testing.T) {
   470  	sk, cfgStr, err := generateProducerPrivKey()
   471  	require.NoError(t, err)
   472  	require.NoError(t, makePathAndWriteFile(cfgStr, "_subChainPath"))
   473  
   474  	require.NoError(t, makePathAndWriteFile(cfgStr, "_secretPath"))
   475  
   476  	defer resetPathValues(t, []string{"_subChainPath", "_secretPath"})
   477  
   478  	cfg, err := NewSub([]string{"", _subChainPath})
   479  	require.NoError(t, err)
   480  	require.NotNil(t, cfg)
   481  	require.Equal(t, sk.HexString(), cfg.Chain.ProducerPrivKey)
   482  }
   483  
   484  func TestNewSubConfigWithLookupEnv(t *testing.T) {
   485  	oldEnv, oldExist := os.LookupEnv("IOTEX_TEST_NODE_TYPE")
   486  
   487  	_, cfgStr, err := generateProducerPrivKey()
   488  	require.NoError(t, err)
   489  
   490  	require.NoError(t, makePathAndWriteFile(cfgStr, "_subChainPath"))
   491  
   492  	defer resetPathValuesWithLookupEnv(t, oldEnv, oldExist, "_subChainPath")
   493  
   494  	cfg, err := NewSub([]string{"", _subChainPath})
   495  	require.NoError(t, err)
   496  	require.NotNil(t, cfg)
   497  
   498  	err = os.Unsetenv("IOTEX_TEST_NODE_TYPE")
   499  	require.NoError(t, err)
   500  
   501  	cfg, err = NewSub([]string{"", _subChainPath})
   502  	require.NoError(t, err)
   503  	require.NotNil(t, cfg)
   504  }