Compare commits

...

12 Commits

Author SHA1 Message Date
Nishant Das
75338dd83d Fix User Agent In Builder Client (#12264) 2023-04-13 03:12:51 +00:00
terencechain
99eebe9bac Fix capella unblind block with bls field (#12263) 2023-04-12 18:47:34 -07:00
keithchew
6b1efff4e8 allow setting GOMAXPROCS from environment variable (#12256)
* allow setting GOMAXPROCS from environment variable

* remove unused import
2023-04-10 23:28:48 -04:00
Preston Van Loon
763e9e3361 Update go to version 1.19.8 (#12238)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-04-10 19:03:24 +00:00
Nishant Das
37182168e3 Fix Deadlock in StreamChainHead (#12250)
* fix it possibly

* buffer it more

* fix test
2023-04-07 15:41:31 -05:00
terencechain
0325741318 Add orphaned operations to the appropriate pools in saveOrphanedOps() and mark included slashings in prunePostBlockOperationPools(). (#12249)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-04-06 16:26:01 -05:00
Preston Van Loon
150e8aa14d Remove unused beacon-chain/server binary (#12241)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-04-06 20:56:55 +00:00
kasey
f4307a902c build tag to exclude mainnet genesis from prysmctl (#12244)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-04-06 19:45:35 +00:00
james-prysm
d257ef1742 Builder: fix nil panic edgecase (#12236)
* adding fix for buildervalue nil

* fixing linting

* changing based on review comment

* editing based on suggestions

* fixing unit test

* fixing linting

* fall back to local

* fix linting

* updating based on slack feedback
2023-04-06 14:19:51 -05:00
Nishant Das
aad7aa79d4 Fix Next State Mismatch (#12247)
* fix mismatch

* add regression test
2023-04-06 17:52:15 +08:00
terencechain
2eb2f87913 Default to local payload should set block to not blind (#12243) 2023-04-05 23:29:41 +00:00
Patrice Vignola
9214364c5e Add REST API endpoint for beacon chain client's ListValidators (#12228)
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-04-05 13:00:49 +02:00
32 changed files with 1048 additions and 190 deletions

View File

@@ -164,7 +164,7 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe
go_rules_dependencies()
go_register_toolchains(
go_version = "1.19.7",
go_version = "1.19.8",
nogo = "@//:nogo",
)

View File

@@ -157,6 +157,7 @@ func (c *Client) do(ctx context.Context, method string, path string, body io.Rea
if err != nil {
return
}
req.Header.Add("User-Agent", version.BuildData())
for _, o := range opts {
o(req)
}

View File

@@ -403,6 +403,19 @@ func (s *Service) saveOrphanedOperations(ctx context.Context, orphanedRoot [32]b
}
saveOrphanedAttCount.Inc()
}
for _, as := range orphanedBlk.Block().Body().AttesterSlashings() {
if err := s.cfg.SlashingPool.InsertAttesterSlashing(ctx, s.headStateReadOnly(ctx), as); err != nil {
log.WithError(err).Error("Could not insert reorg attester slashing")
}
}
for _, vs := range orphanedBlk.Block().Body().ProposerSlashings() {
if err := s.cfg.SlashingPool.InsertProposerSlashing(ctx, s.headStateReadOnly(ctx), vs); err != nil {
log.WithError(err).Error("Could not insert reorg proposer slashing")
}
}
for _, v := range orphanedBlk.Block().Body().VoluntaryExits() {
s.cfg.ExitPool.InsertVoluntaryExit(v)
}
if orphanedBlk.Version() >= version.Capella {
changes, err := orphanedBlk.Block().Body().BLSToExecutionChanges()
if err != nil {

View File

@@ -326,6 +326,88 @@ func TestSaveOrphanedAtts(t *testing.T) {
require.DeepEqual(t, wantAtts, atts)
}
func TestSaveOrphanedOps(t *testing.T) {
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig()
config.ShardCommitteePeriod = 0
params.OverrideBeaconConfig(config)
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := setupBeaconChain(t, beaconDB)
service.genesisTime = time.Now().Add(time.Duration(-10*int64(1)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second)
// Chain setup
// 0 -- 1 -- 2 -- 3
// \-4
st, keys := util.DeterministicGenesisState(t, 64)
service.head = &head{state: st}
blkG, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 0)
assert.NoError(t, err)
util.SaveBlock(t, ctx, service.cfg.BeaconDB, blkG)
rG, err := blkG.Block.HashTreeRoot()
require.NoError(t, err)
blk1, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 1)
assert.NoError(t, err)
blk1.Block.ParentRoot = rG[:]
r1, err := blk1.Block.HashTreeRoot()
require.NoError(t, err)
blk2, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 2)
assert.NoError(t, err)
blk2.Block.ParentRoot = r1[:]
r2, err := blk2.Block.HashTreeRoot()
require.NoError(t, err)
blkConfig := util.DefaultBlockGenConfig()
blkConfig.NumBLSChanges = 5
blkConfig.NumProposerSlashings = 1
blkConfig.NumAttesterSlashings = 1
blkConfig.NumVoluntaryExits = 1
blk3, err := util.GenerateFullBlock(st, keys, blkConfig, 3)
assert.NoError(t, err)
blk3.Block.ParentRoot = r2[:]
r3, err := blk3.Block.HashTreeRoot()
require.NoError(t, err)
blk4 := util.NewBeaconBlock()
blk4.Block.Slot = 4
blk4.Block.ParentRoot = rG[:]
r4, err := blk4.Block.HashTreeRoot()
require.NoError(t, err)
ojc := &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
ofc := &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk3, blk4} {
r, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
require.NoError(t, err)
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
util.SaveBlock(t, ctx, beaconDB, blk)
}
require.NoError(t, service.saveOrphanedOperations(ctx, r3, r4))
require.Equal(t, 3, service.cfg.AttPool.AggregatedAttestationCount())
wantAtts := []*ethpb.Attestation{
blk3.Block.Body.Attestations[0],
blk2.Block.Body.Attestations[0],
blk1.Block.Body.Attestations[0],
}
atts := service.cfg.AttPool.AggregatedAttestations()
sort.Slice(atts, func(i, j int) bool {
return atts[i].Data.Slot > atts[j].Data.Slot
})
require.DeepEqual(t, wantAtts, atts)
require.Equal(t, 1, len(service.cfg.SlashingPool.PendingProposerSlashings(ctx, st, false)))
require.Equal(t, 1, len(service.cfg.SlashingPool.PendingAttesterSlashings(ctx, st, false)))
exits, err := service.cfg.ExitPool.PendingExits()
require.NoError(t, err)
require.Equal(t, 1, len(exits))
}
func TestSaveOrphanedAtts_CanFilter(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)

View File

@@ -179,10 +179,14 @@ func (s *Service) prunePostBlockOperationPools(ctx context.Context, blk interfac
return errors.Wrap(err, "could not process BLSToExecutionChanges")
}
// Mark attester slashings as seen so we don't include same ones in future blocks.
// Mark slashings as seen so we don't include same ones in future blocks.
for _, as := range blk.Block().Body().AttesterSlashings() {
s.cfg.SlashingPool.MarkIncludedAttesterSlashing(as)
}
for _, ps := range blk.Block().Body().ProposerSlashings() {
s.cfg.SlashingPool.MarkIncludedProposerSlashing(ps)
}
return nil
}

View File

@@ -23,6 +23,8 @@ import (
mockExecution "github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/attestations"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/slashings"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/voluntaryexits"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
@@ -131,6 +133,8 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service {
WithDepositCache(depositCache),
WithChainStartFetcher(web3Service),
WithAttestationPool(attestations.NewPool()),
WithSlashingPool(slashings.NewPool()),
WithExitPool(voluntaryexits.NewPool()),
WithP2PBroadcaster(&mockBroadcaster{}),
WithStateNotifier(&mockBeaconNode{}),
WithForkChoiceStore(fc),

View File

@@ -9,6 +9,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
types "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
)
@@ -36,14 +37,14 @@ var (
// NextSlotState returns the saved state for the given blockroot.
// It returns the last updated state if it matches. Otherwise it returns the previously
// updated state if it matches its root. If no root matches it returns nil
func NextSlotState(root []byte) state.BeaconState {
func NextSlotState(root []byte, wantedSlot types.Slot) state.BeaconState {
nsc.Lock()
defer nsc.Unlock()
if bytes.Equal(root, nsc.lastRoot) {
if bytes.Equal(root, nsc.lastRoot) && nsc.lastState.Slot() <= wantedSlot {
nextSlotCacheHit.Inc()
return nsc.lastState.Copy()
}
if bytes.Equal(root, nsc.prevRoot) {
if bytes.Equal(root, nsc.prevRoot) && nsc.prevState.Slot() <= wantedSlot {
nextSlotCacheHit.Inc()
return nsc.prevState.Copy()
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
)
@@ -13,12 +14,12 @@ import (
func TestTrailingSlotState_RoundTrip(t *testing.T) {
ctx := context.Background()
r := []byte{'a'}
s := transition.NextSlotState(r)
s := transition.NextSlotState(r, 0)
require.Equal(t, nil, s)
s, _ = util.DeterministicGenesisState(t, 1)
require.NoError(t, transition.UpdateNextSlotCache(ctx, r, s))
s = transition.NextSlotState(r)
s = transition.NextSlotState(r, 1)
require.Equal(t, primitives.Slot(1), s.Slot())
lastRoot, lastState := transition.LastCachedState()
@@ -26,10 +27,23 @@ func TestTrailingSlotState_RoundTrip(t *testing.T) {
require.Equal(t, s.Slot(), lastState.Slot())
require.NoError(t, transition.UpdateNextSlotCache(ctx, r, s))
s = transition.NextSlotState(r)
s = transition.NextSlotState(r, 2)
require.Equal(t, primitives.Slot(2), s.Slot())
lastRoot, lastState = transition.LastCachedState()
require.DeepEqual(t, r, lastRoot)
require.Equal(t, s.Slot(), lastState.Slot())
}
func TestTrailingSlotState_StateAdvancedBeyondRequest(t *testing.T) {
ctx := context.Background()
r := []byte{'a'}
s := transition.NextSlotState(r, 0)
require.Equal(t, nil, s)
s, _ = util.DeterministicGenesisState(t, 1)
assert.NoError(t, s.SetSlot(2))
require.NoError(t, transition.UpdateNextSlotCache(ctx, r, s))
s = transition.NextSlotState(r, 1)
require.Equal(t, nil, s)
}

View File

@@ -147,7 +147,7 @@ func ProcessSlotsUsingNextSlotCache(
ctx, span := trace.StartSpan(ctx, "core.state.ProcessSlotsUsingNextSlotCache")
defer span.End()
nextSlotState := NextSlotState(parentRoot)
nextSlotState := NextSlotState(parentRoot, slot)
if nextSlotState != nil {
parentState = nextSlotState
}

View File

@@ -346,12 +346,19 @@ func (bs *Server) StreamBlocks(req *ethpb.StreamBlocksRequest, stream ethpb.Beac
// StreamChainHead to clients every single time the head block and state of the chain change.
// DEPRECATED: This endpoint is superseded by the /eth/v1/events Beacon API endpoint
func (bs *Server) StreamChainHead(_ *emptypb.Empty, stream ethpb.BeaconChain_StreamChainHeadServer) error {
stateChannel := make(chan *feed.Event, 1)
stateChannel := make(chan *feed.Event, 4)
stateSub := bs.StateNotifier.StateFeed().Subscribe(stateChannel)
defer stateSub.Unsubscribe()
for {
select {
case stateEvent := <-stateChannel:
// In the event our node is in sync mode
// we do not send the chainhead to the caller
// due to the possibility of deadlocks when retrieving
// all the chain related data.
if bs.SyncChecker.Syncing() {
continue
}
if stateEvent.Type == statefeed.BlockProcessed {
res, err := bs.chainHeadRetrieval(stream.Context())
if err != nil {

View File

@@ -13,6 +13,7 @@ import (
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
"github.com/prysmaticlabs/prysm/v4/config/features"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
@@ -287,6 +288,7 @@ func TestServer_StreamChainHead_OnHeadUpdated(t *testing.T) {
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
OptimisticModeFetcher: &chainMock.ChainService{},
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
exitRoutine := make(chan bool)
ctrl := gomock.NewController(t)

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"math/big"
"time"
"github.com/pkg/errors"
@@ -68,6 +69,7 @@ func (vs *Server) setExecutionData(ctx context.Context, blk interfaces.SignedBea
v, err = builderPayload.Value()
if err != nil {
log.WithError(err).Warn("Proposer: failed to get builder payload value") // Default to local if can't get builder value.
v = big.NewInt(0) // Default to local if can't get builder value.
}
builderValue := v.Uint64()
@@ -86,6 +88,7 @@ func (vs *Server) setExecutionData(ctx context.Context, blk interfaces.SignedBea
blk.SetBlinded(true)
if err := blk.SetExecution(builderPayload); err != nil {
log.WithError(err).Warn("Proposer: failed to set builder payload")
blk.SetBlinded(false)
} else {
return nil
}
@@ -102,12 +105,12 @@ func (vs *Server) setExecutionData(ctx context.Context, blk interfaces.SignedBea
blk.SetBlinded(true)
if err := blk.SetExecution(builderPayload); err != nil {
log.WithError(err).Warn("Proposer: failed to set builder payload")
blk.SetBlinded(false)
} else {
return nil
}
}
}
}
executionData, err := vs.getExecutionPayload(ctx, slot, idx, blk.Block().ParentRoot(), headState)
@@ -148,6 +151,9 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot primitiv
if signedBid.IsNil() {
return nil, errors.New("builder returned nil bid")
}
if signedBid.Version() != b.Version() {
return nil, fmt.Errorf("builder bid response version: %d is different from head block version: %d", signedBid.Version(), b.Version())
}
bid, err := signedBid.Message()
if err != nil {
return nil, errors.Wrap(err, "could not get bid")

View File

@@ -172,7 +172,7 @@ func TestServer_setExecutionData(t *testing.T) {
vs.BlockBuilder = &builderTest.MockBuilderService{
BidCapella: sBid,
}
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockBellatrix())
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
require.NoError(t, err)
chain := &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New(), Genesis: time.Now(), Block: wb}
vs.ForkFetcher = chain

View File

@@ -66,6 +66,10 @@ func (vs *Server) unblindBuilderBlockCapella(ctx context.Context, b interfaces.R
randaoReveal := b.Block().Body().RandaoReveal()
graffiti := b.Block().Body().Graffiti()
sig := b.Signature()
blsToExecChange, err := b.Block().Body().BLSToExecutionChanges()
if err != nil {
return nil, errors.Wrap(err, "could not get bls to execution changes")
}
sb := &ethpb.SignedBlindedBeaconBlockCapella{
Block: &ethpb.BlindedBeaconBlockCapella{
Slot: b.Block().Slot(),
@@ -83,6 +87,7 @@ func (vs *Server) unblindBuilderBlockCapella(ctx context.Context, b interfaces.R
VoluntaryExits: b.Block().Body().VoluntaryExits(),
SyncAggregate: agg,
ExecutionPayloadHeader: header,
BlsToExecutionChanges: blsToExecChange,
},
},
Signature: sig[:],
@@ -122,16 +127,17 @@ func (vs *Server) unblindBuilderBlockCapella(ctx context.Context, b interfaces.R
ParentRoot: sb.Block.ParentRoot,
StateRoot: sb.Block.StateRoot,
Body: &ethpb.BeaconBlockBodyCapella{
RandaoReveal: sb.Block.Body.RandaoReveal,
Eth1Data: sb.Block.Body.Eth1Data,
Graffiti: sb.Block.Body.Graffiti,
ProposerSlashings: sb.Block.Body.ProposerSlashings,
AttesterSlashings: sb.Block.Body.AttesterSlashings,
Attestations: sb.Block.Body.Attestations,
Deposits: sb.Block.Body.Deposits,
VoluntaryExits: sb.Block.Body.VoluntaryExits,
SyncAggregate: agg,
ExecutionPayload: capellaPayload,
RandaoReveal: sb.Block.Body.RandaoReveal,
Eth1Data: sb.Block.Body.Eth1Data,
Graffiti: sb.Block.Body.Graffiti,
ProposerSlashings: sb.Block.Body.ProposerSlashings,
AttesterSlashings: sb.Block.Body.AttesterSlashings,
Attestations: sb.Block.Body.Attestations,
Deposits: sb.Block.Body.Deposits,
VoluntaryExits: sb.Block.Body.VoluntaryExits,
SyncAggregate: agg,
ExecutionPayload: capellaPayload,
BlsToExecutionChanges: blsToExecChange,
},
},
Signature: sb.Signature,

View File

@@ -11,6 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
v1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
)
@@ -100,6 +101,12 @@ func TestServer_unblindBuilderCapellaBlock(t *testing.T) {
GasLimit: 123,
WithdrawalsRoot: wdRoot[:],
}
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 1, FromBlsPubkey: []byte{'a'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 2, FromBlsPubkey: []byte{'b'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 3, FromBlsPubkey: []byte{'c'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 4, FromBlsPubkey: []byte{'d'}}},
}
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb
@@ -113,6 +120,12 @@ func TestServer_unblindBuilderCapellaBlock(t *testing.T) {
b.Block.Slot = 1
b.Block.ProposerIndex = 2
b.Block.Body.ExecutionPayload = p
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 1, FromBlsPubkey: []byte{'a'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 2, FromBlsPubkey: []byte{'b'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 3, FromBlsPubkey: []byte{'c'}}},
{Message: &eth.BLSToExecutionChange{ValidatorIndex: 4, FromBlsPubkey: []byte{'d'}}},
}
wb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
return wb

View File

@@ -1,28 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"log.go",
"main.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/server",
visibility = ["//visibility:private"],
deps = [
"//api/gateway:go_default_library",
"//beacon-chain/gateway:go_default_library",
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//cmd/beacon-chain/flags:go_default_library",
"//runtime/maxprocs:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_joonix_log//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_binary(
name = "server",
embed = [":go_default_library"],
visibility = ["//visibility:private"],
)

View File

@@ -1,5 +0,0 @@
package main
import "github.com/sirupsen/logrus"
var log = logrus.New()

View File

@@ -1,96 +0,0 @@
// Package main allows for creation of an HTTP-JSON to gRPC
// gateway as a binary go process.
package main
import (
"context"
"flag"
"fmt"
"math"
"net/http"
"strings"
"github.com/gorilla/mux"
joonix "github.com/joonix/log"
"github.com/prysmaticlabs/prysm/v4/api/gateway"
beaconGateway "github.com/prysmaticlabs/prysm/v4/beacon-chain/gateway"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
_ "github.com/prysmaticlabs/prysm/v4/runtime/maxprocs"
"github.com/sirupsen/logrus"
)
var (
beaconRPC = flag.String("beacon-rpc", "localhost:4000", "Beacon chain gRPC endpoint")
port = flag.Int("port", 8000, "Port to serve on")
host = flag.String("host", "127.0.0.1", "Host to serve on")
debug = flag.Bool("debug", false, "Enable debug logging")
allowedOrigins = flag.String("corsdomain", "localhost:4242", "A comma separated list of CORS domains to allow")
enableDebugRPCEndpoints = flag.Bool("enable-debug-rpc-endpoints", false, "Enable debug rpc endpoints such as /eth/v1alpha1/beacon/state")
grpcMaxMsgSize = flag.Int("grpc-max-msg-size", math.MaxInt32, "Integer to define max recieve message call size")
httpModules = flag.String(
"http-modules",
strings.Join([]string{flags.PrysmAPIModule, flags.EthAPIModule}, ","),
"Comma-separated list of API module names. Possible values: `"+flags.PrysmAPIModule+`,`+flags.EthAPIModule+"`.",
)
)
func init() {
logrus.SetFormatter(joonix.NewFormatter())
}
func main() {
flag.Parse()
if *debug {
log.SetLevel(logrus.DebugLevel)
}
r := mux.NewRouter()
gatewayConfig := beaconGateway.DefaultConfig(*enableDebugRPCEndpoints, *httpModules)
muxs := make([]*gateway.PbMux, 0)
if gatewayConfig.V1AlphaPbMux != nil {
muxs = append(muxs, gatewayConfig.V1AlphaPbMux)
}
if gatewayConfig.EthPbMux != nil {
muxs = append(muxs, gatewayConfig.EthPbMux)
}
opts := []gateway.Option{
gateway.WithRouter(r),
gateway.WithPbHandlers(muxs),
gateway.WithMuxHandler(gatewayConfig.Handler),
gateway.WithRemoteAddr(*beaconRPC),
gateway.WithGatewayAddr(fmt.Sprintf("%s:%d", *host, *port)),
gateway.WithAllowedOrigins(strings.Split(*allowedOrigins, ",")),
gateway.WithMaxCallRecvMsgSize(uint64(*grpcMaxMsgSize)),
}
if flags.EnableHTTPEthAPI(*httpModules) {
opts = append(opts, gateway.WithApiMiddleware(&apimiddleware.BeaconEndpointFactory{}))
}
gw, err := gateway.New(context.Background(), opts...)
if err != nil {
log.Fatal(err)
}
r.HandleFunc("/swagger/", gateway.SwaggerServer())
r.HandleFunc("/healthz", healthzServer(gw))
gw.Start()
select {}
}
// healthzServer returns a simple health handler which returns ok.
func healthzServer(gw *gateway.Gateway) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
if err := gw.Status(); err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
if _, err := fmt.Fprintln(w, "ok"); err != nil {
log.WithError(err).Error("failed to respond to healthz")
}
}
}

View File

@@ -2,7 +2,10 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["genesis.go"],
srcs = [
"genesis.go",
"genesis_mainnet.go",
],
embedsrcs = ["mainnet.ssz.snappy"],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/genesis",
visibility = ["//beacon-chain/db:__subpackages__"],

View File

@@ -6,24 +6,18 @@ import (
"github.com/golang/snappy"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/config/params"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
var (
//go:embed mainnet.ssz.snappy
mainnetRawSSZCompressed []byte // 1.8Mb
)
var embeddedStates = map[string]*[]byte{}
// State returns a copy of the genesis state from a hardcoded value.
func State(name string) (state.BeaconState, error) {
switch name {
case params.MainnetName:
return load(mainnetRawSSZCompressed)
default:
// No state found.
return nil, nil
sb, exists := embeddedStates[name]
if exists {
return load(*sb)
}
return nil, nil
}
// load a compressed ssz state file into a beacon state struct.

View File

@@ -0,0 +1,19 @@
//go:build !noMainnetGenesis
// +build !noMainnetGenesis
package genesis
import (
_ "embed"
"github.com/prysmaticlabs/prysm/v4/config/params"
)
var (
//go:embed mainnet.ssz.snappy
mainnetRawSSZCompressed []byte // 1.8Mb
)
func init() {
embeddedStates[params.MainnetName] = &mainnetRawSSZCompressed
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
runtimeDebug "runtime/debug"
gethlog "github.com/ethereum/go-ethereum/log"
@@ -198,7 +197,6 @@ func main() {
if ctx.IsSet(flags.SetGCPercent.Name) {
runtimeDebug.SetGCPercent(ctx.Int(flags.SetGCPercent.Name))
}
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
return err
}

View File

@@ -110,5 +110,6 @@ docker_push(
go_binary(
name = "prysmctl",
embed = [":go_default_library"],
gotags = ["noMainnetGenesis"],
visibility = ["//visibility:public"],
)

View File

@@ -7,7 +7,6 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
runtimeDebug "runtime/debug"
joonix "github.com/joonix/log"
@@ -183,7 +182,6 @@ func main() {
log.WithError(err).Error("Cannot update data directory")
}
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
return err
}

View File

@@ -66,6 +66,7 @@ go_test(
srcs = [
"activation_test.go",
"attestation_data_test.go",
"beacon_api_beacon_chain_client_test.go",
"beacon_api_helpers_test.go",
"beacon_api_node_client_test.go",
"beacon_api_validator_client_test.go",

View File

@@ -3,16 +3,24 @@ package beacon_api
import (
"context"
"net/http"
"reflect"
"strconv"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"github.com/prysmaticlabs/prysm/v4/validator/client/iface"
)
type beaconApiBeaconChainClient struct {
fallbackClient iface.BeaconChainClient
jsonRestHandler jsonRestHandler
fallbackClient iface.BeaconChainClient
jsonRestHandler jsonRestHandler
stateValidatorsProvider stateValidatorsProvider
}
func (c beaconApiBeaconChainClient) GetChainHead(ctx context.Context, in *empty.Empty) (*ethpb.ChainHead, error) {
@@ -34,12 +42,166 @@ func (c beaconApiBeaconChainClient) ListValidatorBalances(ctx context.Context, i
}
func (c beaconApiBeaconChainClient) ListValidators(ctx context.Context, in *ethpb.ListValidatorsRequest) (*ethpb.Validators, error) {
if c.fallbackClient != nil {
return c.fallbackClient.ListValidators(ctx, in)
pageSize := in.PageSize
// We follow the gRPC behavior here, which returns a maximum of 250 results when pageSize == 0
if pageSize == 0 {
pageSize = 250
}
// TODO: Implement me
panic("beaconApiBeaconChainClient.ListValidators is not implemented. To use a fallback client, pass a fallback client as the last argument of NewBeaconApiBeaconChainClientWithFallback.")
var pageToken uint64
var err error
if in.PageToken != "" {
if pageToken, err = strconv.ParseUint(in.PageToken, 10, 64); err != nil {
return nil, errors.Wrapf(err, "failed to parse page token `%s`", in.PageToken)
}
}
var statuses []string
if in.Active {
statuses = []string{"active"}
}
pubkeys := make([]string, len(in.PublicKeys))
for idx, pubkey := range in.PublicKeys {
pubkeys[idx] = hexutil.Encode(pubkey)
}
var stateValidators *apimiddleware.StateValidatorsResponseJson
var epoch primitives.Epoch
switch queryFilter := in.QueryFilter.(type) {
case *ethpb.ListValidatorsRequest_Epoch:
slot, err := slots.EpochStart(queryFilter.Epoch)
if err != nil {
return nil, errors.Wrapf(err, "failed to get first slot for epoch `%d`", queryFilter.Epoch)
}
if stateValidators, err = c.stateValidatorsProvider.GetStateValidatorsForSlot(ctx, slot, pubkeys, in.Indices, statuses); err != nil {
return nil, errors.Wrapf(err, "failed to get state validators for slot `%d`", slot)
}
epoch = slots.ToEpoch(slot)
case *ethpb.ListValidatorsRequest_Genesis:
if stateValidators, err = c.stateValidatorsProvider.GetStateValidatorsForSlot(ctx, 0, pubkeys, in.Indices, statuses); err != nil {
return nil, errors.Wrapf(err, "failed to get genesis state validators")
}
epoch = 0
case nil:
if stateValidators, err = c.stateValidatorsProvider.GetStateValidatorsForHead(ctx, pubkeys, in.Indices, statuses); err != nil {
return nil, errors.Wrap(err, "failed to get head state validators")
}
blockHeader := apimiddleware.BlockHeaderResponseJson{}
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, "/eth/v1/beacon/headers/head", &blockHeader); err != nil {
return nil, errors.Wrap(err, "failed to get head block header")
}
if blockHeader.Data == nil || blockHeader.Data.Header == nil {
return nil, errors.New("block header data is nil")
}
if blockHeader.Data.Header.Message == nil {
return nil, errors.New("block header message is nil")
}
slot, err := strconv.ParseUint(blockHeader.Data.Header.Message.Slot, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse header slot `%s`", blockHeader.Data.Header.Message.Slot)
}
epoch = slots.ToEpoch(primitives.Slot(slot))
default:
return nil, errors.Errorf("unsupported query filter type `%v`", reflect.TypeOf(queryFilter))
}
if stateValidators.Data == nil {
return nil, errors.New("state validators data is nil")
}
start := pageToken * uint64(pageSize)
if start > uint64(len(stateValidators.Data)) {
start = uint64(len(stateValidators.Data))
}
end := start + uint64(pageSize)
if end > uint64(len(stateValidators.Data)) {
end = uint64(len(stateValidators.Data))
}
validators := make([]*ethpb.Validators_ValidatorContainer, end-start)
for idx := start; idx < end; idx++ {
stateValidator := stateValidators.Data[idx]
if stateValidator.Validator == nil {
return nil, errors.Errorf("state validator at index `%d` is nil", idx)
}
pubkey, err := hexutil.Decode(stateValidator.Validator.PublicKey)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode validator pubkey `%s`", stateValidator.Validator.PublicKey)
}
withdrawalCredentials, err := hexutil.Decode(stateValidator.Validator.WithdrawalCredentials)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode validator withdrawal credentials `%s`", stateValidator.Validator.WithdrawalCredentials)
}
effectiveBalance, err := strconv.ParseUint(stateValidator.Validator.EffectiveBalance, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator effective balance `%s`", stateValidator.Validator.EffectiveBalance)
}
validatorIndex, err := strconv.ParseUint(stateValidator.Index, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator index `%s`", stateValidator.Index)
}
activationEligibilityEpoch, err := strconv.ParseUint(stateValidator.Validator.ActivationEligibilityEpoch, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator activation eligibility epoch `%s`", stateValidator.Validator.ActivationEligibilityEpoch)
}
activationEpoch, err := strconv.ParseUint(stateValidator.Validator.ActivationEpoch, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator activation epoch `%s`", stateValidator.Validator.ActivationEpoch)
}
exitEpoch, err := strconv.ParseUint(stateValidator.Validator.ExitEpoch, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator exit epoch `%s`", stateValidator.Validator.ExitEpoch)
}
withdrawableEpoch, err := strconv.ParseUint(stateValidator.Validator.WithdrawableEpoch, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse validator withdrawable epoch `%s`", stateValidator.Validator.WithdrawableEpoch)
}
validators[idx-start] = &ethpb.Validators_ValidatorContainer{
Index: primitives.ValidatorIndex(validatorIndex),
Validator: &ethpb.Validator{
PublicKey: pubkey,
WithdrawalCredentials: withdrawalCredentials,
EffectiveBalance: effectiveBalance,
Slashed: stateValidator.Validator.Slashed,
ActivationEligibilityEpoch: primitives.Epoch(activationEligibilityEpoch),
ActivationEpoch: primitives.Epoch(activationEpoch),
ExitEpoch: primitives.Epoch(exitEpoch),
WithdrawableEpoch: primitives.Epoch(withdrawableEpoch),
},
}
}
var nextPageToken string
if end < uint64(len(stateValidators.Data)) {
nextPageToken = strconv.FormatUint(pageToken+1, 10)
}
return &ethpb.Validators{
TotalSize: int32(len(stateValidators.Data)),
Epoch: epoch,
ValidatorList: validators,
NextPageToken: nextPageToken,
}, nil
}
func (c beaconApiBeaconChainClient) GetValidatorQueue(ctx context.Context, in *empty.Empty) (*ethpb.ValidatorQueue, error) {
@@ -76,7 +238,8 @@ func NewBeaconApiBeaconChainClientWithFallback(host string, timeout time.Duratio
}
return &beaconApiBeaconChainClient{
jsonRestHandler: jsonRestHandler,
fallbackClient: fallbackClient,
jsonRestHandler: jsonRestHandler,
fallbackClient: fallbackClient,
stateValidatorsProvider: beaconApiStateValidatorsProvider{jsonRestHandler: jsonRestHandler},
}
}

View File

@@ -0,0 +1,579 @@
package beacon_api
import (
"context"
"errors"
"fmt"
"math"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/client/beacon-api/mock"
)
func TestListValidators(t *testing.T) {
const blockHeaderEndpoint = "/eth/v1/beacon/headers/head"
t.Run("invalid token", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
beaconChainClient := beaconApiBeaconChainClient{}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
PageToken: "foo",
})
assert.ErrorContains(t, "failed to parse page token `foo`", err)
})
t.Run("query filter epoch overflow", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
beaconChainClient := beaconApiBeaconChainClient{}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: &ethpb.ListValidatorsRequest_Epoch{
Epoch: math.MaxUint64,
},
})
assert.ErrorContains(t, fmt.Sprintf("failed to get first slot for epoch `%d`", uint64(math.MaxUint64)), err)
})
t.Run("fails to get validators for epoch filter", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForSlot(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
nil,
errors.New("foo error"),
)
beaconChainClient := beaconApiBeaconChainClient{stateValidatorsProvider: stateValidatorsProvider}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: &ethpb.ListValidatorsRequest_Epoch{
Epoch: 0,
},
})
assert.ErrorContains(t, "failed to get state validators for slot `0`: foo error", err)
})
t.Run("fails to get validators for genesis filter", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForSlot(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
nil,
errors.New("bar error"),
)
beaconChainClient := beaconApiBeaconChainClient{stateValidatorsProvider: stateValidatorsProvider}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: &ethpb.ListValidatorsRequest_Genesis{},
})
assert.ErrorContains(t, "failed to get genesis state validators: bar error", err)
})
t.Run("fails to get validators for nil filter", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForHead(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(
nil,
errors.New("foo error"),
)
beaconChainClient := beaconApiBeaconChainClient{stateValidatorsProvider: stateValidatorsProvider}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: nil,
})
assert.ErrorContains(t, "failed to get head state validators: foo error", err)
})
t.Run("fails to get latest block header for nil filter", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForHead(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(
nil,
nil,
)
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(ctx, blockHeaderEndpoint, gomock.Any()).Return(
nil,
errors.New("bar error"),
)
beaconChainClient := beaconApiBeaconChainClient{
stateValidatorsProvider: stateValidatorsProvider,
jsonRestHandler: jsonRestHandler,
}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: nil,
})
assert.ErrorContains(t, "failed to get head block header: bar error", err)
})
t.Run("fails to read block header response", func(t *testing.T) {
testCases := []struct {
name string
expectedError string
blockHeaderResponse apimiddleware.BlockHeaderResponseJson
}{
{
name: "nil data",
blockHeaderResponse: apimiddleware.BlockHeaderResponseJson{
Data: nil,
},
expectedError: "block header data is nil",
},
{
name: "nil data header",
blockHeaderResponse: apimiddleware.BlockHeaderResponseJson{
Data: &apimiddleware.BlockHeaderContainerJson{
Header: nil,
},
},
expectedError: "block header data is nil",
},
{
name: "nil message",
blockHeaderResponse: apimiddleware.BlockHeaderResponseJson{
Data: &apimiddleware.BlockHeaderContainerJson{
Header: &apimiddleware.BeaconBlockHeaderContainerJson{
Message: nil,
},
},
},
expectedError: "block header message is nil",
},
{
name: "invalid header slot",
blockHeaderResponse: apimiddleware.BlockHeaderResponseJson{
Data: &apimiddleware.BlockHeaderContainerJson{
Header: &apimiddleware.BeaconBlockHeaderContainerJson{
Message: &apimiddleware.BeaconBlockHeaderJson{
Slot: "foo",
},
},
},
},
expectedError: "failed to parse header slot `foo`",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForHead(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(
nil,
nil,
)
jsonRestHandler := mock.NewMockjsonRestHandler(ctrl)
jsonRestHandler.EXPECT().GetRestJsonResponse(ctx, blockHeaderEndpoint, gomock.Any()).Return(
nil,
nil,
).SetArg(
2,
testCase.blockHeaderResponse,
)
beaconChainClient := beaconApiBeaconChainClient{
stateValidatorsProvider: stateValidatorsProvider,
jsonRestHandler: jsonRestHandler,
}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: nil,
})
assert.ErrorContains(t, testCase.expectedError, err)
})
}
})
t.Run("fails to get validators for genesis filter", func(t *testing.T) {
generateValidStateValidatorsResponse := func() *apimiddleware.StateValidatorsResponseJson {
return &apimiddleware.StateValidatorsResponseJson{
Data: []*apimiddleware.ValidatorContainerJson{
{
Index: "1",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode([]byte{3}),
WithdrawalCredentials: hexutil.Encode([]byte{4}),
EffectiveBalance: "5",
Slashed: true,
ActivationEligibilityEpoch: "6",
ActivationEpoch: "7",
ExitEpoch: "8",
WithdrawableEpoch: "9",
},
},
},
}
}
testCases := []struct {
name string
generateStateValidatorsResponse func() *apimiddleware.StateValidatorsResponseJson
expectedError string
}{
{
name: "nil validator",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator = nil
return validatorsResponse
},
expectedError: "state validator at index `0` is nil",
},
{
name: "invalid pubkey",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.PublicKey = "foo"
return validatorsResponse
},
expectedError: "failed to decode validator pubkey `foo`",
},
{
name: "invalid withdrawal credentials",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.WithdrawalCredentials = "bar"
return validatorsResponse
},
expectedError: "failed to decode validator withdrawal credentials `bar`",
},
{
name: "invalid effective balance",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.EffectiveBalance = "foo"
return validatorsResponse
},
expectedError: "failed to parse validator effective balance `foo`",
},
{
name: "invalid validator index",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Index = "bar"
return validatorsResponse
},
expectedError: "failed to parse validator index `bar`",
},
{
name: "invalid activation eligibility epoch",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.ActivationEligibilityEpoch = "foo"
return validatorsResponse
},
expectedError: "failed to parse validator activation eligibility epoch `foo`",
},
{
name: "invalid activation epoch",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.ActivationEpoch = "bar"
return validatorsResponse
},
expectedError: "failed to parse validator activation epoch `bar`",
},
{
name: "invalid exit epoch",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.ExitEpoch = "foo"
return validatorsResponse
},
expectedError: "failed to parse validator exit epoch `foo`",
},
{
name: "invalid withdrawable epoch",
generateStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validatorsResponse := generateValidStateValidatorsResponse()
validatorsResponse.Data[0].Validator.WithdrawableEpoch = "bar"
return validatorsResponse
},
expectedError: "failed to parse validator withdrawable epoch `bar`",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForSlot(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(
testCase.generateStateValidatorsResponse(),
nil,
)
beaconChainClient := beaconApiBeaconChainClient{stateValidatorsProvider: stateValidatorsProvider}
_, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: &ethpb.ListValidatorsRequest_Genesis{},
})
assert.ErrorContains(t, testCase.expectedError, err)
})
}
})
t.Run("correctly returns the expected validators", func(t *testing.T) {
generateValidStateValidatorsResponse := func() *apimiddleware.StateValidatorsResponseJson {
return &apimiddleware.StateValidatorsResponseJson{
Data: []*apimiddleware.ValidatorContainerJson{
{
Index: "1",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode([]byte{2}),
WithdrawalCredentials: hexutil.Encode([]byte{3}),
EffectiveBalance: "4",
Slashed: true,
ActivationEligibilityEpoch: "5",
ActivationEpoch: "6",
ExitEpoch: "7",
WithdrawableEpoch: "8",
},
},
{
Index: "9",
Validator: &apimiddleware.ValidatorJson{
PublicKey: hexutil.Encode([]byte{10}),
WithdrawalCredentials: hexutil.Encode([]byte{11}),
EffectiveBalance: "12",
Slashed: false,
ActivationEligibilityEpoch: "13",
ActivationEpoch: "14",
ExitEpoch: "15",
WithdrawableEpoch: "16",
},
},
},
}
}
testCases := []struct {
name string
generateJsonStateValidatorsResponse func() *apimiddleware.StateValidatorsResponseJson
generateProtoValidatorsResponse func() *ethpb.Validators
pubkeys [][]byte
pubkeyStrings []string
indices []primitives.ValidatorIndex
statuses []string
pageSize int32
pageToken string
}{
{
name: "page size 0",
generateJsonStateValidatorsResponse: func() *apimiddleware.StateValidatorsResponseJson {
validValidatorsResponse := generateValidStateValidatorsResponse()
// Generate more than 250 validators, but expect only 250 to be returned
validators := make([]*apimiddleware.ValidatorContainerJson, 267)
for idx := 0; idx < len(validators); idx++ {
validators[idx] = validValidatorsResponse.Data[0]
}
validatorsResponse := &apimiddleware.StateValidatorsResponseJson{
Data: validators,
}
return validatorsResponse
},
generateProtoValidatorsResponse: func() *ethpb.Validators {
validators := make([]*ethpb.Validators_ValidatorContainer, 250)
for idx := 0; idx < len(validators); idx++ {
validators[idx] = &ethpb.Validators_ValidatorContainer{
Index: 1,
Validator: &ethpb.Validator{
PublicKey: []byte{2},
WithdrawalCredentials: []byte{3},
EffectiveBalance: 4,
Slashed: true,
ActivationEligibilityEpoch: 5,
ActivationEpoch: 6,
ExitEpoch: 7,
WithdrawableEpoch: 8,
},
}
}
return &ethpb.Validators{
ValidatorList: validators,
TotalSize: 267,
Epoch: 0,
NextPageToken: "1",
}
},
pubkeys: [][]byte{},
pubkeyStrings: make([]string, 0),
indices: []primitives.ValidatorIndex{},
statuses: nil,
pageSize: 0,
pageToken: "",
},
{
name: "pageSize==1 and pageToken==0",
generateJsonStateValidatorsResponse: generateValidStateValidatorsResponse,
generateProtoValidatorsResponse: func() *ethpb.Validators {
return &ethpb.Validators{
ValidatorList: []*ethpb.Validators_ValidatorContainer{
{
Index: 1,
Validator: &ethpb.Validator{
PublicKey: []byte{2},
WithdrawalCredentials: []byte{3},
EffectiveBalance: 4,
Slashed: true,
ActivationEligibilityEpoch: 5,
ActivationEpoch: 6,
ExitEpoch: 7,
WithdrawableEpoch: 8,
},
},
},
TotalSize: 2,
Epoch: 0,
NextPageToken: "1",
}
},
pageSize: 1,
pageToken: "0",
},
{
name: "pageSize==2 and pageToken==0",
generateJsonStateValidatorsResponse: generateValidStateValidatorsResponse,
generateProtoValidatorsResponse: func() *ethpb.Validators {
return &ethpb.Validators{
ValidatorList: []*ethpb.Validators_ValidatorContainer{
{
Index: 1,
Validator: &ethpb.Validator{
PublicKey: []byte{2},
WithdrawalCredentials: []byte{3},
EffectiveBalance: 4,
Slashed: true,
ActivationEligibilityEpoch: 5,
ActivationEpoch: 6,
ExitEpoch: 7,
WithdrawableEpoch: 8,
},
},
{
Index: 9,
Validator: &ethpb.Validator{
PublicKey: []byte{10},
WithdrawalCredentials: []byte{11},
EffectiveBalance: 12,
Slashed: false,
ActivationEligibilityEpoch: 13,
ActivationEpoch: 14,
ExitEpoch: 15,
WithdrawableEpoch: 16,
},
},
},
TotalSize: 2,
Epoch: 0,
NextPageToken: "",
}
},
pageSize: 2,
pageToken: "0",
},
{
name: "pageSize==1 and pageToken==1",
generateJsonStateValidatorsResponse: generateValidStateValidatorsResponse,
generateProtoValidatorsResponse: func() *ethpb.Validators {
return &ethpb.Validators{
ValidatorList: []*ethpb.Validators_ValidatorContainer{
{
Index: 9,
Validator: &ethpb.Validator{
PublicKey: []byte{10},
WithdrawalCredentials: []byte{11},
EffectiveBalance: 12,
Slashed: false,
ActivationEligibilityEpoch: 13,
ActivationEpoch: 14,
ExitEpoch: 15,
WithdrawableEpoch: 16,
},
},
},
TotalSize: 2,
Epoch: 0,
NextPageToken: "",
}
},
pageSize: 1,
pageToken: "1",
},
{
name: "pageSize==1 and pageToken==2",
generateJsonStateValidatorsResponse: generateValidStateValidatorsResponse,
generateProtoValidatorsResponse: func() *ethpb.Validators {
return &ethpb.Validators{
ValidatorList: []*ethpb.Validators_ValidatorContainer{},
TotalSize: 2,
Epoch: 0,
NextPageToken: "",
}
},
pageSize: 1,
pageToken: "2",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
stateValidatorsProvider := mock.NewMockstateValidatorsProvider(ctrl)
stateValidatorsProvider.EXPECT().GetStateValidatorsForSlot(ctx, primitives.Slot(0), make([]string, 0), []primitives.ValidatorIndex{}, nil).Return(
testCase.generateJsonStateValidatorsResponse(),
nil,
)
beaconChainClient := beaconApiBeaconChainClient{stateValidatorsProvider: stateValidatorsProvider}
validators, err := beaconChainClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{
QueryFilter: &ethpb.ListValidatorsRequest_Genesis{},
PublicKeys: [][]byte{},
Indices: []primitives.ValidatorIndex{},
Active: false,
PageSize: testCase.pageSize,
PageToken: testCase.pageToken,
})
require.NoError(t, err)
require.NotNil(t, validators)
expectedValidators := testCase.generateProtoValidatorsResponse()
assert.DeepEqual(t, expectedValidators, validators)
})
}
})
}

View File

@@ -10,6 +10,7 @@ import (
gomock "github.com/golang/mock/gomock"
apimiddleware "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
primitives "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
)
// MockstateValidatorsProvider is a mock of stateValidatorsProvider interface.
@@ -49,3 +50,33 @@ func (mr *MockstateValidatorsProviderMockRecorder) GetStateValidators(arg0, arg1
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateValidators", reflect.TypeOf((*MockstateValidatorsProvider)(nil).GetStateValidators), arg0, arg1, arg2, arg3)
}
// GetStateValidatorsForHead mocks base method.
func (m *MockstateValidatorsProvider) GetStateValidatorsForHead(arg0 context.Context, arg1 []string, arg2 []primitives.ValidatorIndex, arg3 []string) (*apimiddleware.StateValidatorsResponseJson, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStateValidatorsForHead", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*apimiddleware.StateValidatorsResponseJson)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStateValidatorsForHead indicates an expected call of GetStateValidatorsForHead.
func (mr *MockstateValidatorsProviderMockRecorder) GetStateValidatorsForHead(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateValidatorsForHead", reflect.TypeOf((*MockstateValidatorsProvider)(nil).GetStateValidatorsForHead), arg0, arg1, arg2, arg3)
}
// GetStateValidatorsForSlot mocks base method.
func (m *MockstateValidatorsProvider) GetStateValidatorsForSlot(arg0 context.Context, arg1 primitives.Slot, arg2 []string, arg3 []primitives.ValidatorIndex, arg4 []string) (*apimiddleware.StateValidatorsResponseJson, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStateValidatorsForSlot", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*apimiddleware.StateValidatorsResponseJson)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStateValidatorsForSlot indicates an expected call of GetStateValidatorsForSlot.
func (mr *MockstateValidatorsProviderMockRecorder) GetStateValidatorsForSlot(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateValidatorsForSlot", reflect.TypeOf((*MockstateValidatorsProvider)(nil).GetStateValidatorsForSlot), arg0, arg1, arg2, arg3, arg4)
}

View File

@@ -2,15 +2,19 @@ package beacon_api
import (
"context"
"fmt"
neturl "net/url"
"strconv"
"github.com/pkg/errors"
rpcmiddleware "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
)
type stateValidatorsProvider interface {
GetStateValidators(context.Context, []string, []int64, []string) (*rpcmiddleware.StateValidatorsResponseJson, error)
GetStateValidatorsForSlot(context.Context, primitives.Slot, []string, []primitives.ValidatorIndex, []string) (*rpcmiddleware.StateValidatorsResponseJson, error)
GetStateValidatorsForHead(context.Context, []string, []primitives.ValidatorIndex, []string) (*rpcmiddleware.StateValidatorsResponseJson, error)
}
type beaconApiStateValidatorsProvider struct {
@@ -24,9 +28,59 @@ func (c beaconApiStateValidatorsProvider) GetStateValidators(
statuses []string,
) (*rpcmiddleware.StateValidatorsResponseJson, error) {
params := neturl.Values{}
stringPubKeysSet := make(map[string]struct{}, len(stringPubkeys))
indexesSet := make(map[int64]struct{}, len(indexes))
for _, index := range indexes {
if _, ok := indexesSet[index]; !ok {
indexesSet[index] = struct{}{}
params.Add("id", strconv.FormatInt(index, 10))
}
}
return c.getStateValidatorsHelper(ctx, "/eth/v1/beacon/states/head/validators", params, stringPubkeys, statuses)
}
func (c beaconApiStateValidatorsProvider) GetStateValidatorsForSlot(
ctx context.Context,
slot primitives.Slot,
stringPubkeys []string,
indices []primitives.ValidatorIndex,
statuses []string,
) (*rpcmiddleware.StateValidatorsResponseJson, error) {
params := convertValidatorIndicesToParams(indices)
url := fmt.Sprintf("/eth/v1/beacon/states/%d/validators", slot)
return c.getStateValidatorsHelper(ctx, url, params, stringPubkeys, statuses)
}
func (c beaconApiStateValidatorsProvider) GetStateValidatorsForHead(
ctx context.Context,
stringPubkeys []string,
indices []primitives.ValidatorIndex,
statuses []string,
) (*rpcmiddleware.StateValidatorsResponseJson, error) {
params := convertValidatorIndicesToParams(indices)
return c.getStateValidatorsHelper(ctx, "/eth/v1/beacon/states/head/validators", params, stringPubkeys, statuses)
}
func convertValidatorIndicesToParams(indices []primitives.ValidatorIndex) neturl.Values {
params := neturl.Values{}
indicesSet := make(map[primitives.ValidatorIndex]struct{}, len(indices))
for _, index := range indices {
if _, ok := indicesSet[index]; !ok {
indicesSet[index] = struct{}{}
params.Add("id", strconv.FormatUint(uint64(index), 10))
}
}
return params
}
func (c beaconApiStateValidatorsProvider) getStateValidatorsHelper(
ctx context.Context,
endpoint string,
params neturl.Values,
stringPubkeys []string,
statuses []string,
) (*rpcmiddleware.StateValidatorsResponseJson, error) {
stringPubKeysSet := make(map[string]struct{}, len(stringPubkeys))
for _, stringPubkey := range stringPubkeys {
if _, ok := stringPubKeysSet[stringPubkey]; !ok {
@@ -35,22 +89,11 @@ func (c beaconApiStateValidatorsProvider) GetStateValidators(
}
}
for _, index := range indexes {
if _, ok := indexesSet[index]; !ok {
indexesSet[index] = struct{}{}
params.Add("id", strconv.FormatInt(index, 10))
}
}
for _, status := range statuses {
params.Add("status", status)
}
url := buildURL(
"/eth/v1/beacon/states/head/validators",
params,
)
url := buildURL(endpoint, params)
stateValidatorsJson := &rpcmiddleware.StateValidatorsResponseJson{}
if _, err := c.jsonRestHandler.GetRestJsonResponse(ctx, url, stateValidatorsJson); err != nil {

View File

@@ -19,11 +19,11 @@ func TestGetStateValidators_Nominal(t *testing.T) {
url := strings.Join([]string{
"/eth/v1/beacon/states/head/validators?",
"id=12345&",
"id=0x8000091c2ae64ee414a54c1cc1fc67dec663408bc636cb86756e0200e41a75c8f86603f104f02c856983d2783116be13&", // active_ongoing
"id=0x80000e851c0f53c3246ff726d7ff7766661ca5e12a07c45c114d208d54f0f8233d4380b2e9aff759d69795d1df905526&", // active_exiting
"id=0x424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242&", // does not exist
"id=0x800015473bdc3a7f45ef8eb8abc598bc20021e55ad6e6ad1d745aaef9730dd2c28ec08bf42df18451de94dd4a6d24ec5&", // exited_slashed
"id=12345&", // active_ongoing
"status=active_ongoing&status=active_exiting&status=exited_slashed&status=exited_unslashed",
}, "")

View File

@@ -218,6 +218,8 @@ func TestKeyReload_ActiveKey(t *testing.T) {
}
func TestKeyReload_NoActiveKey(t *testing.T) {
t.Skip("Flakey test. Skipping until we can figure out how to test this properly")
ctx, cancel := context.WithCancel(context.Background())
km := &mockKeymanager{}
v := &testutil.FakeValidator{Km: km}

View File

@@ -251,6 +251,8 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
// Regression test for a scenario where you start with an inactive key and then import an active key.
func TestWaitForActivation_AccountsChanged(t *testing.T) {
t.Skip("Flakey test. Skipping until we can figure out how to test this properly")
hook := logTest.NewGlobal()
ctrl := gomock.NewController(t)
defer ctrl.Finish()