github.com/ava-labs/avalanchego@v1.11.11/genesis/genesis_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package genesis
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/stretchr/testify/require"
    17  
    18  	_ "embed"
    19  
    20  	"github.com/ava-labs/avalanchego/ids"
    21  	"github.com/ava-labs/avalanchego/utils/constants"
    22  	"github.com/ava-labs/avalanchego/utils/hashing"
    23  	"github.com/ava-labs/avalanchego/utils/perms"
    24  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis"
    25  )
    26  
    27  var (
    28  	//go:embed genesis_test.json
    29  	customGenesisConfigJSON  []byte
    30  	invalidGenesisConfigJSON = []byte(`{
    31  		"networkID": 9999}}}}
    32  	}`)
    33  
    34  	genesisStakingCfg = &StakingConfig{
    35  		MaxStakeDuration: 365 * 24 * time.Hour,
    36  	}
    37  )
    38  
    39  func TestValidateConfig(t *testing.T) {
    40  	tests := map[string]struct {
    41  		networkID   uint32
    42  		config      *Config
    43  		expectedErr error
    44  	}{
    45  		"mainnet": {
    46  			networkID:   1,
    47  			config:      &MainnetConfig,
    48  			expectedErr: nil,
    49  		},
    50  		"fuji": {
    51  			networkID:   5,
    52  			config:      &FujiConfig,
    53  			expectedErr: nil,
    54  		},
    55  		"local": {
    56  			networkID:   12345,
    57  			config:      &LocalConfig,
    58  			expectedErr: nil,
    59  		},
    60  		"mainnet (networkID mismatch)": {
    61  			networkID:   2,
    62  			config:      &MainnetConfig,
    63  			expectedErr: errConflictingNetworkIDs,
    64  		},
    65  		"invalid start time": {
    66  			networkID: 12345,
    67  			config: func() *Config {
    68  				thisConfig := LocalConfig
    69  				thisConfig.StartTime = 999999999999999
    70  				return &thisConfig
    71  			}(),
    72  			expectedErr: errFutureStartTime,
    73  		},
    74  		"no initial supply": {
    75  			networkID: 12345,
    76  			config: func() *Config {
    77  				thisConfig := LocalConfig
    78  				thisConfig.Allocations = []Allocation{}
    79  				return &thisConfig
    80  			}(),
    81  			expectedErr: errNoSupply,
    82  		},
    83  		"no initial stakers": {
    84  			networkID: 12345,
    85  			config: func() *Config {
    86  				thisConfig := LocalConfig
    87  				thisConfig.InitialStakers = []Staker{}
    88  				return &thisConfig
    89  			}(),
    90  			expectedErr: errNoStakers,
    91  		},
    92  		"invalid initial stake duration": {
    93  			networkID: 12345,
    94  			config: func() *Config {
    95  				thisConfig := LocalConfig
    96  				thisConfig.InitialStakeDuration = 0
    97  				return &thisConfig
    98  			}(),
    99  			expectedErr: errNoStakeDuration,
   100  		},
   101  		"too large initial stake duration": {
   102  			networkID: 12345,
   103  			config: func() *Config {
   104  				thisConfig := LocalConfig
   105  				thisConfig.InitialStakeDuration = uint64(genesisStakingCfg.MaxStakeDuration+time.Second) / uint64(time.Second)
   106  				return &thisConfig
   107  			}(),
   108  			expectedErr: errStakeDurationTooHigh,
   109  		},
   110  		"invalid stake offset": {
   111  			networkID: 12345,
   112  			config: func() *Config {
   113  				thisConfig := LocalConfig
   114  				thisConfig.InitialStakeDurationOffset = 100000000
   115  				return &thisConfig
   116  			}(),
   117  			expectedErr: errInitialStakeDurationTooLow,
   118  		},
   119  		"empty initial staked funds": {
   120  			networkID: 12345,
   121  			config: func() *Config {
   122  				thisConfig := LocalConfig
   123  				thisConfig.InitialStakedFunds = []ids.ShortID(nil)
   124  				return &thisConfig
   125  			}(),
   126  			expectedErr: errNoInitiallyStakedFunds,
   127  		},
   128  		"duplicate initial staked funds": {
   129  			networkID: 12345,
   130  			config: func() *Config {
   131  				thisConfig := LocalConfig
   132  				thisConfig.InitialStakedFunds = append(thisConfig.InitialStakedFunds, thisConfig.InitialStakedFunds[0])
   133  				return &thisConfig
   134  			}(),
   135  			expectedErr: errDuplicateInitiallyStakedAddress,
   136  		},
   137  		"initial staked funds not in allocations": {
   138  			networkID: 5,
   139  			config: func() *Config {
   140  				thisConfig := FujiConfig
   141  				thisConfig.InitialStakedFunds = append(thisConfig.InitialStakedFunds, LocalConfig.InitialStakedFunds[0])
   142  				return &thisConfig
   143  			}(),
   144  			expectedErr: errNoAllocationToStake,
   145  		},
   146  		"empty C-Chain genesis": {
   147  			networkID: 12345,
   148  			config: func() *Config {
   149  				thisConfig := LocalConfig
   150  				thisConfig.CChainGenesis = ""
   151  				return &thisConfig
   152  			}(),
   153  			expectedErr: errNoCChainGenesis,
   154  		},
   155  		"empty message": {
   156  			networkID: 12345,
   157  			config: func() *Config {
   158  				thisConfig := LocalConfig
   159  				thisConfig.Message = ""
   160  				return &thisConfig
   161  			}(),
   162  			expectedErr: nil,
   163  		},
   164  	}
   165  
   166  	for name, test := range tests {
   167  		t.Run(name, func(t *testing.T) {
   168  			err := validateConfig(test.networkID, test.config, genesisStakingCfg)
   169  			require.ErrorIs(t, err, test.expectedErr)
   170  		})
   171  	}
   172  }
   173  
   174  func TestGenesisFromFile(t *testing.T) {
   175  	tests := map[string]struct {
   176  		networkID       uint32
   177  		customConfig    []byte
   178  		missingFilepath string
   179  		expectedErr     error
   180  		expectedHash    string
   181  	}{
   182  		"mainnet": {
   183  			networkID:    constants.MainnetID,
   184  			customConfig: customGenesisConfigJSON,
   185  			expectedErr:  errOverridesStandardNetworkConfig,
   186  		},
   187  		"fuji": {
   188  			networkID:    constants.FujiID,
   189  			customConfig: customGenesisConfigJSON,
   190  			expectedErr:  errOverridesStandardNetworkConfig,
   191  		},
   192  		"fuji (with custom specified)": {
   193  			networkID:    constants.FujiID,
   194  			customConfig: localGenesisConfigJSON, // won't load
   195  			expectedErr:  errOverridesStandardNetworkConfig,
   196  		},
   197  		"local": {
   198  			networkID:    constants.LocalID,
   199  			customConfig: customGenesisConfigJSON,
   200  			expectedErr:  errOverridesStandardNetworkConfig,
   201  		},
   202  		"local (with custom specified)": {
   203  			networkID:    constants.LocalID,
   204  			customConfig: customGenesisConfigJSON,
   205  			expectedErr:  errOverridesStandardNetworkConfig,
   206  		},
   207  		"custom": {
   208  			networkID:    9999,
   209  			customConfig: customGenesisConfigJSON,
   210  			expectedErr:  nil,
   211  			expectedHash: "a1d1838586db85fe94ab1143560c3356df9ba2445794b796bba050be89f4fcb4",
   212  		},
   213  		"custom (networkID mismatch)": {
   214  			networkID:    9999,
   215  			customConfig: localGenesisConfigJSON,
   216  			expectedErr:  errConflictingNetworkIDs,
   217  		},
   218  		"custom (invalid format)": {
   219  			networkID:    9999,
   220  			customConfig: invalidGenesisConfigJSON,
   221  			expectedErr:  errInvalidGenesisJSON,
   222  		},
   223  		"custom (missing filepath)": {
   224  			networkID:       9999,
   225  			missingFilepath: "missing.json",
   226  			expectedErr:     os.ErrNotExist,
   227  		},
   228  	}
   229  
   230  	for name, test := range tests {
   231  		t.Run(name, func(t *testing.T) {
   232  			require := require.New(t)
   233  
   234  			// test loading of genesis from file
   235  			var customFile string
   236  			if len(test.customConfig) > 0 {
   237  				customFile = filepath.Join(t.TempDir(), "config.json")
   238  				require.NoError(perms.WriteFile(customFile, test.customConfig, perms.ReadWrite))
   239  			}
   240  
   241  			if len(test.missingFilepath) > 0 {
   242  				customFile = test.missingFilepath
   243  			}
   244  
   245  			genesisBytes, _, err := FromFile(test.networkID, customFile, genesisStakingCfg)
   246  			require.ErrorIs(err, test.expectedErr)
   247  			if test.expectedErr == nil {
   248  				genesisHash := hex.EncodeToString(hashing.ComputeHash256(genesisBytes))
   249  				require.Equal(test.expectedHash, genesisHash, "genesis hash mismatch")
   250  
   251  				_, err = genesis.Parse(genesisBytes)
   252  				require.NoError(err)
   253  			}
   254  		})
   255  	}
   256  }
   257  
   258  func TestGenesisFromFlag(t *testing.T) {
   259  	tests := map[string]struct {
   260  		networkID    uint32
   261  		customConfig []byte
   262  		expectedErr  error
   263  		expectedHash string
   264  	}{
   265  		"mainnet": {
   266  			networkID:   constants.MainnetID,
   267  			expectedErr: errOverridesStandardNetworkConfig,
   268  		},
   269  		"fuji": {
   270  			networkID:   constants.FujiID,
   271  			expectedErr: errOverridesStandardNetworkConfig,
   272  		},
   273  		"local": {
   274  			networkID:   constants.LocalID,
   275  			expectedErr: errOverridesStandardNetworkConfig,
   276  		},
   277  		"local (with custom specified)": {
   278  			networkID:    constants.LocalID,
   279  			customConfig: customGenesisConfigJSON,
   280  			expectedErr:  errOverridesStandardNetworkConfig,
   281  		},
   282  		"custom": {
   283  			networkID:    9999,
   284  			customConfig: customGenesisConfigJSON,
   285  			expectedErr:  nil,
   286  			expectedHash: "a1d1838586db85fe94ab1143560c3356df9ba2445794b796bba050be89f4fcb4",
   287  		},
   288  		"custom (networkID mismatch)": {
   289  			networkID:    9999,
   290  			customConfig: localGenesisConfigJSON,
   291  			expectedErr:  errConflictingNetworkIDs,
   292  		},
   293  		"custom (invalid format)": {
   294  			networkID:    9999,
   295  			customConfig: invalidGenesisConfigJSON,
   296  			expectedErr:  errInvalidGenesisJSON,
   297  		},
   298  		"custom (missing content)": {
   299  			networkID:   9999,
   300  			expectedErr: errInvalidGenesisJSON,
   301  		},
   302  	}
   303  
   304  	for name, test := range tests {
   305  		t.Run(name, func(t *testing.T) {
   306  			require := require.New(t)
   307  
   308  			// test loading of genesis content from flag/env-var
   309  			var genBytes []byte
   310  			if len(test.customConfig) == 0 {
   311  				// try loading a default config
   312  				var err error
   313  				switch test.networkID {
   314  				case constants.MainnetID:
   315  					genBytes, err = json.Marshal(&MainnetConfig)
   316  					require.NoError(err)
   317  				case constants.TestnetID:
   318  					genBytes, err = json.Marshal(&FujiConfig)
   319  					require.NoError(err)
   320  				case constants.LocalID:
   321  					genBytes, err = json.Marshal(&LocalConfig)
   322  					require.NoError(err)
   323  				default:
   324  					genBytes = make([]byte, 0)
   325  				}
   326  			} else {
   327  				genBytes = test.customConfig
   328  			}
   329  			content := base64.StdEncoding.EncodeToString(genBytes)
   330  
   331  			genesisBytes, _, err := FromFlag(test.networkID, content, genesisStakingCfg)
   332  			require.ErrorIs(err, test.expectedErr)
   333  			if test.expectedErr == nil {
   334  				genesisHash := hex.EncodeToString(hashing.ComputeHash256(genesisBytes))
   335  				require.Equal(test.expectedHash, genesisHash, "genesis hash mismatch")
   336  
   337  				_, err = genesis.Parse(genesisBytes)
   338  				require.NoError(err)
   339  			}
   340  		})
   341  	}
   342  }
   343  
   344  func TestGenesis(t *testing.T) {
   345  	tests := []struct {
   346  		config     *Config
   347  		expectedID string
   348  	}{
   349  		{
   350  			config:     &MainnetConfig,
   351  			expectedID: "UUvXi6j7QhVvgpbKM89MP5HdrxKm9CaJeHc187TsDNf8nZdLk",
   352  		},
   353  		{
   354  			config:     &FujiConfig,
   355  			expectedID: "MSj6o9TpezwsQx4Tv7SHqpVvCbJ8of1ikjsqPZ1bKRjc9zBy3",
   356  		},
   357  		{
   358  			config:     &unmodifiedLocalConfig,
   359  			expectedID: "23DnViuN2kgePiBN4JxZXh1VrfXca2rwUp6XrKgNGdj3TSQjiN",
   360  		},
   361  	}
   362  	for _, test := range tests {
   363  		t.Run(constants.NetworkIDToNetworkName[test.config.NetworkID], func(t *testing.T) {
   364  			require := require.New(t)
   365  
   366  			genesisBytes, _, err := FromConfig(test.config)
   367  			require.NoError(err)
   368  
   369  			var genesisID ids.ID = hashing.ComputeHash256Array(genesisBytes)
   370  			require.Equal(test.expectedID, genesisID.String())
   371  		})
   372  	}
   373  }
   374  
   375  func TestVMGenesis(t *testing.T) {
   376  	type vmTest struct {
   377  		vmID       ids.ID
   378  		expectedID string
   379  	}
   380  	tests := []struct {
   381  		networkID uint32
   382  		vmTest    []vmTest
   383  	}{
   384  		{
   385  			networkID: constants.MainnetID,
   386  			vmTest: []vmTest{
   387  				{
   388  					vmID:       constants.AVMID,
   389  					expectedID: "2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM",
   390  				},
   391  				{
   392  					vmID:       constants.EVMID,
   393  					expectedID: "2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5",
   394  				},
   395  			},
   396  		},
   397  		{
   398  			networkID: constants.FujiID,
   399  			vmTest: []vmTest{
   400  				{
   401  					vmID:       constants.AVMID,
   402  					expectedID: "2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm",
   403  				},
   404  				{
   405  					vmID:       constants.EVMID,
   406  					expectedID: "yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp",
   407  				},
   408  			},
   409  		},
   410  		{
   411  			networkID: constants.LocalID,
   412  			vmTest: []vmTest{
   413  				{
   414  					vmID:       constants.AVMID,
   415  					expectedID: "2eNy1mUFdmaxXNj1eQHUe7Np4gju9sJsEtWQ4MX3ToiNKuADed",
   416  				},
   417  				{
   418  					vmID:       constants.EVMID,
   419  					expectedID: "2CA6j5zYzasynPsFeNoqWkmTCt3VScMvXUZHbfDJ8k3oGzAPtU",
   420  				},
   421  			},
   422  		},
   423  	}
   424  
   425  	for _, test := range tests {
   426  		for _, vmTest := range test.vmTest {
   427  			name := fmt.Sprintf("%s-%s",
   428  				constants.NetworkIDToNetworkName[test.networkID],
   429  				vmTest.vmID,
   430  			)
   431  			t.Run(name, func(t *testing.T) {
   432  				require := require.New(t)
   433  
   434  				config := GetConfig(test.networkID)
   435  				genesisBytes, _, err := FromConfig(config)
   436  				require.NoError(err)
   437  
   438  				genesisTx, err := VMGenesis(genesisBytes, vmTest.vmID)
   439  				require.NoError(err)
   440  
   441  				require.Equal(
   442  					vmTest.expectedID,
   443  					genesisTx.ID().String(),
   444  					"%s genesisID with networkID %d mismatch",
   445  					vmTest.vmID,
   446  					test.networkID,
   447  				)
   448  			})
   449  		}
   450  	}
   451  }
   452  
   453  func TestAVAXAssetID(t *testing.T) {
   454  	tests := []struct {
   455  		networkID  uint32
   456  		expectedID string
   457  	}{
   458  		{
   459  			networkID:  constants.MainnetID,
   460  			expectedID: "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z",
   461  		},
   462  		{
   463  			networkID:  constants.FujiID,
   464  			expectedID: "U8iRqJoiJm8xZHAacmvYyZVwqQx6uDNtQeP3CQ6fcgQk3JqnK",
   465  		},
   466  		{
   467  			networkID:  constants.LocalID,
   468  			expectedID: "2fombhL7aGPwj3KH4bfrmJwW6PVnMobf9Y2fn9GwxiAAJyFDbe",
   469  		},
   470  	}
   471  
   472  	for _, test := range tests {
   473  		t.Run(constants.NetworkIDToNetworkName[test.networkID], func(t *testing.T) {
   474  			require := require.New(t)
   475  
   476  			config := GetConfig(test.networkID)
   477  			_, avaxAssetID, err := FromConfig(config)
   478  			require.NoError(err)
   479  
   480  			require.Equal(
   481  				test.expectedID,
   482  				avaxAssetID.String(),
   483  				"AVAX assetID with networkID %d mismatch",
   484  				test.networkID,
   485  			)
   486  		})
   487  	}
   488  }