diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index 866de19e2e..4a800142a2 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -283,3 +283,10 @@ type GetPendingPartialWithdrawalsResponse struct { Finalized bool `json:"finalized"` Data []*PendingPartialWithdrawal `json:"data"` } + +type GetProposerLookaheadResponse struct { + Version string `json:"version"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data []string `json:"data"` // validator indexes +} diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index d5182a97ae..63d8549187 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -985,6 +985,16 @@ func (s *Service) beaconEndpoints( handler: server.GetPendingPartialWithdrawals, methods: []string{http.MethodGet}, }, + { + template: "/eth/v1/beacon/states/{state_id}/proposer_lookahead", + name: namespace + ".GetProposerLookahead", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}), + middleware.AcceptEncodingHeaderHandler(), + }, + handler: server.GetProposerLookahead, + methods: []string{http.MethodGet}, + }, } } diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index cf6487e0be..e37f304662 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -32,6 +32,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/beacon/states/{state_id}/pending_deposits": {http.MethodGet}, "/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals": {http.MethodGet}, "/eth/v1/beacon/states/{state_id}/pending_consolidations": {http.MethodGet}, + "/eth/v1/beacon/states/{state_id}/proposer_lookahead": {http.MethodGet}, "/eth/v1/beacon/headers": {http.MethodGet}, "/eth/v1/beacon/headers/{block_id}": {http.MethodGet}, "/eth/v1/beacon/blinded_blocks": {http.MethodPost}, diff --git a/beacon-chain/rpc/eth/beacon/BUILD.bazel b/beacon-chain/rpc/eth/beacon/BUILD.bazel index 824faeb098..09b40a6cc2 100644 --- a/beacon-chain/rpc/eth/beacon/BUILD.bazel +++ b/beacon-chain/rpc/eth/beacon/BUILD.bazel @@ -123,6 +123,7 @@ go_test( "@com_github_crate_crypto_go_kzg_4844//:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_fastssz//:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_stretchr_testify//mock:go_default_library", diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go index e903a5917b..ddffa24a75 100644 --- a/beacon-chain/rpc/eth/beacon/handlers.go +++ b/beacon-chain/rpc/eth/beacon/handlers.go @@ -1790,6 +1790,63 @@ func (s *Server) GetPendingPartialWithdrawals(w http.ResponseWriter, r *http.Req } } +func (s *Server) GetProposerLookahead(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.GetProposerLookahead") + defer span.End() + + stateId := r.PathValue("state_id") + if stateId == "" { + httputil.HandleError(w, "state_id is required in URL params", http.StatusBadRequest) + return + } + st, err := s.Stater.State(ctx, []byte(stateId)) + if err != nil { + shared.WriteStateFetchError(w, err) + return + } + if st.Version() < version.Fulu { + httputil.HandleError(w, "state_id is prior to fulu", http.StatusBadRequest) + return + } + pl, err := st.ProposerLookahead() + if err != nil { + httputil.HandleError(w, "Could not get proposer look ahead: "+err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set(api.VersionHeader, version.String(st.Version())) + if httputil.RespondWithSsz(r) { + sszLen := (*primitives.ValidatorIndex)(nil).SizeSSZ() + sszData := make([]byte, len(pl)*sszLen) + for i, idx := range pl { + copy(sszData[i*sszLen:(i+1)*sszLen], ssz.MarshalUint64([]byte{}, uint64(idx))) + } + httputil.WriteSsz(w, sszData) + } else { + isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB) + if err != nil { + httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError) + return + } + blockRoot, err := st.LatestBlockHeader().HashTreeRoot() + if err != nil { + httputil.HandleError(w, "Could not calculate root of latest block header: "+err.Error(), http.StatusInternalServerError) + return + } + isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot) + vi := make([]string, len(pl)) + for i, v := range pl { + vi[i] = strconv.FormatUint(uint64(v), 10) + } + resp := structs.GetProposerLookaheadResponse{ + Version: version.String(st.Version()), + ExecutionOptimistic: isOptimistic, + Finalized: isFinalized, + Data: vi, + } + httputil.WriteJson(w, resp) + } +} + // SerializeItems serializes a slice of items, each of which implements the MarshalSSZ method, // into a single byte array. func serializeItems[T interface{ MarshalSSZ() ([]byte, error) }](items []T) ([]byte, error) { diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index 28e1f5a2f4..35efeeea04 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -43,6 +43,7 @@ import ( GoKZG "github.com/crate-crypto/go-kzg-4844" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" + ssz "github.com/prysmaticlabs/fastssz" "github.com/prysmaticlabs/go-bitfield" logTest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/mock" @@ -5363,3 +5364,190 @@ func TestGetPendingPartialWithdrawals(t *testing.T) { require.Equal(t, true, resp.Finalized) }) } + +func TestGetProposerLookahead(t *testing.T) { + numValidators := 50 + // Create a Fulu state with proposer lookahead data + st, _ := util.DeterministicGenesisStateFulu(t, uint64(numValidators)) + lookaheadSize := int(params.BeaconConfig().MinSeedLookahead+1) * int(params.BeaconConfig().SlotsPerEpoch) + lookahead := make([]primitives.ValidatorIndex, lookaheadSize) + for i := 0; i < lookaheadSize; i++ { + lookahead[i] = primitives.ValidatorIndex(i % numValidators) // Cycle through validators + } + + require.NoError(t, st.SetProposerLookahead(lookahead)) + + chainService := &chainMock.ChainService{ + Optimistic: false, + FinalizedRoots: map[[32]byte]bool{}, + } + server := &Server{ + Stater: &testutil.MockStater{ + BeaconState: st, + }, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + t.Run("json response", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + req.SetPathValue("state_id", "head") + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + server.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "fulu", rec.Header().Get(api.VersionHeader)) + + var resp structs.GetProposerLookaheadResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + + expectedVersion := version.String(st.Version()) + require.Equal(t, expectedVersion, resp.Version) + require.Equal(t, false, resp.ExecutionOptimistic) + require.Equal(t, false, resp.Finalized) + + // Verify the data + require.Equal(t, lookaheadSize, len(resp.Data)) + for i := 0; i < lookaheadSize; i++ { + expectedIdx := strconv.FormatUint(uint64(i%numValidators), 10) + require.Equal(t, expectedIdx, resp.Data[i]) + } + }) + + t.Run("ssz response", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + req.Header.Set("Accept", "application/octet-stream") + req.SetPathValue("state_id", "head") + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + server.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "fulu", rec.Header().Get(api.VersionHeader)) + responseBytes := rec.Body.Bytes() + validatorIndexSize := (*primitives.ValidatorIndex)(nil).SizeSSZ() + require.Equal(t, len(responseBytes), validatorIndexSize*lookaheadSize) + + recoveredIndices := make([]primitives.ValidatorIndex, lookaheadSize) + for i := 0; i < lookaheadSize; i++ { + start := i * validatorIndexSize + end := start + validatorIndexSize + + idx := ssz.UnmarshallUint64(responseBytes[start:end]) + recoveredIndices[i] = primitives.ValidatorIndex(idx) + } + require.DeepEqual(t, lookahead, recoveredIndices) + }) + + t.Run("pre fulu state", func(t *testing.T) { + preEplusSt, _ := util.DeterministicGenesisStateElectra(t, 1) + preFuluServer := &Server{ + Stater: &testutil.MockStater{ + BeaconState: preEplusSt, + }, + OptimisticModeFetcher: chainService, + FinalizationFetcher: chainService, + } + + // Test JSON request + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + req.SetPathValue("state_id", "head") + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + preFuluServer.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusBadRequest, rec.Code) + + var errResp struct { + Code int `json:"code"` + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.Equal(t, "state_id is prior to fulu", errResp.Message) + + // Test SSZ request + sszReq := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + sszReq.Header.Set("Accept", "application/octet-stream") + sszReq.SetPathValue("state_id", "head") + sszRec := httptest.NewRecorder() + sszRec.Body = new(bytes.Buffer) + + preFuluServer.GetProposerLookahead(sszRec, sszReq) + require.Equal(t, http.StatusBadRequest, sszRec.Code) + + var sszErrResp struct { + Code int `json:"code"` + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(sszRec.Body.Bytes(), &sszErrResp)) + require.Equal(t, "state_id is prior to fulu", sszErrResp.Message) + }) + + t.Run("missing state_id parameter", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + server.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusBadRequest, rec.Code) + + var errResp struct { + Code int `json:"code"` + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.Equal(t, "state_id is required in URL params", errResp.Message) + }) + + t.Run("optimistic node", func(t *testing.T) { + optimisticChainService := &chainMock.ChainService{ + Optimistic: true, + FinalizedRoots: map[[32]byte]bool{}, + } + optimisticServer := &Server{ + Stater: server.Stater, + OptimisticModeFetcher: optimisticChainService, + FinalizationFetcher: optimisticChainService, + } + + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + req.SetPathValue("state_id", "head") + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + optimisticServer.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + var resp structs.GetProposerLookaheadResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, true, resp.ExecutionOptimistic) + }) + + t.Run("finalized node", func(t *testing.T) { + blockRoot, err := st.LatestBlockHeader().HashTreeRoot() + require.NoError(t, err) + + finalizedChainService := &chainMock.ChainService{ + Optimistic: false, + FinalizedRoots: map[[32]byte]bool{blockRoot: true}, + } + finalizedServer := &Server{ + Stater: server.Stater, + OptimisticModeFetcher: finalizedChainService, + FinalizationFetcher: finalizedChainService, + } + + req := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/proposer_lookahead", nil) + req.SetPathValue("state_id", "head") + rec := httptest.NewRecorder() + rec.Body = new(bytes.Buffer) + + finalizedServer.GetProposerLookahead(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + var resp structs.GetProposerLookaheadResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, true, resp.Finalized) + }) +} diff --git a/changelog/james-prysm_proposer-lookahead-api.md b/changelog/james-prysm_proposer-lookahead-api.md new file mode 100644 index 0000000000..fadad42a7d --- /dev/null +++ b/changelog/james-prysm_proposer-lookahead-api.md @@ -0,0 +1,3 @@ +### Added + +- Implements the `/eth/v1/beacon/states/{state_id}/proposer_lookahead` beacon api endpoint. \ No newline at end of file diff --git a/testing/util/BUILD.bazel b/testing/util/BUILD.bazel index 87218c6aa6..fa92f19f7c 100644 --- a/testing/util/BUILD.bazel +++ b/testing/util/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "electra_state.go", "fulu.go", "fulu_block.go", + "fulu_state.go", "helpers.go", "lightclient.go", "logging.go", diff --git a/testing/util/fulu_state.go b/testing/util/fulu_state.go new file mode 100644 index 0000000000..ec77d9b56e --- /dev/null +++ b/testing/util/fulu_state.go @@ -0,0 +1,308 @@ +package util + +import ( + "context" + "testing" + + "github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers" + "github.com/OffchainLabs/prysm/v6/beacon-chain/state" + state_native "github.com/OffchainLabs/prysm/v6/beacon-chain/state/state-native" + "github.com/OffchainLabs/prysm/v6/beacon-chain/state/stateutil" + fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams" + "github.com/OffchainLabs/prysm/v6/config/params" + "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" + "github.com/OffchainLabs/prysm/v6/crypto/bls" + enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1" + ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" + "github.com/OffchainLabs/prysm/v6/time/slots" + "github.com/pkg/errors" +) + +// DeterministicGenesisStateFulu returns a genesis state in Fulu format made using the deterministic deposits. +func DeterministicGenesisStateFulu(t testing.TB, numValidators uint64) (state.BeaconState, []bls.SecretKey) { + deposits, privKeys, err := DeterministicDepositsAndKeys(numValidators) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get %d deposits", numValidators)) + } + eth1Data, err := DeterministicEth1Data(len(deposits)) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get eth1data for %d deposits", numValidators)) + } + beaconState, err := genesisBeaconStateFulu(t.Context(), deposits, uint64(0), eth1Data) + if err != nil { + t.Fatal(errors.Wrapf(err, "failed to get genesis beacon state of %d validators", numValidators)) + } + if err := setKeysToActiveFulu(beaconState); err != nil { + t.Fatal(errors.Wrapf(err, "failed to set keys to active")) + } + resetCache() + return beaconState, privKeys +} + +// setKeysToActiveFulu is a function to set the validators to active post fulu, fulu no longer processes deposits based on eth1data +func setKeysToActiveFulu(beaconState state.BeaconState) error { + vals := make([]*ethpb.Validator, len(beaconState.Validators())) + for i, val := range beaconState.Validators() { + val.ActivationEpoch = 0 + val.EffectiveBalance = params.BeaconConfig().MinActivationBalance + vals[i] = val + } + return beaconState.SetValidators(vals) +} + +// genesisBeaconStateFulu returns the genesis beacon state. +func genesisBeaconStateFulu(ctx context.Context, deposits []*ethpb.Deposit, genesisTime uint64, eth1Data *ethpb.Eth1Data) (state.BeaconState, error) { + st, err := emptyGenesisStateFulu() + if err != nil { + return nil, err + } + + // Process initial deposits. + st, err = helpers.UpdateGenesisEth1Data(st, deposits, eth1Data) + if err != nil { + return nil, err + } + + st, err = processPreGenesisDeposits(ctx, st, deposits) + if err != nil { + return nil, errors.Wrap(err, "could not process validator deposits") + } + + return buildGenesisBeaconStateFulu(genesisTime, st, st.Eth1Data()) +} + +// emptyGenesisStateFulu returns an empty genesis state in Fulu format. +func emptyGenesisStateFulu() (state.BeaconState, error) { + st := ðpb.BeaconStateFulu{ + // Misc fields. + Slot: 0, + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().ElectraForkVersion, + CurrentVersion: params.BeaconConfig().FuluForkVersion, + Epoch: 0, + }, + // Validator registry fields. + Validators: []*ethpb.Validator{}, + Balances: []uint64{}, + InactivityScores: []uint64{}, + + JustificationBits: []byte{0}, + HistoricalRoots: [][]byte{}, + CurrentEpochParticipation: []byte{}, + PreviousEpochParticipation: []byte{}, + + // Eth1 data. + Eth1Data: ðpb.Eth1Data{}, + Eth1DataVotes: []*ethpb.Eth1Data{}, + Eth1DepositIndex: 0, + + LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderDeneb{}, + + // Electra fields + DepositBalanceToConsume: primitives.Gwei(0), + ExitBalanceToConsume: primitives.Gwei(0), + ConsolidationBalanceToConsume: primitives.Gwei(0), + + // Fulu specific field + ProposerLookahead: []uint64{}, + } + return state_native.InitializeFromProtoFulu(st) +} + +func buildGenesisBeaconStateFulu(genesisTime uint64, preState state.BeaconState, eth1Data *ethpb.Eth1Data) (state.BeaconState, error) { + if eth1Data == nil { + return nil, errors.New("no eth1data provided for genesis state") + } + + randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(randaoMixes); i++ { + h := make([]byte, 32) + copy(h, eth1Data.BlockHash) + randaoMixes[i] = h + } + + zeroHash := params.BeaconConfig().ZeroHash[:] + + activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) + for i := 0; i < len(activeIndexRoots); i++ { + activeIndexRoots[i] = zeroHash + } + + blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(blockRoots); i++ { + blockRoots[i] = zeroHash + } + + stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := 0; i < len(stateRoots); i++ { + stateRoots[i] = zeroHash + } + + slashings := make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector) + + genesisValidatorsRoot, err := stateutil.ValidatorRegistryRoot(preState.Validators()) + if err != nil { + return nil, errors.Wrapf(err, "could not hash tree root genesis validators %v", err) + } + + prevEpochParticipation, err := preState.PreviousEpochParticipation() + if err != nil { + return nil, err + } + currEpochParticipation, err := preState.CurrentEpochParticipation() + if err != nil { + return nil, err + } + scores, err := preState.InactivityScores() + if err != nil { + return nil, err + } + tab, err := helpers.TotalActiveBalance(preState) + if err != nil { + return nil, err + } + + st := ðpb.BeaconStateFulu{ + // Misc fields. + Slot: 0, + GenesisTime: genesisTime, + GenesisValidatorsRoot: genesisValidatorsRoot[:], + + Fork: ðpb.Fork{ + PreviousVersion: params.BeaconConfig().GenesisForkVersion, + CurrentVersion: params.BeaconConfig().GenesisForkVersion, + Epoch: 0, + }, + + // Validator registry fields. + Validators: preState.Validators(), + Balances: preState.Balances(), + PreviousEpochParticipation: prevEpochParticipation, + CurrentEpochParticipation: currEpochParticipation, + InactivityScores: scores, + + // Randomness and committees. + RandaoMixes: randaoMixes, + + // Finality. + PreviousJustifiedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + CurrentJustifiedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + JustificationBits: []byte{0}, + FinalizedCheckpoint: ðpb.Checkpoint{ + Epoch: 0, + Root: params.BeaconConfig().ZeroHash[:], + }, + + HistoricalRoots: [][]byte{}, + BlockRoots: blockRoots, + StateRoots: stateRoots, + Slashings: slashings, + + // Eth1 data. + Eth1Data: eth1Data, + Eth1DataVotes: []*ethpb.Eth1Data{}, + Eth1DepositIndex: preState.Eth1DepositIndex(), + + // Electra Data + DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex, + ExitBalanceToConsume: helpers.ActivationExitChurnLimit(primitives.Gwei(tab)), + EarliestConsolidationEpoch: helpers.ActivationExitEpoch(slots.ToEpoch(preState.Slot())), + ConsolidationBalanceToConsume: helpers.ConsolidationChurnLimit(primitives.Gwei(tab)), + PendingDeposits: make([]*ethpb.PendingDeposit, 0), + PendingPartialWithdrawals: make([]*ethpb.PendingPartialWithdrawal, 0), + PendingConsolidations: make([]*ethpb.PendingConsolidation, 0), + } + + var scBits [fieldparams.SyncAggregateSyncCommitteeBytesLength]byte + bodyRoot, err := (ðpb.BeaconBlockBodyElectra{ + RandaoReveal: make([]byte, 96), + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, 32), + BlockHash: make([]byte, 32), + }, + Graffiti: make([]byte, 32), + SyncAggregate: ðpb.SyncAggregate{ + SyncCommitteeBits: scBits[:], + SyncCommitteeSignature: make([]byte, 96), + }, + ExecutionPayload: &enginev1.ExecutionPayloadDeneb{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptsRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + Transactions: make([][]byte, 0), + }, + ExecutionRequests: &enginev1.ExecutionRequests{ + Deposits: make([]*enginev1.DepositRequest, 0), + Withdrawals: make([]*enginev1.WithdrawalRequest, 0), + Consolidations: make([]*enginev1.ConsolidationRequest, 0), + }, + }).HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root empty block body") + } + + st.LatestBlockHeader = ðpb.BeaconBlockHeader{ + ParentRoot: zeroHash, + StateRoot: zeroHash, + BodyRoot: bodyRoot[:], + } + + var pubKeys [][]byte + vals := preState.Validators() + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ { + j := i % uint64(len(vals)) + pubKeys = append(pubKeys, vals[j].PublicKey) + } + aggregated, err := bls.AggregatePublicKeys(pubKeys) + if err != nil { + return nil, err + } + st.CurrentSyncCommittee = ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: aggregated.Marshal(), + } + st.NextSyncCommittee = ðpb.SyncCommittee{ + Pubkeys: pubKeys, + AggregatePubkey: aggregated.Marshal(), + } + + st.LatestExecutionPayloadHeader = &enginev1.ExecutionPayloadHeaderDeneb{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptsRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, 32), + WithdrawalsRoot: make([]byte, 32), + } + + // Calculate proposer lookahead for genesis + preFuluSt, err := state_native.InitializeFromProtoFulu(st) + if err != nil { + return nil, err + } + proposerLookahead, err := helpers.InitializeProposerLookahead(context.Background(), preFuluSt, slots.ToEpoch(preState.Slot())) + if err != nil { + return nil, errors.Wrap(err, "could not calculate proposer lookahead") + } + + // Fulu specific field + st.ProposerLookahead = proposerLookahead + return state_native.InitializeFromProtoFulu(st) +}