diff --git a/beacon-chain/rpc/validator_server.go b/beacon-chain/rpc/validator_server.go index 0cf3c89d3..201fe93dc 100644 --- a/beacon-chain/rpc/validator_server.go +++ b/beacon-chain/rpc/validator_server.go @@ -7,9 +7,11 @@ import ( "time" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/beacon-chain/core/state" "github.com/prysmaticlabs/prysm/beacon-chain/db" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" + "github.com/prysmaticlabs/prysm/shared/hashutil" "github.com/prysmaticlabs/prysm/shared/params" ) @@ -131,6 +133,22 @@ func (vs *ValidatorServer) CommitteeAssignment( if err != nil { return nil, fmt.Errorf("could not get active validator index: %v", err) } + chainHead, err := vs.beaconDB.ChainHead() + if err != nil { + return nil, fmt.Errorf("could not get chain head: %v", err) + } + headRoot, err := hashutil.HashBeaconBlock(chainHead) + if err != nil { + return nil, fmt.Errorf("could not hash block: %v", err) + } + for beaconState.Slot < req.EpochStart { + beaconState, err = state.ExecuteStateTransition( + ctx, beaconState, nil /* block */, headRoot, state.DefaultConfig(), + ) + if err != nil { + return nil, fmt.Errorf("could not execute head transition: %v", err) + } + } committee, shard, slot, isProposer, err := helpers.CommitteeAssignment(beaconState, req.EpochStart, uint64(idx), false) diff --git a/validator/client/fake_validator_test.go b/validator/client/fake_validator_test.go index 5993b0492..c0015d524 100644 --- a/validator/client/fake_validator_test.go +++ b/validator/client/fake_validator_test.go @@ -5,6 +5,7 @@ import ( "time" pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" + "github.com/prysmaticlabs/prysm/shared/params" ) var _ = Validator(&fakeValidator{}) @@ -15,6 +16,7 @@ type fakeValidator struct { WaitForChainStartCalled bool NextSlotRet <-chan uint64 NextSlotCalled bool + CanonicalHeadSlotCalled bool UpdateAssignmentsCalled bool UpdateAssignmentsArg1 uint64 UpdateAssignmentsRet error @@ -43,6 +45,11 @@ func (fv *fakeValidator) WaitForActivation(_ context.Context) error { return nil } +func (fv *fakeValidator) CanonicalHeadSlot(_ context.Context) (uint64, error) { + fv.CanonicalHeadSlotCalled = true + return params.BeaconConfig().GenesisSlot, nil +} + func (fv *fakeValidator) SlotDeadline(_ uint64) time.Time { fv.SlotDeadlineCalled = true return time.Now() diff --git a/validator/client/runner.go b/validator/client/runner.go index 0714bffb4..0657bacf0 100644 --- a/validator/client/runner.go +++ b/validator/client/runner.go @@ -17,6 +17,7 @@ type Validator interface { Done() WaitForChainStart(ctx context.Context) error WaitForActivation(ctx context.Context) error + CanonicalHeadSlot(ctx context.Context) (uint64, error) NextSlot() <-chan uint64 SlotDeadline(slot uint64) time.Time LogValidatorGainsAndLosses(ctx context.Context, slot uint64) error @@ -44,8 +45,12 @@ func run(ctx context.Context, v Validator) { if err := v.WaitForActivation(ctx); err != nil { log.Fatalf("Could not wait for validator activation: %v", err) } - if err := v.UpdateAssignments(ctx, params.BeaconConfig().GenesisSlot); err != nil { - handleAssignmentError(err, params.BeaconConfig().GenesisSlot) + headSlot, err := v.CanonicalHeadSlot(ctx) + if err != nil { + log.Fatalf("Could not get current canonical head slot: %v", err) + } + if err := v.UpdateAssignments(ctx, headSlot); err != nil { + handleAssignmentError(err, headSlot) } for { ctx, span := trace.StartSpan(ctx, "processSlot") diff --git a/validator/client/validator.go b/validator/client/validator.go index b962940dc..9a26bceae 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -107,6 +107,18 @@ func (v *validator) WaitForActivation(ctx context.Context) error { return nil } +// CanonicalHeadSlot returns the slot of canonical block currently found in the +// beacon chain via RPC. +func (v *validator) CanonicalHeadSlot(ctx context.Context) (uint64, error) { + ctx, span := trace.StartSpan(ctx, "validator.CanonicalHeadSlot") + defer span.End() + head, err := v.beaconClient.CanonicalHead(ctx, &ptypes.Empty{}) + if err != nil { + return params.BeaconConfig().GenesisSlot, err + } + return head.Slot, nil +} + // NextSlot emits the next slot number at the start time of that slot. func (v *validator) NextSlot() <-chan uint64 { return v.ticker.C() diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 3e742e0e2..af3b83fb5 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -250,6 +250,44 @@ func TestWaitActivation_LogsActivationEpochOK(t *testing.T) { testutil.AssertLogsContain(t, hook, "Validator activated") } +func TestCanonicalHeadSlot_FailedRPC(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := internal.NewMockBeaconServiceClient(ctrl) + v := validator{ + key: validatorKey, + beaconClient: client, + } + client.EXPECT().CanonicalHead( + gomock.Any(), + gomock.Any(), + ).Return(nil, errors.New("failed")) + if _, err := v.CanonicalHeadSlot(context.Background()); !strings.Contains(err.Error(), "failed") { + t.Errorf("Wanted: %v, received: %v", "failed", err) + } +} + +func TestCanonicalHeadSlot_OK(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := internal.NewMockBeaconServiceClient(ctrl) + v := validator{ + key: validatorKey, + beaconClient: client, + } + client.EXPECT().CanonicalHead( + gomock.Any(), + gomock.Any(), + ).Return(&pbp2p.BeaconBlock{Slot: params.BeaconConfig().GenesisSlot}, nil) + headSlot, err := v.CanonicalHeadSlot(context.Background()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if headSlot != params.BeaconConfig().GenesisSlot { + t.Errorf("Mismatch slots, wanted: %v, received: %v", params.BeaconConfig().GenesisSlot, headSlot) + } +} + func TestUpdateAssignments_ReturnsError(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()