From 74bb0821a8328a2321a5e66944d923f5d85e2381 Mon Sep 17 00:00:00 2001 From: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:52:58 -0500 Subject: [PATCH] Use slot to determine fork version (#14653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use slot to determine version * gaz * solve cyclic dependency * Radek' review * unit test * gaz * use require instead of assert * fix test * fix test * fix TestGetAggregateAttestation * fix ListAttestations test * James' review * Radek' review * add extra checks to GetAttesterSlashingsV2 * fix matchingAtts * improve tests + fix * fix * stop appending all non electra atts * more tests * changelog * revert TestProduceSyncCommitteeContribution changes --------- Co-authored-by: RadosÅ‚aw Kapka Co-authored-by: rkapka --- CHANGELOG.md | 1 + beacon-chain/rpc/eth/beacon/handlers_pool.go | 31 +- .../rpc/eth/beacon/handlers_pool_test.go | 103 +++- beacon-chain/rpc/eth/validator/handlers.go | 36 +- .../rpc/eth/validator/handlers_test.go | 449 +++++++++++------- runtime/version/fork.go | 4 +- time/slots/BUILD.bazel | 2 + time/slots/slottime.go | 20 + time/slots/slottime_test.go | 56 +++ 9 files changed, 497 insertions(+), 205 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f08ac6749..3d21860b8b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Added `Eth-Consensus-Version` header to `ListAttestationsV2` and `GetAggregateAttestationV2` endpoints. - Updated light client consensus types. [PR](https://github.com/prysmaticlabs/prysm/pull/14652) - Fixed pending deposits processing on Electra. +- Modified `ListAttestationsV2`, `GetAttesterSlashingsV2` and `GetAggregateAttestationV2` endpoints to use slot to determine fork version. ### Deprecated diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool.go b/beacon-chain/rpc/eth/beacon/handlers_pool.go index f522e659b5a..a255af67008 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool.go @@ -87,7 +87,7 @@ func (s *Server) ListAttestations(w http.ResponseWriter, r *http.Request) { // ListAttestationsV2 retrieves attestations known by the node but // not necessarily incorporated into any block. Allows filtering by committee index or slot. func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { - ctx, span := trace.StartSpan(r.Context(), "beacon.ListAttestationsV2") + _, span := trace.StartSpan(r.Context(), "beacon.ListAttestationsV2") defer span.End() rawSlot, slot, ok := shared.UintFromQuery(w, r, "slot", false) @@ -98,13 +98,10 @@ func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { if !ok { return } - - headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx) - if err != nil { - httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError) - return + v := slots.ToForkVersion(primitives.Slot(slot)) + if rawSlot == "" { + v = slots.ToForkVersion(s.TimeFetcher.CurrentSlot()) } - attestations := s.AttestationsPool.AggregatedAttestations() unaggAtts, err := s.AttestationsPool.UnaggregatedAttestations() if err != nil { @@ -116,7 +113,7 @@ func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { filteredAtts := make([]interface{}, 0, len(attestations)) for _, att := range attestations { var includeAttestation bool - if headState.Version() >= version.Electra { + if v >= version.Electra && att.Version() >= version.Electra { attElectra, ok := att.(*eth.AttestationElectra) if !ok { httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError) @@ -128,7 +125,7 @@ func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { attStruct := structs.AttElectraFromConsensus(attElectra) filteredAtts = append(filteredAtts, attStruct) } - } else { + } else if v < version.Electra && att.Version() < version.Electra { attOld, ok := att.(*eth.Attestation) if !ok { httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError) @@ -149,9 +146,9 @@ func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set(api.VersionHeader, version.String(headState.Version())) + w.Header().Set(api.VersionHeader, version.String(v)) httputil.WriteJson(w, &structs.ListAttestationsResponse{ - Version: version.String(headState.Version()), + Version: version.String(v), Data: attsData, }) } @@ -726,31 +723,33 @@ func (s *Server) GetAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) ctx, span := trace.StartSpan(r.Context(), "beacon.GetAttesterSlashingsV2") defer span.End() + v := slots.ToForkVersion(s.TimeFetcher.CurrentSlot()) headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx) if err != nil { httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError) return } - var attStructs []interface{} sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */) for _, slashing := range sourceSlashings { var attStruct interface{} - if headState.Version() >= version.Electra { + if v >= version.Electra && slashing.Version() >= version.Electra { a, ok := slashing.(*eth.AttesterSlashingElectra) if !ok { httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T to an Electra slashing", slashing), http.StatusInternalServerError) return } attStruct = structs.AttesterSlashingElectraFromConsensus(a) - } else { + } else if v < version.Electra && slashing.Version() < version.Electra { a, ok := slashing.(*eth.AttesterSlashing) if !ok { httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T to a Phase0 slashing", slashing), http.StatusInternalServerError) return } attStruct = structs.AttesterSlashingFromConsensus(a) + } else { + continue } attStructs = append(attStructs, attStruct) } @@ -762,10 +761,10 @@ func (s *Server) GetAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) } resp := &structs.GetAttesterSlashingsResponse{ - Version: version.String(headState.Version()), + Version: version.String(v), Data: attBytes, } - w.Header().Set(api.VersionHeader, version.String(headState.Version())) + w.Header().Set(api.VersionHeader, version.String(v)) httputil.WriteJson(w, resp) } diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go index ee425f4bd4d..5ff2e5d228a 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go @@ -115,9 +115,16 @@ func TestListAttestations(t *testing.T) { Signature: bytesutil.PadTo([]byte("signature4"), 96), } t.Run("V1", func(t *testing.T) { + bs, err := util.NewBeaconState() + require.NoError(t, err) + + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ + ChainInfoFetcher: chainService, + TimeFetcher: chainService, AttestationsPool: attestations.NewPool(), } + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2})) require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4})) @@ -204,10 +211,19 @@ func TestListAttestations(t *testing.T) { t.Run("Pre-Electra", func(t *testing.T) { bs, err := util.NewBeaconState() require.NoError(t, err) + + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + ChainInfoFetcher: chainService, + TimeFetcher: chainService, AttestationsPool: attestations.NewPool(), } + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.DenebForkEpoch = 0 + params.OverrideBeaconConfig(config) + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2})) require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4})) t.Run("empty request", func(t *testing.T) { @@ -226,7 +242,7 @@ func TestListAttestations(t *testing.T) { var atts []*structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &atts)) assert.Equal(t, 4, len(atts)) - assert.Equal(t, "phase0", resp.Version) + assert.Equal(t, "deneb", resp.Version) }) t.Run("slot request", func(t *testing.T) { url := "http://example.com?slot=2" @@ -244,7 +260,7 @@ func TestListAttestations(t *testing.T) { var atts []*structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &atts)) assert.Equal(t, 2, len(atts)) - assert.Equal(t, "phase0", resp.Version) + assert.Equal(t, "deneb", resp.Version) for _, a := range atts { assert.Equal(t, "2", a.Data.Slot) } @@ -265,7 +281,7 @@ func TestListAttestations(t *testing.T) { var atts []*structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &atts)) assert.Equal(t, 2, len(atts)) - assert.Equal(t, "phase0", resp.Version) + assert.Equal(t, "deneb", resp.Version) for _, a := range atts { assert.Equal(t, "4", a.Data.CommitteeIndex) } @@ -286,7 +302,7 @@ func TestListAttestations(t *testing.T) { var atts []*structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &atts)) assert.Equal(t, 1, len(atts)) - assert.Equal(t, "phase0", resp.Version) + assert.Equal(t, "deneb", resp.Version) for _, a := range atts { assert.Equal(t, "2", a.Data.Slot) assert.Equal(t, "4", a.Data.CommitteeIndex) @@ -370,12 +386,21 @@ func TestListAttestations(t *testing.T) { } bs, err := util.NewBeaconStateElectra() require.NoError(t, err) + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 0 + params.OverrideBeaconConfig(config) + + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ AttestationsPool: attestations.NewPool(), - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + ChainInfoFetcher: chainService, + TimeFetcher: chainService, } - require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{attElectra1, attElectra2})) - require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{attElectra3, attElectra4})) + // Added one pre electra attestation to ensure it is ignored. + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{attElectra1, attElectra2, att1})) + require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{attElectra3, attElectra4, att3})) t.Run("empty request", func(t *testing.T) { url := "http://example.com" @@ -1658,12 +1683,59 @@ func TestGetAttesterSlashings(t *testing.T) { }) }) t.Run("V2", func(t *testing.T) { + t.Run("post-electra-ok-1-pre-slashing", func(t *testing.T) { + bs, err := util.NewBeaconStateElectra() + require.NoError(t, err) + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 100 + params.OverrideBeaconConfig(config) + + chainService := &blockchainmock.ChainService{State: bs} + + s := &Server{ + ChainInfoFetcher: chainService, + TimeFetcher: chainService, + SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PostElectra, slashing2PostElectra, slashing1PreElectra}}, + } + + request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/beacon/pool/attester_slashings", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAttesterSlashingsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetAttesterSlashingsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + assert.Equal(t, "electra", resp.Version) + + // Unmarshal resp.Data into a slice of slashings + var slashings []*structs.AttesterSlashingElectra + require.NoError(t, json.Unmarshal(resp.Data, &slashings)) + + ss, err := structs.AttesterSlashingsElectraToConsensus(slashings) + require.NoError(t, err) + + require.DeepEqual(t, slashing1PostElectra, ss[0]) + require.DeepEqual(t, slashing2PostElectra, ss[1]) + }) t.Run("post-electra-ok", func(t *testing.T) { bs, err := util.NewBeaconStateElectra() require.NoError(t, err) + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 100 + params.OverrideBeaconConfig(config) + + chainService := &blockchainmock.ChainService{State: bs} + s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + ChainInfoFetcher: chainService, + TimeFetcher: chainService, SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PostElectra, slashing2PostElectra}}, } @@ -1692,9 +1764,11 @@ func TestGetAttesterSlashings(t *testing.T) { t.Run("pre-electra-ok", func(t *testing.T) { bs, err := util.NewBeaconState() require.NoError(t, err) + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + ChainInfoFetcher: chainService, + TimeFetcher: chainService, SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PreElectra, slashing2PreElectra}}, } @@ -1722,8 +1796,15 @@ func TestGetAttesterSlashings(t *testing.T) { bs, err := util.NewBeaconStateElectra() require.NoError(t, err) + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 100 + params.OverrideBeaconConfig(config) + + chainService := &blockchainmock.ChainService{State: bs} s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + ChainInfoFetcher: chainService, + TimeFetcher: chainService, SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{}}, } diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 1b56aba5360..b903ce81b59 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -75,7 +75,7 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) // GetAggregateAttestationV2 aggregates all attestations matching the given attestation data root and slot, returning the aggregated result. func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Request) { - ctx, span := trace.StartSpan(r.Context(), "validator.GetAggregateAttestationV2") + _, span := trace.StartSpan(r.Context(), "validator.GetAggregateAttestationV2") defer span.End() _, attDataRoot, ok := shared.HexFromQuery(w, r, "attestation_data_root", fieldparams.RootLength, true) @@ -91,14 +91,15 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques return } + v := slots.ToForkVersion(primitives.Slot(slot)) agg := s.aggregatedAttestation(w, primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) if agg == nil { return } resp := &structs.AggregateAttestationResponse{ - Version: version.String(agg.Version()), + Version: version.String(v), } - if agg.Version() >= version.Electra { + if v >= version.Electra { typedAgg, ok := agg.(*ethpbalpha.AttestationElectra) if !ok { httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.AttestationElectra{}), http.StatusInternalServerError) @@ -123,12 +124,7 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques } resp.Data = data } - headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx) - if err != nil { - httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set(api.VersionHeader, version.String(headState.Version())) + w.Header().Set(api.VersionHeader, version.String(v)) httputil.WriteJson(w, resp) } @@ -179,27 +175,37 @@ func matchingAtts(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byt return []ethpbalpha.Att{}, nil } - postElectra := atts[0].Version() >= version.Electra - + postElectra := slots.ToForkVersion(slot) >= version.Electra result := make([]ethpbalpha.Att, 0) for _, att := range atts { if att.GetData().Slot != slot { continue } + // We ignore the committee index from the request before Electra. // This is because before Electra the committee index is part of the attestation data, // meaning that comparing the data root is sufficient. // Post-Electra the committee index in the data root is always 0, so we need to // compare the committee index separately. if postElectra { - ci, err := att.GetCommitteeIndex() - if err != nil { - return nil, err + if att.Version() >= version.Electra { + ci, err := att.GetCommitteeIndex() + if err != nil { + return nil, err + } + if ci != index { + continue + } + } else { + continue } - if ci != index { + } else { + // If postElectra is false and att.Version >= version.Electra, ignore the attestation. + if att.Version() >= version.Electra { continue } } + root, err := att.GetData().HashTreeRoot() if err != nil { return nil, errors.Wrap(err, "could not get attestation data root") diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index ec4d7d8e2a1..24d280c44fe 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -55,15 +55,33 @@ func TestGetAggregateAttestation(t *testing.T) { require.NoError(t, err) sig := key.Sign([]byte("sig")) - t.Run("V1", func(t *testing.T) { - createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte) *ethpbalpha.Attestation { - return ðpbalpha.Attestation{ - AggregationBits: aggregationBits, - Data: createAttestationData(slot, 1, 1, root), - Signature: sig.Marshal(), - } + // It is important to use 0 as the index because that's the only way + // pre and post-Electra attestations can both match, + // which allows us to properly test that attestations from the + // wrong fork are ignored. + committeeIndex := uint64(0) + + createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte) *ethpbalpha.Attestation { + return ðpbalpha.Attestation{ + AggregationBits: aggregationBits, + Data: createAttestationData(slot, primitives.CommitteeIndex(committeeIndex), root), + Signature: sig.Marshal(), + } + } + + createAttestationElectra := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte) *ethpbalpha.AttestationElectra { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(committeeIndex, true) + + return ðpbalpha.AttestationElectra{ + CommitteeBits: committeeBits, + AggregationBits: aggregationBits, + Data: createAttestationData(slot, primitives.CommitteeIndex(committeeIndex), root), + Signature: sig.Marshal(), } + } + t.Run("V1", func(t *testing.T) { aggSlot1_Root1_1 := createAttestation(1, bitfield.Bitlist{0b11100}, root1) aggSlot1_Root1_2 := createAttestation(1, bitfield.Bitlist{0b10111}, root1) aggSlot1_Root2 := createAttestation(1, bitfield.Bitlist{0b11100}, root2) @@ -84,7 +102,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") - assert.Equal(t, "1", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, "0", attestation.Data.CommitteeIndex, "Committee index mismatch") assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") // Source checkpoint checks @@ -206,181 +224,288 @@ func TestGetAggregateAttestation(t *testing.T) { }) }) t.Run("V2", func(t *testing.T) { - createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, bits uint64) *ethpbalpha.AttestationElectra { + t.Run("pre-electra", func(t *testing.T) { committeeBits := bitfield.NewBitvector64() - committeeBits.SetBitAt(bits, true) - - return ðpbalpha.AttestationElectra{ - CommitteeBits: committeeBits, - AggregationBits: aggregationBits, - Data: createAttestationData(slot, 0, 1, root), - Signature: sig.Marshal(), + committeeBits.SetBitAt(1, true) + + aggSlot1_Root1_1 := createAttestation(1, bitfield.Bitlist{0b11100}, root1) + aggSlot1_Root1_2 := createAttestation(1, bitfield.Bitlist{0b10111}, root1) + aggSlot1_Root2 := createAttestation(1, bitfield.Bitlist{0b11100}, root2) + aggSlot2 := createAttestation(2, bitfield.Bitlist{0b11100}, root1) + unaggSlot3_Root1_1 := createAttestation(3, bitfield.Bitlist{0b11000}, root1) + unaggSlot3_Root1_2 := createAttestation(3, bitfield.Bitlist{0b10100}, root1) + unaggSlot3_Root2 := createAttestation(3, bitfield.Bitlist{0b11000}, root2) + unaggSlot4 := createAttestation(4, bitfield.Bitlist{0b11000}, root1) + + // Add one post-electra attestation to ensure that it is being ignored. + // We choose slot 2 where we have one pre-electra attestation with less attestation bits. + postElectraAtt := createAttestationElectra(2, bitfield.Bitlist{0b11111}, root1) + + compareResult := func( + t *testing.T, + attestation structs.Attestation, + expectedSlot string, + expectedAggregationBits string, + expectedRoot []byte, + expectedSig []byte, + ) { + assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") + assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") + assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") + assert.Equal(t, "0", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source, "Source checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Source.Epoch, "Source epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Source.Root, "Source root mismatch") + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target, "Target checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Target.Epoch, "Target epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Target.Root, "Target root mismatch") } - } - - aggSlot1_Root1_1 := createAttestation(1, bitfield.Bitlist{0b11100}, root1, 1) - aggSlot1_Root1_2 := createAttestation(1, bitfield.Bitlist{0b10111}, root1, 1) - aggSlot1_Root2 := createAttestation(1, bitfield.Bitlist{0b11100}, root2, 1) - aggSlot2 := createAttestation(2, bitfield.Bitlist{0b11100}, root1, 1) - unaggSlot3_Root1_1 := createAttestation(3, bitfield.Bitlist{0b11000}, root1, 1) - unaggSlot3_Root1_2 := createAttestation(3, bitfield.Bitlist{0b10100}, root1, 1) - unaggSlot3_Root2 := createAttestation(3, bitfield.Bitlist{0b11000}, root2, 1) - unaggSlot4 := createAttestation(4, bitfield.Bitlist{0b11000}, root1, 1) - - compareResult := func( - t *testing.T, - attestation structs.AttestationElectra, - expectedSlot string, - expectedAggregationBits string, - expectedRoot []byte, - expectedSig []byte, - expectedCommitteeBits string, - ) { - assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") - assert.Equal(t, expectedCommitteeBits, attestation.CommitteeBits) - assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") - assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") - assert.Equal(t, "0", attestation.Data.CommitteeIndex, "Committee index mismatch") - assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") - - // Source checkpoint checks - require.NotNil(t, attestation.Data.Source, "Source checkpoint should not be nil") - assert.Equal(t, "1", attestation.Data.Source.Epoch, "Source epoch mismatch") - assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Source.Root, "Source root mismatch") - - // Target checkpoint checks - require.NotNil(t, attestation.Data.Target, "Target checkpoint should not be nil") - assert.Equal(t, "1", attestation.Data.Target.Epoch, "Target epoch mismatch") - assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Target.Root, "Target root mismatch") - } - - pool := attestations.NewPool() - require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggSlot3_Root1_1, unaggSlot3_Root1_2, unaggSlot3_Root2, unaggSlot4}), "Failed to save unaggregated attestations") - unagg, err := pool.UnaggregatedAttestations() - require.NoError(t, err) - require.Equal(t, 4, len(unagg), "Expected 4 unaggregated attestations") - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{aggSlot1_Root1_1, aggSlot1_Root1_2, aggSlot1_Root2, aggSlot2}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 4, len(agg), "Expected 4 aggregated attestations") - bs, err := util.NewBeaconState() - require.NoError(t, err) - s := &Server{ - ChainInfoFetcher: &mockChain.ChainService{State: bs}, - AttestationsPool: pool, - } - t.Run("non-matching attestation request", func(t *testing.T) { - reqRoot, err := aggSlot2.Data.HashTreeRoot() - require.NoError(t, err, "Failed to generate attestation data hash tree root") - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - - s.GetAggregateAttestationV2(writer, request) - assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") - }) - t.Run("1 matching aggregated attestation", func(t *testing.T) { - reqRoot, err := aggSlot2.Data.HashTreeRoot() - require.NoError(t, err, "Failed to generate attestation data hash tree root") - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - - s.GetAggregateAttestationV2(writer, request) - require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - - var resp structs.AggregateAttestationResponse - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") - require.NotNil(t, resp.Data, "Response data should not be nil") - - var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - - compareResult(t, attestation, "2", hexutil.Encode(aggSlot2.AggregationBits), root1, sig.Marshal(), hexutil.Encode(aggSlot2.CommitteeBits)) - }) - t.Run("multiple matching aggregated attestations - return the one with most bits", func(t *testing.T) { - reqRoot, err := aggSlot1_Root1_1.Data.HashTreeRoot() - require.NoError(t, err, "Failed to generate attestation data hash tree root") - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - - s.GetAggregateAttestationV2(writer, request) - require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - - var resp structs.AggregateAttestationResponse - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") - require.NotNil(t, resp.Data, "Response data should not be nil") - var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + pool := attestations.NewPool() + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggSlot3_Root1_1, unaggSlot3_Root1_2, unaggSlot3_Root2, unaggSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 4, len(unagg), "Expected 4 unaggregated attestations") + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{aggSlot1_Root1_1, aggSlot1_Root1_2, aggSlot1_Root2, aggSlot2, postElectraAtt}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 5, len(agg), "Expected 5 aggregated attestations, 4 pre electra and 1 post electra") + s := &Server{ + AttestationsPool: pool, + } - compareResult(t, attestation, "1", hexutil.Encode(aggSlot1_Root1_2.AggregationBits), root1, sig.Marshal(), hexutil.Encode(aggSlot1_Root1_1.CommitteeBits)) + t.Run("non-matching attestation request", func(t *testing.T) { + reqRoot, err := aggSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") + }) + t.Run("1 matching aggregated attestation", func(t *testing.T) { + reqRoot, err := aggSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + + compareResult(t, attestation, "2", hexutil.Encode(aggSlot2.AggregationBits), root1, sig.Marshal()) + }) + t.Run("multiple matching aggregated attestations - return the one with most bits", func(t *testing.T) { + reqRoot, err := aggSlot1_Root1_1.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + + compareResult(t, attestation, "1", hexutil.Encode(aggSlot1_Root1_2.AggregationBits), root1, sig.Marshal()) + }) }) - t.Run("1 matching unaggregated attestation", func(t *testing.T) { - reqRoot, err := unaggSlot4.Data.HashTreeRoot() - require.NoError(t, err, "Failed to generate attestation data hash tree root") - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - - s.GetAggregateAttestationV2(writer, request) - require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - - var resp structs.AggregateAttestationResponse - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") - require.NotNil(t, resp.Data, "Response data should not be nil") + t.Run("post-electra", func(t *testing.T) { + aggSlot1_Root1_1 := createAttestationElectra(1, bitfield.Bitlist{0b11100}, root1) + aggSlot1_Root1_2 := createAttestationElectra(1, bitfield.Bitlist{0b10111}, root1) + aggSlot1_Root2 := createAttestationElectra(1, bitfield.Bitlist{0b11100}, root2) + aggSlot2 := createAttestationElectra(2, bitfield.Bitlist{0b11100}, root1) + unaggSlot3_Root1_1 := createAttestationElectra(3, bitfield.Bitlist{0b11000}, root1) + unaggSlot3_Root1_2 := createAttestationElectra(3, bitfield.Bitlist{0b10100}, root1) + unaggSlot3_Root2 := createAttestationElectra(3, bitfield.Bitlist{0b11000}, root2) + unaggSlot4 := createAttestationElectra(4, bitfield.Bitlist{0b11000}, root1) + + // Add one pre-electra attestation to ensure that it is being ignored. + // We choose slot 2 where we have one post-electra attestation with less attestation bits. + preElectraAtt := createAttestation(2, bitfield.Bitlist{0b11111}, root1) + + compareResult := func( + t *testing.T, + attestation structs.AttestationElectra, + expectedSlot string, + expectedAggregationBits string, + expectedRoot []byte, + expectedSig []byte, + expectedCommitteeBits string, + ) { + assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") + assert.Equal(t, expectedCommitteeBits, attestation.CommitteeBits) + assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") + assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") + assert.Equal(t, "0", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source, "Source checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Source.Epoch, "Source epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Source.Root, "Source root mismatch") + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target, "Target checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Target.Epoch, "Target epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Target.Root, "Target root mismatch") + } - var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "4", hexutil.Encode(unaggSlot4.AggregationBits), root1, sig.Marshal(), hexutil.Encode(unaggSlot4.CommitteeBits)) - }) - t.Run("multiple matching unaggregated attestations - their aggregate is returned", func(t *testing.T) { - reqRoot, err := unaggSlot3_Root1_1.Data.HashTreeRoot() - require.NoError(t, err, "Failed to generate attestation data hash tree root") - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() + pool := attestations.NewPool() + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggSlot3_Root1_1, unaggSlot3_Root1_2, unaggSlot3_Root2, unaggSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 4, len(unagg), "Expected 4 unaggregated attestations") + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{aggSlot1_Root1_1, aggSlot1_Root1_2, aggSlot1_Root2, aggSlot2, preElectraAtt}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 5, len(agg), "Expected 5 aggregated attestations, 4 electra and 1 pre electra") + bs, err := util.NewBeaconState() + require.NoError(t, err) - s.GetAggregateAttestationV2(writer, request) - require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 0 + params.OverrideBeaconConfig(config) - var resp structs.AggregateAttestationResponse - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") - require.NotNil(t, resp.Data, "Response data should not be nil") + chainService := &mockChain.ChainService{State: bs} + s := &Server{ + ChainInfoFetcher: chainService, + TimeFetcher: chainService, + AttestationsPool: pool, + } + t.Run("non-matching attestation request", func(t *testing.T) { + reqRoot, err := aggSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") + }) + t.Run("1 matching aggregated attestation", func(t *testing.T) { + reqRoot, err := aggSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + + compareResult(t, attestation, "2", hexutil.Encode(aggSlot2.AggregationBits), root1, sig.Marshal(), hexutil.Encode(aggSlot2.CommitteeBits)) + }) + t.Run("multiple matching aggregated attestations - return the one with most bits", func(t *testing.T) { + reqRoot, err := aggSlot1_Root1_1.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + + compareResult(t, attestation, "1", hexutil.Encode(aggSlot1_Root1_2.AggregationBits), root1, sig.Marshal(), hexutil.Encode(aggSlot1_Root1_1.CommitteeBits)) + }) + t.Run("1 matching unaggregated attestation", func(t *testing.T) { + reqRoot, err := unaggSlot4.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + compareResult(t, attestation, "4", hexutil.Encode(unaggSlot4.AggregationBits), root1, sig.Marshal(), hexutil.Encode(unaggSlot4.CommitteeBits)) + }) + t.Run("multiple matching unaggregated attestations - their aggregate is returned", func(t *testing.T) { + reqRoot, err := unaggSlot3_Root1_1.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=0" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + + s.GetAggregateAttestationV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + sig1, err := bls.SignatureFromBytes(unaggSlot3_Root1_1.Signature) + require.NoError(t, err) + sig2, err := bls.SignatureFromBytes(unaggSlot3_Root1_2.Signature) + require.NoError(t, err) + expectedSig := bls.AggregateSignatures([]common.Signature{sig1, sig2}) + compareResult(t, attestation, "3", hexutil.Encode(bitfield.Bitlist{0b11100}), root1, expectedSig.Marshal(), hexutil.Encode(unaggSlot3_Root1_1.CommitteeBits)) + }) + t.Run("pre-electra attestation is ignored", func(t *testing.T) { - var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - sig1, err := bls.SignatureFromBytes(unaggSlot3_Root1_1.Signature) - require.NoError(t, err) - sig2, err := bls.SignatureFromBytes(unaggSlot3_Root1_2.Signature) - require.NoError(t, err) - expectedSig := bls.AggregateSignatures([]common.Signature{sig1, sig2}) - compareResult(t, attestation, "3", hexutil.Encode(bitfield.Bitlist{0b11100}), root1, expectedSig.Marshal(), hexutil.Encode(unaggSlot3_Root1_1.CommitteeBits)) + }) }) }) + } -func createAttestationData( - slot primitives.Slot, - committeeIndex primitives.CommitteeIndex, - epoch primitives.Epoch, - root []byte, -) *ethpbalpha.AttestationData { +func createAttestationData(slot primitives.Slot, committeeIndex primitives.CommitteeIndex, root []byte) *ethpbalpha.AttestationData { return ðpbalpha.AttestationData{ Slot: slot, CommitteeIndex: committeeIndex, BeaconBlockRoot: root, Source: ðpbalpha.Checkpoint{ - Epoch: epoch, + Epoch: 1, Root: root, }, Target: ðpbalpha.Checkpoint{ - Epoch: epoch, + Epoch: 1, Root: root, }, } diff --git a/runtime/version/fork.go b/runtime/version/fork.go index 902393c8bc8..ecf8521ad45 100644 --- a/runtime/version/fork.go +++ b/runtime/version/fork.go @@ -1,6 +1,8 @@ package version -import "github.com/pkg/errors" +import ( + "github.com/pkg/errors" +) const ( Phase0 = iota diff --git a/time/slots/BUILD.bazel b/time/slots/BUILD.bazel index ca39ef4fded..779cf851e64 100644 --- a/time/slots/BUILD.bazel +++ b/time/slots/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", "//math:go_default_library", + "//runtime/version:go_default_library", "//time:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", @@ -32,6 +33,7 @@ go_test( deps = [ "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//time:go_default_library", diff --git a/time/slots/slottime.go b/time/slots/slottime.go index 273b0f5f95f..f847af7133f 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -9,6 +9,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" mathutil "github.com/prysmaticlabs/prysm/v5/math" + "github.com/prysmaticlabs/prysm/v5/runtime/version" prysmTime "github.com/prysmaticlabs/prysm/v5/time" "github.com/sirupsen/logrus" ) @@ -81,6 +82,25 @@ func ToEpoch(slot primitives.Slot) primitives.Epoch { return primitives.Epoch(slot.DivSlot(params.BeaconConfig().SlotsPerEpoch)) } +// ToForkVersion translates a slot into it's corresponding version. +func ToForkVersion(slot primitives.Slot) int { + epoch := ToEpoch(slot) + switch { + case epoch >= params.BeaconConfig().ElectraForkEpoch: + return version.Electra + case epoch >= params.BeaconConfig().DenebForkEpoch: + return version.Deneb + case epoch >= params.BeaconConfig().CapellaForkEpoch: + return version.Capella + case epoch >= params.BeaconConfig().BellatrixForkEpoch: + return version.Bellatrix + case epoch >= params.BeaconConfig().AltairForkEpoch: + return version.Altair + default: + return version.Phase0 + } +} + // EpochStart returns the first slot number of the // current epoch. // diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index 30e1907ecfd..eb57fcaee05 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -7,6 +7,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" prysmTime "github.com/prysmaticlabs/prysm/v5/time" @@ -632,3 +633,58 @@ func TestSecondsUntilNextEpochStart(t *testing.T) { require.Equal(t, true, IsEpochStart(currentSlot)) } + +func TestToForkVersion(t *testing.T) { + t.Run("Electra fork version", func(t *testing.T) { + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.ElectraForkEpoch = 100 + params.OverrideBeaconConfig(config) + + slot, err := EpochStart(params.BeaconConfig().ElectraForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot) + require.Equal(t, version.Electra, result) + }) + + t.Run("Deneb fork version", func(t *testing.T) { + slot, err := EpochStart(params.BeaconConfig().DenebForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot) + require.Equal(t, version.Deneb, result) + }) + + t.Run("Capella fork version", func(t *testing.T) { + slot, err := EpochStart(params.BeaconConfig().CapellaForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot) + require.Equal(t, version.Capella, result) + }) + + t.Run("Bellatrix fork version", func(t *testing.T) { + slot, err := EpochStart(params.BeaconConfig().BellatrixForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot) + require.Equal(t, version.Bellatrix, result) + }) + + t.Run("Altair fork version", func(t *testing.T) { + slot, err := EpochStart(params.BeaconConfig().AltairForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot) + require.Equal(t, version.Altair, result) + }) + + t.Run("Phase0 fork version", func(t *testing.T) { + slot, err := EpochStart(params.BeaconConfig().AltairForkEpoch) + require.NoError(t, err) + + result := ToForkVersion(slot - 1) + require.Equal(t, version.Phase0, result) + }) +}