mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 16:08:26 -05:00
Validator RPC: add optimistic status check (#10291)
* Add optimistic status check * Simplify a bit more * Update status_test.go * Add non opt tests * Update aggregator_test.go * More tests * Preston's feedback
This commit is contained in:
@@ -154,6 +154,8 @@ go_test(
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
|
||||
@@ -28,6 +28,11 @@ func (vs *Server) SubmitAggregateSelectionProof(ctx context.Context, req *ethpb.
|
||||
return nil, status.Errorf(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
||||
}
|
||||
|
||||
// An optimistic validator MUST NOT participate in attestation. (i.e., sign across the DOMAIN_BEACON_ATTESTER, DOMAIN_SELECTION_PROOF or DOMAIN_AGGREGATE_AND_PROOF domains).
|
||||
if err := vs.optimisticStatus(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st, err := vs.HeadFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine head state: %v", err)
|
||||
|
||||
@@ -55,6 +55,7 @@ func TestSubmitAggregateAndProof_CantFindValidatorIndex(t *testing.T) {
|
||||
server := &Server{
|
||||
HeadFetcher: &mock.ChainService{State: s},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -82,6 +83,7 @@ func TestSubmitAggregateAndProof_IsAggregatorAndNoAtts(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{State: s},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -115,6 +117,7 @@ func TestSubmitAggregateAndProof_UnaggregateOk(t *testing.T) {
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
P2P: &mockp2p.MockBroadcaster{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -152,6 +155,7 @@ func TestSubmitAggregateAndProof_AggregateOk(t *testing.T) {
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
P2P: &mockp2p.MockBroadcaster{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -191,6 +195,7 @@ func TestSubmitAggregateAndProof_AggregateNotOk(t *testing.T) {
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
P2P: &mockp2p.MockBroadcaster{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -319,6 +324,7 @@ func TestSubmitAggregateAndProof_PreferOwnAttestation(t *testing.T) {
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
P2P: &mockp2p.MockBroadcaster{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
@@ -369,6 +375,7 @@ func TestSubmitAggregateAndProof_SelectsMostBitsWhenOwnAttestationNotPresent(t *
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
AttPool: attestations.NewPool(),
|
||||
P2P: &mockp2p.MockBroadcaster{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
|
||||
@@ -37,6 +37,11 @@ func (vs *Server) GetAttestationData(ctx context.Context, req *ethpb.Attestation
|
||||
return nil, status.Errorf(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
||||
}
|
||||
|
||||
// An optimistic validator MUST NOT participate in attestation. (i.e., sign across the DOMAIN_BEACON_ATTESTER, DOMAIN_SELECTION_PROOF or DOMAIN_AGGREGATE_AND_PROOF domains).
|
||||
if err := vs.optimisticStatus(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := helpers.ValidateAttestationTime(req.Slot, vs.TimeFetcher.GenesisTime(),
|
||||
params.BeaconNetworkConfig().MaximumGossipClockDisparity); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid request: %v", err))
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/time"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -169,6 +171,35 @@ func TestGetAttestationData_SyncNotReady(t *testing.T) {
|
||||
assert.ErrorContains(t, "Syncing to latest head", err)
|
||||
}
|
||||
|
||||
func TestGetAttestationData_Optimistic(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.BellatrixForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
as := &Server{
|
||||
SyncChecker: &mockSync.Sync{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
HeadFetcher: &mock.ChainService{Optimistic: true},
|
||||
}
|
||||
_, err := as.GetAttestationData(context.Background(), ðpb.AttestationDataRequest{})
|
||||
s, ok := status.FromError(err)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, codes.Unavailable, s.Code())
|
||||
require.ErrorContains(t, errOptimisticMode.Error(), err)
|
||||
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
as = &Server{
|
||||
SyncChecker: &mockSync.Sync{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
HeadFetcher: &mock.ChainService{Optimistic: false, State: beaconState},
|
||||
AttestationCache: cache.NewAttestationCache(),
|
||||
}
|
||||
_, err = as.GetAttestationData(context.Background(), ðpb.AttestationDataRequest{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAttestationDataAtSlot_HandlesFarAwayJustifiedEpoch(t *testing.T) {
|
||||
// Scenario:
|
||||
//
|
||||
@@ -317,6 +348,7 @@ func TestServer_GetAttestationData_InvalidRequestSlot(t *testing.T) {
|
||||
offset := int64(slot.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
attesterServer := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
HeadFetcher: &mock.ChainService{},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second)},
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,11 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
return ðpb.GenericBeaconBlock{Block: ðpb.GenericBeaconBlock_Altair{Altair: blk}}, nil
|
||||
}
|
||||
|
||||
// An optimistic validator MUST NOT produce a block (i.e., sign across the DOMAIN_BEACON_PROPOSER domain).
|
||||
if err := vs.optimisticStatus(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blk, err := vs.getBellatrixBeaconBlock(ctx, req)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not fetch Bellatrix beacon block: %v", err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
coretime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
|
||||
dbutil "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings"
|
||||
@@ -39,6 +40,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -320,7 +323,7 @@ func TestProposer_ComputeStateRoot_OK(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(beaconState.Slot()-1))
|
||||
req.Block.Body.RandaoReveal = randaoReveal
|
||||
currentEpoch := time.CurrentEpoch(beaconState)
|
||||
currentEpoch := coretime.CurrentEpoch(beaconState)
|
||||
req.Signature, err = signing.ComputeDomainAndSign(beaconState, currentEpoch, req.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[proposerIdx])
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2284,7 +2287,8 @@ func TestProposer_GetBeaconBlock_BellatrixEpoch(t *testing.T) {
|
||||
require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state")
|
||||
|
||||
proposerServer := &Server{
|
||||
HeadFetcher: &mock.ChainService{State: beaconState, Root: parentRoot[:]},
|
||||
HeadFetcher: &mock.ChainService{State: beaconState, Root: parentRoot[:], Optimistic: false},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
BlockReceiver: &mock.ChainService{},
|
||||
ChainStartFetcher: &mockPOW.POWChain{},
|
||||
@@ -2320,6 +2324,27 @@ func TestProposer_GetBeaconBlock_BellatrixEpoch(t *testing.T) {
|
||||
assert.DeepEqual(t, req.Graffiti, bellatrixBlk.Bellatrix.Body.Graffiti, "Expected block to have correct Graffiti")
|
||||
}
|
||||
|
||||
func TestProposer_GetBeaconBlock_Optimistic(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.MainnetConfig().Copy()
|
||||
cfg.BellatrixForkEpoch = 2
|
||||
cfg.AltairForkEpoch = 1
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
bellatrixSlot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
proposerServer := &Server{HeadFetcher: &mock.ChainService{Optimistic: true}, TimeFetcher: &mock.ChainService{}}
|
||||
req := ðpb.BlockRequest{
|
||||
Slot: bellatrixSlot + 1,
|
||||
}
|
||||
_, err = proposerServer.GetBeaconBlock(context.Background(), req)
|
||||
s, ok := status.FromError(err)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, codes.Unavailable, s.Code())
|
||||
require.ErrorContains(t, errOptimisticMode.Error(), err)
|
||||
}
|
||||
|
||||
func TestProposer_GetSyncAggregate_OK(t *testing.T) {
|
||||
proposerServer := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
)
|
||||
|
||||
var errPubkeyDoesNotExist = errors.New("pubkey does not exist")
|
||||
var errOptimisticMode = errors.New("the node is currently optimistic and cannot serve validators")
|
||||
var nonExistentIndex = types.ValidatorIndex(^uint64(0))
|
||||
|
||||
const numStatesToCheck = 2
|
||||
@@ -237,6 +238,29 @@ func (vs *Server) activationStatus(
|
||||
return activeValidatorExists, statusResponses, nil
|
||||
}
|
||||
|
||||
// optimisticStatus returns an error if the node is currently optimistic with respect to head.
|
||||
// by definition, an optimistic node is not a full node. It is unable to produce blocks,
|
||||
// since an execution engine cannot produce a payload upon an unknown parent.
|
||||
// It cannot faithfully attest to the head block of the chain, since it has not fully verified that block.
|
||||
//
|
||||
// Spec:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md
|
||||
func (vs *Server) optimisticStatus(ctx context.Context) error {
|
||||
if slots.ToEpoch(vs.TimeFetcher.CurrentSlot()) < params.BeaconConfig().BellatrixForkEpoch {
|
||||
return nil
|
||||
}
|
||||
optimistic, err := vs.HeadFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not determine if the node is a optimistic node: %v", err)
|
||||
}
|
||||
if !optimistic {
|
||||
return nil
|
||||
}
|
||||
|
||||
return status.Errorf(codes.Unavailable, errOptimisticMode.Error())
|
||||
|
||||
}
|
||||
|
||||
// validatorStatus searches for the requested validator's state and deposit to retrieve its inclusion estimate. Also returns the validators index.
|
||||
func (vs *Server) validatorStatus(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/time/slots"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -582,6 +584,28 @@ func TestActivationStatus_OK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptimisticStatus(t *testing.T) {
|
||||
server := &Server{HeadFetcher: &mockChain.ChainService{}, TimeFetcher: &mockChain.ChainService{}}
|
||||
err := server.optimisticStatus(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.BellatrixForkEpoch = 2
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
server = &Server{HeadFetcher: &mockChain.ChainService{Optimistic: true}, TimeFetcher: &mockChain.ChainService{}}
|
||||
err = server.optimisticStatus(context.Background())
|
||||
s, ok := status.FromError(err)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, codes.Unavailable, s.Code())
|
||||
require.ErrorContains(t, errOptimisticMode.Error(), err)
|
||||
|
||||
server = &Server{HeadFetcher: &mockChain.ChainService{Optimistic: false}, TimeFetcher: &mockChain.ChainService{}}
|
||||
err = server.optimisticStatus(context.Background())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidatorStatus_CorrectActivationQueue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ import (
|
||||
func (vs *Server) GetSyncMessageBlockRoot(
|
||||
ctx context.Context, _ *emptypb.Empty,
|
||||
) (*ethpb.SyncMessageBlockRootResponse, error) {
|
||||
// An optimistic validator MUST NOT participate in sync committees
|
||||
// (i.e., sign across the DOMAIN_SYNC_COMMITTEE, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF or DOMAIN_CONTRIBUTION_AND_PROOF domains).
|
||||
if err := vs.optimisticStatus(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := vs.HeadFetcher.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve head root: %v", err)
|
||||
@@ -81,6 +87,12 @@ func (vs *Server) GetSyncSubcommitteeIndex(
|
||||
func (vs *Server) GetSyncCommitteeContribution(
|
||||
ctx context.Context, req *ethpb.SyncCommitteeContributionRequest,
|
||||
) (*ethpb.SyncCommitteeContribution, error) {
|
||||
// An optimistic validator MUST NOT participate in sync committees
|
||||
// (i.e., sign across the DOMAIN_SYNC_COMMITTEE, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF or DOMAIN_CONTRIBUTION_AND_PROOF domains).
|
||||
if err := vs.optimisticStatus(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgs, err := vs.SyncCommitteePool.SyncCommitteeMessages(req.Slot)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get sync subcommittee messages: %v", err)
|
||||
|
||||
@@ -3,6 +3,7 @@ package validator
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
||||
@@ -18,6 +19,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/testing/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
@@ -25,12 +28,37 @@ func TestGetSyncMessageBlockRoot_OK(t *testing.T) {
|
||||
r := []byte{'a'}
|
||||
server := &Server{
|
||||
HeadFetcher: &mock.ChainService{Root: r},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
res, err := server.GetSyncMessageBlockRoot(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, r, res.Root)
|
||||
}
|
||||
|
||||
func TestGetSyncMessageBlockRoot_Optimistic(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.BellatrixForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
server := &Server{
|
||||
HeadFetcher: &mock.ChainService{Optimistic: true},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
_, err := server.GetSyncMessageBlockRoot(context.Background(), &emptypb.Empty{})
|
||||
s, ok := status.FromError(err)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, codes.Unavailable, s.Code())
|
||||
require.ErrorContains(t, errOptimisticMode.Error(), err)
|
||||
|
||||
server = &Server{
|
||||
HeadFetcher: &mock.ChainService{Optimistic: false},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
_, err = server.GetSyncMessageBlockRoot(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSubmitSyncMessage_OK(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, 10)
|
||||
server := &Server{
|
||||
@@ -80,6 +108,7 @@ func TestGetSyncCommitteeContribution_FiltersDuplicates(t *testing.T) {
|
||||
State: st,
|
||||
SyncCommitteeIndices: []types.CommitteeIndex{10},
|
||||
},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now()},
|
||||
}
|
||||
secKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user