Add support for validator count endpoint (#12752)

This commit is contained in:
Dhruv Bodani
2023-08-25 02:41:41 +05:30
committed by GitHub
parent bcf728b9ff
commit 30eaddf48f
8 changed files with 750 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ package helpers
import (
"errors"
"fmt"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
@@ -34,3 +35,15 @@ type SingleIndexedVerificationFailure struct {
Index int `json:"index"`
Message string `json:"message"`
}
// PrepareStateFetchError returns an appropriate error based on the supplied argument.
// The argument error should be a result of fetching state.
func PrepareStateFetchError(err error) error {
if errors.Is(err, stategen.ErrNoDataForSlot) {
return errors.New("lacking historical data needed to fulfill request")
}
if stateNotFoundErr, ok := err.(*lookup.StateNotFoundError); ok {
return fmt.Errorf("state not found: %v", stateNotFoundErr)
}
return fmt.Errorf("could not fetch state: %v", err)
}

View File

@@ -4,38 +4,55 @@ go_library(
name = "go_default_library",
srcs = [
"server.go",
"validator_count.go",
"validator_performance.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/validator",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/rpc/core:go_default_library",
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/sync:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//network/http:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//time/slots:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["validator_performance_test.go"],
srcs = [
"validator_count_test.go",
"validator_performance_test.go",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/core/epoch/precompute:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/rpc/core:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//beacon-chain/rpc/testutil:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/sync/initial-sync/testing:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//network/http:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
],
)

View File

@@ -2,15 +2,22 @@ package validator
import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/core"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
)
// Server defines a server implementation for HTTP endpoints, providing
// access data relevant to the Ethereum Beacon Chain.
type Server struct {
GenesisTimeFetcher blockchain.TimeFetcher
SyncChecker sync.Checker
HeadFetcher blockchain.HeadFetcher
CoreService *core.Service
GenesisTimeFetcher blockchain.TimeFetcher
SyncChecker sync.Checker
HeadFetcher blockchain.HeadFetcher
CoreService *core.Service
OptimisticModeFetcher blockchain.OptimisticModeFetcher
Stater lookup.Stater
ChainInfoFetcher blockchain.ChainInfoFetcher
BeaconDB db.ReadOnlyDatabase
FinalizationFetcher blockchain.FinalizationFetcher
}

View File

@@ -0,0 +1,189 @@
package validator
import (
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"go.opencensus.io/trace"
)
type ValidatorCountResponse struct {
ExecutionOptimistic string `json:"execution_optimistic"`
Finalized string `json:"finalized"`
Data []*ValidatorCount `json:"data"`
}
type ValidatorCount struct {
Status string `json:"status"`
Count string `json:"count"`
}
// GetValidatorCount is a HTTP handler that serves the GET /eth/v1/beacon/states/{state_id}/validator_count endpoint.
// It returns the total validator count according to the given statuses provided as a query parameter.
//
// The state ID is expected to be a valid Beacon Chain state identifier.
// The status query parameter can be an array of strings with the following values: pending_initialized, pending_queued, active_ongoing,
// active_exiting, active_slashed, exited_unslashed, exited_slashed, withdrawal_possible, withdrawal_done, active, pending, exited, withdrawal.
// The response is a JSON object containing the total validator count for the specified statuses.
//
// Example usage:
//
// GET /eth/v1/beacon/states/12345/validator_count?status=active&status=pending
//
// The above request will return a JSON response like:
//
// {
// "execution_optimistic": "false",
// "finalized": "true",
// "data": [
// {
// "status": "active",
// "count": "13"
// },
// {
// "status": "pending",
// "count": "6"
// }
// ]
// }
func (vs *Server) GetValidatorCount(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetValidatorCount")
defer span.End()
stateID := mux.Vars(r)["state_id"]
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateID), vs.OptimisticModeFetcher, vs.Stater, vs.ChainInfoFetcher, vs.BeaconDB)
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: fmt.Sprintf("could not check if slot's block is optimistic: %v", err),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}
st, err := vs.Stater.State(ctx, []byte(stateID))
if err != nil {
var errJson *http2.DefaultErrorJson
if _, ok := err.(*lookup.StateIdParseError); ok {
errJson = &http2.DefaultErrorJson{
Message: "invalid state ID",
Code: http.StatusBadRequest,
}
} else {
errJson = &http2.DefaultErrorJson{
Message: helpers.PrepareStateFetchError(err).Error(),
Code: http.StatusInternalServerError,
}
}
http2.WriteError(w, errJson)
return
}
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: fmt.Sprintf("could not calculate root of latest block header: %v", err),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}
isFinalized := vs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
var statusVals []validator.ValidatorStatus
for _, status := range r.URL.Query()["status"] {
statusVal, ok := ethpb.ValidatorStatus_value[strings.ToUpper(status)]
if !ok {
errJson := &http2.DefaultErrorJson{
Message: fmt.Sprintf("invalid status query parameter: %v", status),
Code: http.StatusBadRequest,
}
http2.WriteError(w, errJson)
return
}
statusVals = append(statusVals, validator.ValidatorStatus(statusVal))
}
// If no status was provided then consider all the statuses to return validator count for each status.
if len(statusVals) == 0 {
for _, val := range ethpb.ValidatorStatus_value {
statusVals = append(statusVals, validator.ValidatorStatus(val))
}
}
epoch := slots.ToEpoch(st.Slot())
valCount, err := validatorCountByStatus(st.Validators(), statusVals, epoch)
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: fmt.Sprintf("could not get validator count: %v", err),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}
valCountResponse := &ValidatorCountResponse{
ExecutionOptimistic: strconv.FormatBool(isOptimistic),
Finalized: strconv.FormatBool(isFinalized),
Data: valCount,
}
http2.WriteJson(w, valCountResponse)
}
// validatorCountByStatus returns a slice of validator count for each status in the given epoch.
func validatorCountByStatus(validators []*eth.Validator, statuses []validator.ValidatorStatus, epoch primitives.Epoch) ([]*ValidatorCount, error) {
countByStatus := make(map[validator.ValidatorStatus]uint64)
for _, val := range validators {
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
return nil, fmt.Errorf("could not convert validator: %v", err)
}
valStatus, err := helpers.ValidatorStatus(readOnlyVal, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator status: %v", err)
}
valSubStatus, err := helpers.ValidatorSubStatus(readOnlyVal, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator sub status: %v", err)
}
for _, status := range statuses {
if valStatus == status || valSubStatus == status {
countByStatus[status]++
}
}
}
var resp []*ValidatorCount
for status, count := range countByStatus {
resp = append(resp, &ValidatorCount{
Status: strings.ToLower(ethpb.ValidatorStatus_name[int32(status)]),
Count: strconv.FormatUint(count, 10),
})
}
// Sort the response slice according to status strings for deterministic ordering of validator count response.
sort.Slice(resp, func(i, j int) bool {
return resp[i].Status < resp[j].Status
})
return resp, nil
}

View File

@@ -0,0 +1,491 @@
package validator
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
neturl "net/url"
"strconv"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/gorilla/mux"
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
)
func TestGetValidatorCountInvalidRequest(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 10)
stateIdCheckerStateFunc := func(_ context.Context, stateId []byte) (state.BeaconState, error) {
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head", "genesis", "finalized", "justified":
return st, nil
default:
if len(stateId) == 32 {
return nil, nil
} else {
_, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
e := lookup.NewStateIdParseError(parseErr)
return nil, &e
}
return st, nil
}
}
}
tests := []struct {
name string
stater lookup.Stater
status string
stateID string
expectedErrorMessage string
statusCode int
}{
{
name: "invalid status",
stater: &testutil.MockStater{
BeaconState: st,
},
status: "helloworld",
stateID: "head",
expectedErrorMessage: "invalid status query parameter",
statusCode: http.StatusBadRequest,
},
{
name: "invalid state ID",
stater: &testutil.MockStater{StateProviderFunc: stateIdCheckerStateFunc},
stateID: "helloworld",
expectedErrorMessage: "invalid state ID",
statusCode: http.StatusBadRequest,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: false, FinalizedRoots: make(map[[32]byte]bool)}
server := &Server{
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
Stater: test.stater,
}
testRouter := mux.NewRouter()
testRouter.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", server.GetValidatorCount)
s := httptest.NewServer(testRouter)
defer s.Close()
queryParams := neturl.Values{}
queryParams.Add("status", test.status)
resp, err := http.Get(s.URL + fmt.Sprintf("/eth/v1/beacon/states/%s/validator_count?%s",
test.stateID, queryParams.Encode()))
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
var errJson http2.DefaultErrorJson
err = json.Unmarshal(body, &errJson)
require.NoError(t, err)
require.Equal(t, test.statusCode, errJson.Code)
require.StringContains(t, test.expectedErrorMessage, errJson.Message)
})
}
}
func TestGetValidatorCount(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 10)
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
validators := []*eth.Validator{
// Pending initialized.
{
ActivationEpoch: farFutureEpoch,
ActivationEligibilityEpoch: farFutureEpoch,
ExitEpoch: farFutureEpoch,
WithdrawableEpoch: farFutureEpoch,
},
// Pending queued.
{
ActivationEpoch: 10,
ActivationEligibilityEpoch: 4,
ExitEpoch: farFutureEpoch,
WithdrawableEpoch: farFutureEpoch,
},
// Active ongoing.
{
ActivationEpoch: 0,
ExitEpoch: farFutureEpoch,
},
// Active slashed.
{
ActivationEpoch: 0,
ExitEpoch: 30,
Slashed: true,
WithdrawableEpoch: 50,
},
// Active exiting.
{
ActivationEpoch: 0,
ExitEpoch: 30,
Slashed: false,
WithdrawableEpoch: 50,
},
// Exit slashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 50,
Slashed: true,
},
// Exit unslashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 50,
Slashed: false,
},
// Withdrawable (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
Slashed: false,
},
// Withdrawal done (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: 0,
Slashed: false,
},
}
for _, validator := range validators {
require.NoError(t, st.AppendValidator(validator))
require.NoError(t, st.AppendBalance(params.BeaconConfig().MaxEffectiveBalance))
}
tests := []struct {
name string
stateID string
statuses []string
currentEpoch int
expectedResponse ValidatorCountResponse
}{
{
name: "Head count active validators",
stateID: "head",
statuses: []string{"active"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active",
Count: "13",
},
},
},
},
{
name: "Head count active ongoing validators",
stateID: "head",
statuses: []string{"active_ongoing"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active_ongoing",
Count: "11",
},
},
},
},
{
name: "Head count active exiting validators",
stateID: "head",
statuses: []string{"active_exiting"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active_exiting",
Count: "1",
},
},
},
},
{
name: "Head count active slashed validators",
stateID: "head",
statuses: []string{"active_slashed"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active_slashed",
Count: "1",
},
},
},
},
{
name: "Head count pending validators",
stateID: "head",
statuses: []string{"pending"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "pending",
Count: "6",
},
},
},
},
{
name: "Head count pending initialized validators",
stateID: "head",
statuses: []string{"pending_initialized"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "pending_initialized",
Count: "1",
},
},
},
},
{
name: "Head count pending queued validators",
stateID: "head",
statuses: []string{"pending_queued"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "pending_queued",
Count: "5",
},
},
},
},
{
name: "Head count exited validators",
stateID: "head",
statuses: []string{"exited"},
currentEpoch: 35,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "exited",
Count: "6",
},
},
},
},
{
name: "Head count exited slashed validators",
stateID: "head",
statuses: []string{"exited_slashed"},
currentEpoch: 35,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "exited_slashed",
Count: "2",
},
},
},
},
{
name: "Head count exited unslashed validators",
stateID: "head",
statuses: []string{"exited_unslashed"},
currentEpoch: 35,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "exited_unslashed",
Count: "4",
},
},
},
},
{
name: "Head count withdrawal validators",
stateID: "head",
statuses: []string{"withdrawal"},
currentEpoch: 45,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "withdrawal",
Count: "2",
},
},
},
},
{
name: "Head count withdrawal possible validators",
stateID: "head",
statuses: []string{"withdrawal_possible"},
currentEpoch: 45,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "withdrawal_possible",
Count: "1",
},
},
},
},
{
name: "Head count withdrawal done validators",
stateID: "head",
statuses: []string{"withdrawal_done"},
currentEpoch: 45,
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "withdrawal_done",
Count: "1",
},
},
},
},
{
name: "Head count active and pending validators",
stateID: "head",
statuses: []string{"active", "pending"},
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active",
Count: "13",
},
{
Status: "pending",
Count: "6",
},
},
},
},
{
name: "Head count of ALL validators",
stateID: "head",
expectedResponse: ValidatorCountResponse{
ExecutionOptimistic: "false",
Finalized: "true",
Data: []*ValidatorCount{
{
Status: "active",
Count: "13",
},
{
Status: "active_exiting",
Count: "1",
},
{
Status: "active_ongoing",
Count: "11",
},
{
Status: "active_slashed",
Count: "1",
},
{
Status: "pending",
Count: "6",
},
{
Status: "pending_initialized",
Count: "1",
},
{
Status: "pending_queued",
Count: "5",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: false, FinalizedRoots: make(map[[32]byte]bool)}
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
chainService.FinalizedRoots[blockRoot] = true
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch*primitives.Slot(test.currentEpoch)))
server := &Server{
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
Stater: &testutil.MockStater{
BeaconState: st,
},
}
testRouter := mux.NewRouter()
testRouter.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", server.GetValidatorCount)
s := httptest.NewServer(testRouter)
defer s.Close()
queryParams := neturl.Values{}
for _, status := range test.statuses {
queryParams.Add("status", status)
}
resp, err := http.Get(s.URL + fmt.Sprintf("/eth/v1/beacon/states/%s/validator_count?%s",
test.stateID, queryParams.Encode()))
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
var count ValidatorCountResponse
err = json.Unmarshal(body, &count)
require.NoError(t, err)
require.DeepEqual(t, test.expectedResponse, count)
})
}
}

View File

@@ -402,12 +402,18 @@ func (s *Service) Start() {
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
}
httpServer := &httpserver.Server{
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
HeadFetcher: s.cfg.HeadFetcher,
SyncChecker: s.cfg.SyncService,
CoreService: coreService,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
HeadFetcher: s.cfg.HeadFetcher,
SyncChecker: s.cfg.SyncService,
CoreService: coreService,
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
Stater: stater,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
BeaconDB: s.cfg.BeaconDB,
FinalizationFetcher: s.cfg.FinalizationFetcher,
}
s.cfg.Router.HandleFunc("/prysm/validators/performance", httpServer.GetValidatorPerformance).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", httpServer.GetValidatorCount).Methods(http.MethodGet)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks", beaconChainServerV1.PublishBlockV2).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v2/beacon/blinded_blocks", beaconChainServerV1.PublishBlindedBlockV2).Methods(http.MethodPost)
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks/{block_id}/root", beaconChainServerV1.GetBlockRoot).Methods(http.MethodGet)

View File

@@ -3,24 +3,31 @@ package testutil
import (
"context"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
)
// MockStater is a fake implementation of lookup.Stater.
type MockStater struct {
BeaconState state.BeaconState
BeaconStateRoot []byte
StatesBySlot map[primitives.Slot]state.BeaconState
StatesByRoot map[[32]byte]state.BeaconState
BeaconState state.BeaconState
StateProviderFunc func(ctx context.Context, stateId []byte) (state.BeaconState, error)
BeaconStateRoot []byte
StatesBySlot map[primitives.Slot]state.BeaconState
StatesByRoot map[[32]byte]state.BeaconState
}
// State --
func (m *MockStater) State(_ context.Context, id []byte) (state.BeaconState, error) {
func (m *MockStater) State(ctx context.Context, id []byte) (state.BeaconState, error) {
if m.StateProviderFunc != nil {
return m.StateProviderFunc(ctx, id)
}
if m.BeaconState != nil {
return m.BeaconState, nil
}
return m.StatesByRoot[bytesutil.ToBytes32(id)], nil
}

View File

@@ -77,3 +77,8 @@ func NotEmpty(tb assertions.AssertionTestingTB, obj interface{}, msg ...interfac
func ErrorIs(tb assertions.AssertionTestingTB, err, target error, msg ...interface{}) {
assertions.ErrorIs(tb.Fatalf, err, target, msg)
}
// StringContains asserts that actual string contains expected message.
func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
assertions.StringContains(tb.Fatalf, expected, actual, true, msg)
}