diff --git a/validator/client/validator_attest.go b/validator/client/validator_attest.go index 7d49b8c0d..fcb7bf1f9 100644 --- a/validator/client/validator_attest.go +++ b/validator/client/validator_attest.go @@ -2,17 +2,19 @@ package client import ( "context" - - "github.com/opentracing/opentracing-go" - "fmt" + "time" "github.com/prysmaticlabs/prysm/shared/params" pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1" + + "github.com/opentracing/opentracing-go" ) +var delay = params.BeaconConfig().SlotDuration / 2 + // AttestToBlockHead completes the validator client's attester responsibility at a given slot. // It fetches the latest beacon block head along with the latest canonical beacon state // information in order to sign the block and include information about the validator's @@ -107,6 +109,10 @@ func (v *validator) AttestToBlockHead(ctx context.Context, slot uint64) { // TODO(#1366): Use BLS to generate an aggregate signature. attestation.AggregateSignature = []byte("signed") + duration := time.Duration(slot*params.BeaconConfig().SlotDuration+delay) * time.Second + timeToBroadcast := time.Unix(int64(v.genesisTime), 0).Add(duration) + time.Sleep(time.Until(timeToBroadcast)) + attestRes, err := v.attesterClient.AttestHead(ctx, attestation) if err != nil { log.Errorf("Could not submit attestation to beacon node: %v", err) @@ -114,5 +120,5 @@ func (v *validator) AttestToBlockHead(ctx context.Context, slot uint64) { } log.WithField( "hash", fmt.Sprintf("%#x", attestRes.AttestationHash), - ).Info("Submitted attestation successfully with hash %#x", attestRes.AttestationHash) + ).Infof("Submitted attestation successfully with hash %#x", attestRes.AttestationHash) } diff --git a/validator/client/validator_attest_test.go b/validator/client/validator_attest_test.go index 1d84d19f6..a57489daa 100644 --- a/validator/client/validator_attest_test.go +++ b/validator/client/validator_attest_test.go @@ -3,7 +3,9 @@ package client import ( "context" "errors" + "sync" "testing" + "time" "github.com/prysmaticlabs/prysm/shared/params" @@ -173,3 +175,109 @@ func TestAttestToBlockHead_AttestsCorrectly(t *testing.T) { } testutil.AssertLogsContain(t, hook, "Submitted attestation successfully") } + +func TestAttestToBlockHead_DoesNotAttestBeforeDelay(t *testing.T) { + validator, m, finish := setup(t) + defer finish() + + var wg sync.WaitGroup + wg.Add(3) + defer wg.Wait() + + validator.genesisTime = uint64(time.Now().Unix()) + validatorIndex := uint64(5) + committee := []uint64{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10} + m.validatorClient.EXPECT().ValidatorCommitteeAtSlot( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.CommitteeRequest{}), + gomock.Any(), // ctx + ).Return(&pb.CommitteeResponse{ + Shard: 5, + Committee: committee, + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.attesterClient.EXPECT().AttestationInfoAtSlot( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.AttestationInfoRequest{}), + ).Return(&pb.AttestationInfoResponse{ + BeaconBlockRootHash32: []byte("A"), + EpochBoundaryRootHash32: []byte("B"), + JustifiedBlockRootHash32: []byte("C"), + LatestCrosslink: &pbp2p.Crosslink{ShardBlockRootHash32: []byte{'D'}}, + JustifiedEpoch: 3, + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.validatorClient.EXPECT().ValidatorIndex( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.ValidatorIndexRequest{}), + ).Return(&pb.ValidatorIndexResponse{ + Index: uint64(validatorIndex), + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.attesterClient.EXPECT().AttestHead( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pbp2p.Attestation{}), + ).Return(&pb.AttestResponse{}, nil /* error */).Times(0) + + delay = 2 + go validator.AttestToBlockHead(context.Background(), 0) +} + +func TestAttestToBlockHead_DoesAttestAfterDelay(t *testing.T) { + validator, m, finish := setup(t) + defer finish() + + var wg sync.WaitGroup + wg.Add(3) + defer wg.Wait() + + validator.genesisTime = uint64(time.Now().Unix()) + validatorIndex := uint64(5) + committee := []uint64{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10} + m.validatorClient.EXPECT().ValidatorCommitteeAtSlot( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.CommitteeRequest{}), + gomock.Any(), // ctx + ).Return(&pb.CommitteeResponse{ + Shard: 5, + Committee: committee, + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.attesterClient.EXPECT().AttestationInfoAtSlot( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.AttestationInfoRequest{}), + ).Return(&pb.AttestationInfoResponse{ + BeaconBlockRootHash32: []byte("A"), + EpochBoundaryRootHash32: []byte("B"), + JustifiedBlockRootHash32: []byte("C"), + LatestCrosslink: &pbp2p.Crosslink{ShardBlockRootHash32: []byte{'D'}}, + JustifiedEpoch: 3, + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.validatorClient.EXPECT().ValidatorIndex( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pb.ValidatorIndexRequest{}), + ).Return(&pb.ValidatorIndexResponse{ + Index: uint64(validatorIndex), + }, nil).Do(func(arg0, arg1 interface{}) { + wg.Done() + }) + + m.attesterClient.EXPECT().AttestHead( + gomock.Any(), // ctx + gomock.AssignableToTypeOf(&pbp2p.Attestation{}), + ).Return(&pb.AttestResponse{}, nil).Times(1) + + delay = 0 + go validator.AttestToBlockHead(context.Background(), 0) +}