package genesis_test import ( "context" "errors" "os" "path/filepath" "testing" "time" "github.com/OffchainLabs/prysm/v7/beacon-chain/state" state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native" "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/genesis" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v7/testing/require" "github.com/OffchainLabs/prysm/v7/testing/util" "github.com/ethereum/go-ethereum/common/hexutil" ) func TestInitialize(t *testing.T) { require.NoError(t, genesis.Initialize(t.Context(), "testdata")) require.Equal(t, params.MainnetName, params.BeaconConfig().ConfigName) } func TestEmbeddedMainnetHardcodedValues(t *testing.T) { // Initialize genesis with mainnet config to load embedded state require.NoError(t, genesis.Initialize(t.Context(), t.TempDir())) // Get the initialized genesis state state, err := genesis.State() require.NoError(t, err) require.NotNil(t, state) // Verify hardcoded validators root matches the computed value from the state expectedValidatorsRoot := [32]byte{75, 54, 61, 185, 78, 40, 97, 32, 215, 110, 185, 5, 52, 15, 221, 78, 84, 191, 233, 240, 107, 243, 63, 246, 207, 90, 210, 127, 81, 27, 254, 149} actualValidatorsRoot := state.GenesisValidatorsRoot() require.Equal(t, expectedValidatorsRoot, [32]byte(actualValidatorsRoot), "hardcoded validators root does not match embedded state") // Verify hardcoded genesis time matches the computed value from the state expectedTime := time.Unix(1606824023, 0) actualTime := state.GenesisTime() require.Equal(t, expectedTime, actualTime, "hardcoded genesis time does not match embedded state") } // mockProvider is a test provider for genesis state type mockProvider struct { name string err error state state.BeaconState } func (m *mockProvider) Genesis(context.Context) (state.BeaconState, error) { if m.err != nil { return nil, m.err } return m.state, nil } // createTestGenesisState creates a deterministic genesis state for testing. // This avoids using the embedded mainnet state which could cause false positives. func createTestGenesisState(t *testing.T, numValidators uint64, slot primitives.Slot) state.BeaconState { // Create a deterministic genesis state using test utilities deposits, _, err := util.DeterministicDepositsAndKeys(numValidators) require.NoError(t, err) eth1Data, err := util.DeterministicEth1Data(len(deposits)) require.NoError(t, err) // Create a minimal beacon state directly pb := ðpb.BeaconState{ Slot: slot, GenesisTime: uint64(time.Unix(2000000000, 0).Unix()), // Use a different time than mainnet GenesisValidatorsRoot: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, Eth1Data: eth1Data, Validators: make([]*ethpb.Validator, numValidators), Balances: make([]uint64, numValidators), Fork: ðpb.Fork{ PreviousVersion: params.BeaconConfig().GenesisForkVersion, CurrentVersion: params.BeaconConfig().GenesisForkVersion, Epoch: 0, }, LatestBlockHeader: ðpb.BeaconBlockHeader{ Slot: 0, ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32), }, BlockRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot), StateRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot), RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), JustificationBits: []byte{0}, PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, } // Initialize validators and balances for i := range numValidators { pb.Validators[i] = ðpb.Validator{ PublicKey: deposits[i].Data.PublicKey, WithdrawalCredentials: deposits[i].Data.WithdrawalCredentials, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 0, ActivationEpoch: 0, ExitEpoch: params.BeaconConfig().FarFutureEpoch, WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch, } pb.Balances[i] = params.BeaconConfig().MaxEffectiveBalance } // Initialize arrays with proper sizes for i := 0; i < len(pb.BlockRoots); i++ { pb.BlockRoots[i] = make([]byte, 32) } for i := 0; i < len(pb.StateRoots); i++ { pb.StateRoots[i] = make([]byte, 32) } for i := 0; i < len(pb.RandaoMixes); i++ { pb.RandaoMixes[i] = make([]byte, 32) } st, err := state_native.InitializeFromProtoUnsafePhase0(pb) require.NoError(t, err) return st } func TestInitializeWithProviders(t *testing.T) { originalConfig := params.BeaconConfig().Copy() defer params.OverrideBeaconConfig(originalConfig) t.Run("providers_used_when_no_embedded_or_file", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MainnetConfig().Copy() customConfig.ConfigName = "test-config-no-embedded" params.OverrideBeaconConfig(customConfig) // Use a deterministic test state instead of mainnet to avoid false positives testState := createTestGenesisState(t, 64, 0) provider := &mockProvider{ state: testState, name: "test-provider", } err := genesis.Initialize(t.Context(), t.TempDir(), provider) require.NoError(t, err) // Verify the state was stored storedState, err := genesis.State() require.NoError(t, err) require.NotNil(t, storedState) require.DeepEqual(t, testState.GenesisValidatorsRoot(), storedState.GenesisValidatorsRoot()) // Verify it's not the mainnet state require.NotEqual(t, uint64(1606824023), storedState.GenesisTime().Unix()) }) t.Run("multiple_providers_first_success_wins", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MainnetConfig().Copy() customConfig.ConfigName = "test-config-multiple-providers" params.OverrideBeaconConfig(customConfig) // Use deterministic test states with different slots state1 := createTestGenesisState(t, 64, 50) state2 := createTestGenesisState(t, 32, 100) // Create providers - first fails, second succeeds failingProvider := &mockProvider{ err: errors.New("provider failed"), name: "failing-provider", } successProvider1 := &mockProvider{ state: state1, name: "success-provider-1", } successProvider2 := &mockProvider{ state: state2, name: "success-provider-2", } // Initialize with multiple providers err := genesis.Initialize(t.Context(), t.TempDir(), failingProvider, successProvider1, successProvider2) require.NoError(t, err) // Verify first successful provider's state was used storedState, err := genesis.State() require.NoError(t, err) require.NotNil(t, storedState) // state1 has slot 50, state2 has slot 100 require.Equal(t, primitives.Slot(50), storedState.Slot()) // Verify it's state1 by checking validator count require.Equal(t, 64, storedState.NumValidators()) }) t.Run("all_providers_fail_returns_error", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MinimalSpecConfig().Copy() customConfig.ConfigName = "test-config-all-fail" params.OverrideBeaconConfig(customConfig) // Create failing providers provider1 := &mockProvider{ err: errors.New("provider 1 failed"), name: "failing-provider-1", } provider2 := &mockProvider{ err: errors.New("provider 2 failed"), name: "failing-provider-2", } // Initialize should fail when all providers fail err := genesis.Initialize(t.Context(), t.TempDir(), provider1, provider2) require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) }) t.Run("no_providers_and_no_data_returns_error", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MinimalSpecConfig().Copy() customConfig.ConfigName = "test-config-no-providers" params.OverrideBeaconConfig(customConfig) // Initialize with no providers should fail err := genesis.Initialize(t.Context(), t.TempDir()) require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) }) t.Run("provider_returns_nil_state", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MinimalSpecConfig().Copy() customConfig.ConfigName = "test-config-nil-state" params.OverrideBeaconConfig(customConfig) // Create provider that returns nil state provider := &mockProvider{ state: nil, name: "nil-state-provider", } // Initialize should fail err := genesis.Initialize(t.Context(), t.TempDir(), provider) require.ErrorIs(t, err, genesis.ErrGenesisStateNotInitialized) }) t.Run("empty_dir_path_with_providers", func(t *testing.T) { // Use a custom config that won't have embedded data customConfig := params.MainnetConfig().Copy() customConfig.ConfigName = "test-config-empty-dir" params.OverrideBeaconConfig(customConfig) // Use a deterministic test state testState := createTestGenesisState(t, 16, 0) // Create successful provider provider := &mockProvider{ state: testState, name: "test-provider", } // Initialize with empty dir should fail err := genesis.Initialize(t.Context(), "", provider) require.ErrorIs(t, err, genesis.ErrFilePathUnset) }) t.Run("genesis_file_takes_precedence_over_providers", func(t *testing.T) { // Create temp directory with genesis file tmpDir := t.TempDir() // Use a custom config that won't have embedded data customConfig := params.MainnetConfig().Copy() customConfig.ConfigName = "test-config-file-precedence" params.OverrideBeaconConfig(customConfig) // Create deterministic test states for file and provider fileState := createTestGenesisState(t, 128, 75) fileTime := time.Unix(1234567890, 0) require.NoError(t, fileState.SetGenesisTime(fileTime)) // Save state to file with proper naming convention marshaled, err := fileState.MarshalSSZ() require.NoError(t, err) gvr := fileState.GenesisValidatorsRoot() gvrHex := hexutil.Encode(gvr) filename := filepath.Join(tmpDir, "genesis-1234567890-"+gvrHex+".ssz") err = os.WriteFile(filename, marshaled, 0644) require.NoError(t, err) // Create a provider with different state providerState := createTestGenesisState(t, 256, 200) provider := &mockProvider{ state: providerState, name: "test-provider", } // Initialize should use file, not provider err = genesis.Initialize(t.Context(), tmpDir, provider) require.NoError(t, err) // Verify file state was used, not provider state storedState, err := genesis.State() require.NoError(t, err) require.NotNil(t, storedState) // fileState has slot 75 and 128 validators, providerState has slot 200 and 256 validators require.Equal(t, primitives.Slot(75), storedState.Slot()) require.Equal(t, 128, storedState.NumValidators()) require.Equal(t, fileTime, storedState.GenesisTime()) }) }