Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP endpoint for GetValidatorParticipation #14261

Merged
merged 15 commits into from
Aug 2, 2024
Prev Previous commit
Next Next commit
Radek' review
  • Loading branch information
saolyn committed Aug 2, 2024
commit a8f8e7d69293a3154a720c0e5e829bf90d4dd171
2 changes: 1 addition & 1 deletion beacon-chain/rpc/core/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ func (s *Service) ValidatorParticipation(
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not pre compute attestations: %v")}
}
} else {
return nil, &RpcError{Reason: Internal, Err: fmt.Errorf("invalid state type retrieved with a version of %d", beaconSt.Version())}
return nil, &RpcError{Reason: Internal, Err: fmt.Errorf("invalid state type retrieved with a version of %s", version.String(beaconSt.Version()))}
}

cp := s.FinalizedFetcher.FinalizedCheckpt()
Expand Down
6 changes: 3 additions & 3 deletions beacon-chain/rpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (s *Service) endpoints(
endpoints = append(endpoints, s.eventsEndpoints()...)
endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater, coreService)...)
endpoints = append(endpoints, s.prysmNodeEndpoints()...)
endpoints = append(endpoints, s.prysmValidatorEndpoints(coreService)...)
endpoints = append(endpoints, s.prysmValidatorEndpoints(stater, coreService)...)
if enableDebug {
endpoints = append(endpoints, s.debugEndpoints(stater)...)
}
Expand Down Expand Up @@ -1060,9 +1060,10 @@ func (s *Service) prysmNodeEndpoints() []endpoint {
}
}

func (s *Service) prysmValidatorEndpoints(coreService *core.Service) []endpoint {
func (s *Service) prysmValidatorEndpoints(stater lookup.Stater, coreService *core.Service) []endpoint {
server := &validatorprysm.Server{
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
Stater: stater,
CoreService: coreService,
}

Expand Down Expand Up @@ -1092,7 +1093,6 @@ func (s *Service) prysmValidatorEndpoints(coreService *core.Service) []endpoint
template: "/prysm/v1/validators/participation",
name: namespace + ".GetValidatorParticipation",
middleware: []mux.MiddlewareFunc{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.GetValidatorParticipation,
Expand Down
41 changes: 14 additions & 27 deletions beacon-chain/rpc/prysm/validator/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import (
"fmt"
"net/http"
"strings"

"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
Expand All @@ -21,34 +21,21 @@
ctx, span := trace.StartSpan(r.Context(), "validator.GetValidatorParticipation")
defer span.End()

stateId := strings.ReplaceAll(r.URL.Query().Get("state_id"), " ", "")
var epoch uint64
switch stateId {
case "head":
e, err := s.ChainInfoFetcher.ReceivedBlocksLastEpoch()
if err != nil {
httputil.HandleError(w, "Could not retrieve head root: "+err.Error(), http.StatusInternalServerError)
return
}
epoch = e
case "finalized":
finalized := s.ChainInfoFetcher.FinalizedCheckpt()
epoch = uint64(finalized.Epoch)
case "genesis":
epoch = 0
default:
_, e, ok := shared.UintFromQuery(w, r, "epoch", true)
if !ok {
currentSlot := s.CoreService.GenesisTimeFetcher.CurrentSlot()
currentEpoch := slots.ToEpoch(currentSlot)
epoch = uint64(currentEpoch)
} else {
epoch = e
}
stateId := mux.Vars(r)["state_id"]
if stateId == "" {
httputil.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
return
}
vp, err := s.CoreService.ValidatorParticipation(ctx, primitives.Epoch(epoch))

st, err := s.Stater.State(ctx, []byte(stateId))
if err != nil {
httputil.HandleError(w, err.Err.Error(), core.ErrorReasonToHTTP(err.Reason))
shared.WriteStateFetchError(w, err)
return
}
stEpoch := slots.ToEpoch(st.Slot())
vp, rpcError := s.CoreService.ValidatorParticipation(ctx, primitives.Epoch(stEpoch))

Check failure on line 36 in beacon-chain/rpc/prysm/validator/handlers.go

View workflow job for this annotation

GitHub Actions / Lint

unnecessary conversion (unconvert)
if rpcError != nil {
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
return
}

Expand Down
81 changes: 40 additions & 41 deletions beacon-chain/rpc/prysm/validator/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/gorilla/mux"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
Expand All @@ -20,6 +21,7 @@ import (
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock"
Expand All @@ -42,11 +44,18 @@ func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs)
}

func TestServer_GetValidatorParticipation_CannotRequestFutureEpoch(t *testing.T) {
func TestServer_GetValidatorParticipation_NoState(t *testing.T) {
headState, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, headState.SetSlot(0))

var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 4)

s := &Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
CoreService: &core.Service{
HeadFetcher: &mock.ChainService{
State: headState,
Expand All @@ -55,14 +64,14 @@ func TestServer_GetValidatorParticipation_CannotRequestFutureEpoch(t *testing.T)
},
}

url := "http://example.com?epoch=" + fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1)
url := "http://example.com" + fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1)
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetValidatorParticipation(writer, request)
require.Equal(t, http.StatusBadRequest, writer.Code)
require.StringContains(t, "cannot retrieve information about an epoch", writer.Body.String())
require.StringContains(t, "state_id is required in URL params", writer.Body.String())
}

func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
Expand Down Expand Up @@ -110,7 +119,14 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {

m := &mock.ChainService{State: headState}
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))

var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 4)

s := &Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
BeaconDB: beaconDB,
CoreService: &core.Service{
HeadFetcher: m,
Expand All @@ -128,8 +144,9 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
}
addDefaultReplayerBuilder(s, beaconDB)

url := "http://example.com?epoch=1"
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

Expand Down Expand Up @@ -203,8 +220,14 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {

m := &mock.ChainService{State: headState}
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))

var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 4)
s := &Server{
BeaconDB: beaconDB,
Stater: &testutil.MockStater{
BeaconState: st,
},
CoreService: &core.Service{
HeadFetcher: m,
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
Expand All @@ -221,8 +244,9 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
}
addDefaultReplayerBuilder(s, beaconDB)

url := "http://example.com?epoch=1"
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

Expand Down Expand Up @@ -259,6 +283,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochWithBits(t *testing
t.Run("altair", func(t *testing.T) {
validatorCount := uint64(32)
genState, _ := util.DeterministicGenesisStateAltair(t, validatorCount)

c, err := altair.NextSyncCommittee(context.Background(), genState)
require.NoError(t, err)
require.NoError(t, genState.SetCurrentSyncCommittee(c))
Expand All @@ -271,7 +296,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochWithBits(t *testing
require.NoError(t, genState.SetPreviousParticipationBits(bits))
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockAltair())
assert.NoError(t, err)
runGetValidatorParticipationCurrentAndPrevEpoch(t, genState, gb)
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
})

t.Run("bellatrix", func(t *testing.T) {
Expand All @@ -289,7 +314,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochWithBits(t *testing
require.NoError(t, genState.SetPreviousParticipationBits(bits))
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockBellatrix())
assert.NoError(t, err)
runGetValidatorParticipationCurrentAndPrevEpoch(t, genState, gb)
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
})

t.Run("capella", func(t *testing.T) {
Expand All @@ -307,11 +332,11 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpochWithBits(t *testing
require.NoError(t, genState.SetPreviousParticipationBits(bits))
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
assert.NoError(t, err)
runGetValidatorParticipationCurrentAndPrevEpoch(t, genState, gb)
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
})
}

func runGetValidatorParticipationCurrentAndPrevEpoch(t *testing.T, genState state.BeaconState, gb interfaces.SignedBeaconBlock) {
func runGetValidatorParticipationCurrentEpoch(t *testing.T, genState state.BeaconState, gb interfaces.SignedBeaconBlock) {
helpers.ClearCache()
beaconDB := dbTest.SetupDB(t)

Expand All @@ -332,8 +357,12 @@ func runGetValidatorParticipationCurrentAndPrevEpoch(t *testing.T, genState stat

m := &mock.ChainService{State: genState}
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))

s := &Server{
BeaconDB: beaconDB,
Stater: &testutil.MockStater{
BeaconState: genState,
},
CoreService: &core.Service{
HeadFetcher: m,
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
Expand All @@ -345,8 +374,9 @@ func runGetValidatorParticipationCurrentAndPrevEpoch(t *testing.T, genState stat
}
addDefaultReplayerBuilder(s, beaconDB)

url := "http://example.com?epoch=0"
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

Expand Down Expand Up @@ -374,35 +404,4 @@ func runGetValidatorParticipationCurrentAndPrevEpoch(t *testing.T, genState stat

assert.DeepEqual(t, true, vp.Finalized, "Incorrect validator participation respond")
assert.DeepEqual(t, *want.Participation, *vp.Participation, "Incorrect validator participation respond")

url = "http://example.com?epoch=1"
request = httptest.NewRequest(http.MethodGet, url, nil)
writer = httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetValidatorParticipation(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)

want = &structs.GetValidatorParticipationResponse{
Participation: &structs.ValidatorParticipation{
GlobalParticipationRate: "1.000000",
VotedEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
EligibleEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
CurrentEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
CurrentEpochAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement), // Empty because after one epoch, current participation rotates to previous
CurrentEpochTargetAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
PreviousEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
PreviousEpochAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
PreviousEpochTargetAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
PreviousEpochHeadAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
},
}

err = json.NewDecoder(writer.Body).Decode(&vp)
if err != nil {
t.Fatalf("Failed to decode response: %v", err)
}

assert.DeepEqual(t, true, vp.Finalized, "Incorrect validator participation respond")
assert.DeepEqual(t, *want.Participation, *vp.Participation, "Incorrect validator participation respond")
}
2 changes: 2 additions & 0 deletions beacon-chain/rpc/prysm/validator/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
)

type Server struct {
BeaconDB db.ReadOnlyDatabase
Stater lookup.Stater
CanonicalFetcher blockchain.CanonicalFetcher
FinalizationFetcher blockchain.FinalizationFetcher
ChainInfoFetcher blockchain.ChainInfoFetcher
Expand Down
Loading