diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 255f14c0a..931d73901 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -21,6 +21,7 @@ go_library( "//shared/keystore:go_default_library", "//shared/params:go_default_library", "//shared/slotutil:go_default_library", + "@com_github_gogo_protobuf//proto:go_default_library", "@com_github_gogo_protobuf//types:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@io_opencensus_go//plugin/ocgrpc:go_default_library", diff --git a/validator/client/validator.go b/validator/client/validator.go index 9a26bceae..797432a18 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -135,6 +135,7 @@ func (v *validator) SlotDeadline(slot uint64) time.Time { // beginning of a new epoch. func (v *validator) UpdateAssignments(ctx context.Context, slot uint64) error { // Testing run time for fetching every slot. This is not meant for production! + // https://github.com/prysmaticlabs/prysm/issues/2167 if slot%params.BeaconConfig().SlotsPerEpoch != 0 && v.assignment != nil && false { // Do nothing if not epoch start AND assignments already exist. return nil diff --git a/validator/client/validator_propose.go b/validator/client/validator_propose.go index b5bc44b90..8f0b6ac41 100644 --- a/validator/client/validator_propose.go +++ b/validator/client/validator_propose.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" + "github.com/gogo/protobuf/proto" ptypes "github.com/gogo/protobuf/types" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" @@ -106,7 +107,14 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64) { } // 3. Compute state root transition from parent block to the new block. - block.StateRootHash32 = []byte("root") + resp, err := v.proposerClient.ComputeStateRoot(ctx, block) + if err != nil { + log.WithField( + "block", proto.MarshalTextString(block), + ).Errorf("Not proposing! Unable to compute state root: %v", err) + return + } + block.StateRootHash32 = resp.GetStateRoot() // 4. Sign the complete block. // TODO(1366): BLS sign block diff --git a/validator/client/validator_propose_test.go b/validator/client/validator_propose_test.go index 69ad6628c..5e58c2184 100644 --- a/validator/client/validator_propose_test.go +++ b/validator/client/validator_propose_test.go @@ -124,6 +124,13 @@ func TestProposeBlock_UsePendingDeposits(t *testing.T) { gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}), ).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil) + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return(&pb.StateRootResponse{ + StateRoot: []byte{'F'}, + }, nil /*err*/) + var broadcastedBlock *pbp2p.BeaconBlock m.proposerClient.EXPECT().ProposeBlock( gomock.Any(), // context @@ -199,6 +206,13 @@ func TestProposeBlock_UsesEth1Data(t *testing.T) { gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}), ).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil) + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return(&pb.StateRootResponse{ + StateRoot: []byte{'F'}, + }, nil /*err*/) + var broadcastedBlock *pbp2p.BeaconBlock m.proposerClient.EXPECT().ProposeBlock( gomock.Any(), // ctx @@ -253,6 +267,13 @@ func TestProposeBlock_PendingAttestations_UsesCurrentSlot(t *testing.T) { return &pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil }) + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return(&pb.StateRootResponse{ + StateRoot: []byte{'F'}, + }, nil /*err*/) + m.proposerClient.EXPECT().ProposeBlock( gomock.Any(), // context gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), @@ -308,6 +329,108 @@ func TestProposeBlock_PendingAttestationsFailure(t *testing.T) { testutil.AssertLogsContain(t, hook, "Failed to fetch pending attestations") } +func TestProposeBlock_ComputeStateFailure(t *testing.T) { + hook := logTest.NewGlobal() + validator, m, finish := setup(t) + defer finish() + + m.beaconClient.EXPECT().CanonicalHead( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pbp2p.BeaconBlock{}, nil /*err*/) + + m.beaconClient.EXPECT().PendingDeposits( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pb.PendingDepositsResponse{}, nil /*err*/) + + m.beaconClient.EXPECT().Eth1Data( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pb.Eth1DataResponse{}, nil /*err*/) + + m.beaconClient.EXPECT().ForkData( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pbp2p.Fork{ + Epoch: params.BeaconConfig().GenesisEpoch, + CurrentVersion: 0, + PreviousVersion: 0, + }, nil /*err*/) + + m.proposerClient.EXPECT().PendingAttestations( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}), + ).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil) + + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return(nil /*response*/, errors.New("something bad happened")) + + validator.ProposeBlock(context.Background(), 55) + testutil.AssertLogsContain(t, hook, "something bad happened") +} + +func TestProposeBlock_UsesComputedState(t *testing.T) { + validator, m, finish := setup(t) + defer finish() + + m.beaconClient.EXPECT().CanonicalHead( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pbp2p.BeaconBlock{}, nil /*err*/) + + m.beaconClient.EXPECT().PendingDeposits( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pb.PendingDepositsResponse{}, nil /*err*/) + + m.beaconClient.EXPECT().Eth1Data( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pb.Eth1DataResponse{}, nil /*err*/) + + m.beaconClient.EXPECT().ForkData( + gomock.Any(), // ctx + gomock.Eq(&ptypes.Empty{}), + ).Return(&pbp2p.Fork{ + Epoch: params.BeaconConfig().GenesisEpoch, + CurrentVersion: 0, + PreviousVersion: 0, + }, nil /*err*/) + + m.proposerClient.EXPECT().PendingAttestations( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}), + ).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil) + + var broadcastedBlock *pbp2p.BeaconBlock + m.proposerClient.EXPECT().ProposeBlock( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Do(func(_ context.Context, blk *pbp2p.BeaconBlock) { + broadcastedBlock = blk + }).Return(&pb.ProposeResponse{}, nil /*error*/) + + computedStateRoot := []byte{'T', 'E', 'S', 'T'} + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return( + &pb.StateRootResponse{ + StateRoot: computedStateRoot, + }, + nil, // err + ) + + validator.ProposeBlock(context.Background(), 55) + + if !bytes.Equal(broadcastedBlock.StateRootHash32, computedStateRoot) { + t.Errorf("Unexpected state root hash. want=%#x got=%#x", computedStateRoot, broadcastedBlock.StateRootHash32) + } +} + func TestProposeBlock_BroadcastsABlock(t *testing.T) { validator, m, finish := setup(t) defer finish() @@ -341,6 +464,13 @@ func TestProposeBlock_BroadcastsABlock(t *testing.T) { gomock.AssignableToTypeOf(&pb.PendingAttestationsRequest{}), ).Return(&pb.PendingAttestationsResponse{PendingAttestations: []*pbp2p.Attestation{}}, nil) + m.proposerClient.EXPECT().ComputeStateRoot( + gomock.Any(), // context + gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), + ).Return(&pb.StateRootResponse{ + StateRoot: []byte{'F'}, + }, nil /*err*/) + m.proposerClient.EXPECT().ProposeBlock( gomock.Any(), // ctx gomock.AssignableToTypeOf(&pbp2p.BeaconBlock{}), diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index af3b83fb5..24b7eda17 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -288,6 +288,37 @@ func TestCanonicalHeadSlot_OK(t *testing.T) { } } +func TestUpdateAssignments_DoesNothingWhenNotEpochStartAndAlreadyExistingAssignments(t *testing.T) { + // TODO(2167): Unskip this test. + t.Skip() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := internal.NewMockValidatorServiceClient(ctrl) + + slot := uint64(1) + v := validator{ + key: validatorKey, + validatorClient: client, + assignment: &pb.CommitteeAssignmentResponse{ + Assignment: []*pb.CommitteeAssignmentResponse_CommitteeAssignment{ + { + Committee: []uint64{}, + Slot: 10, + Shard: 20, + }, + }, + }, + } + client.EXPECT().CommitteeAssignment( + gomock.Any(), + gomock.Any(), + ).Times(0) + + if err := v.UpdateAssignments(context.Background(), slot); err != nil { + t.Errorf("Could not update assignments: %v", err) + } +} + func TestUpdateAssignments_ReturnsError(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()