diff --git a/beacon-chain/archiver/service.go b/beacon-chain/archiver/service.go index 8761e1a7e0..4811c33f27 100644 --- a/beacon-chain/archiver/service.go +++ b/beacon-chain/archiver/service.go @@ -115,7 +115,7 @@ func (s *Service) archiveActiveSetChanges(ctx context.Context, headState *pb.Bea // We compute participation metrics by first retrieving the head state and // matching validator attestations during the epoch. func (s *Service) archiveParticipation(ctx context.Context, headState *pb.BeaconState) error { - participation, err := epoch.ComputeValidatorParticipation(headState) + participation, err := epoch.ComputeValidatorParticipation(headState, helpers.SlotToEpoch(headState.Slot)) if err != nil { return errors.Wrap(err, "could not compute participation") } diff --git a/beacon-chain/core/epoch/BUILD.bazel b/beacon-chain/core/epoch/BUILD.bazel index bb290cd121..6a20e57a05 100644 --- a/beacon-chain/core/epoch/BUILD.bazel +++ b/beacon-chain/core/epoch/BUILD.bazel @@ -32,6 +32,7 @@ go_test( "//beacon-chain/core/helpers:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//proto/eth/v1alpha1:go_default_library", + "//shared/bytesutil:go_default_library", "//shared/params:go_default_library", "@com_github_gogo_protobuf//proto:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", diff --git a/beacon-chain/core/epoch/participation.go b/beacon-chain/core/epoch/participation.go index 94de568b3c..6ad10b12bb 100644 --- a/beacon-chain/core/epoch/participation.go +++ b/beacon-chain/core/epoch/participation.go @@ -1,17 +1,28 @@ package epoch import ( + "fmt" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" ) -// ComputeValidatorParticipation by matching validator attestations during the epoch, -// computing the attesting balance, and how much attested compared to the total balances. -func ComputeValidatorParticipation(state *pb.BeaconState) (*ethpb.ValidatorParticipation, error) { - currentEpoch := helpers.SlotToEpoch(state.Slot) - atts, err := MatchAttestations(state, currentEpoch) +// ComputeValidatorParticipation by matching validator attestations from the previous epoch, +// computing the attesting balance, and how much attested compared to the total balance. +func ComputeValidatorParticipation(state *pb.BeaconState, epoch uint64) (*ethpb.ValidatorParticipation, error) { + currentEpoch := helpers.CurrentEpoch(state) + previousEpoch := helpers.PrevEpoch(state) + if epoch != currentEpoch && epoch != previousEpoch { + return nil, fmt.Errorf( + "requested epoch is not previous epoch %d or current epoch %d, requested %d", + previousEpoch, + currentEpoch, + epoch, + ) + } + atts, err := MatchAttestations(state, epoch) if err != nil { return nil, errors.Wrap(err, "could not retrieve head attestations") } diff --git a/beacon-chain/core/epoch/participation_test.go b/beacon-chain/core/epoch/participation_test.go index 7ac2a09d6c..328d0c230d 100644 --- a/beacon-chain/core/epoch/participation_test.go +++ b/beacon-chain/core/epoch/participation_test.go @@ -8,13 +8,14 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" ) -func TestComputeValidatorParticipation(t *testing.T) { +func TestComputeValidatorParticipation_PreviousEpoch(t *testing.T) { params.OverrideBeaconConfig(params.MinimalSpecConfig()) e := uint64(1) - attestedBalance := uint64(1) + attestedBalance := uint64(20) * params.BeaconConfig().MaxEffectiveBalance validatorCount := uint64(100) validators := make([]*ethpb.Validator, validatorCount) @@ -27,22 +28,128 @@ func TestComputeValidatorParticipation(t *testing.T) { balances[i] = params.BeaconConfig().MaxEffectiveBalance } - atts := []*pb.PendingAttestation{{Data: ðpb.AttestationData{Target: ðpb.Checkpoint{}}}} - - s := &pb.BeaconState{ - Slot: e*params.BeaconConfig().SlotsPerEpoch + 1, - Validators: validators, - Balances: balances, - BlockRoots: make([][]byte, 128), - Slashings: []uint64{0, 1e9, 1e9}, - RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), - CurrentEpochAttestations: atts, - FinalizedCheckpoint: ðpb.Checkpoint{}, - JustificationBits: bitfield.Bitvector4{0x00}, - CurrentJustifiedCheckpoint: ðpb.Checkpoint{}, + blockRoots := make([][]byte, 256) + for i := 0; i < len(blockRoots); i++ { + slot := bytesutil.Bytes32(uint64(i)) + blockRoots[i] = slot + } + target := ðpb.Checkpoint{ + Epoch: e, + Root: blockRoots[0], } - res, err := epoch.ComputeValidatorParticipation(s) + atts := []*pb.PendingAttestation{ + { + Data: ðpb.AttestationData{Target: target, Slot: 0}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: 1}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: 2}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: 3}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: 4}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + } + + s := &pb.BeaconState{ + Slot: e*params.BeaconConfig().SlotsPerEpoch + 1, + Validators: validators, + Balances: balances, + BlockRoots: blockRoots, + Slashings: []uint64{0, 1e9, 1e9}, + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + PreviousEpochAttestations: atts, + FinalizedCheckpoint: ðpb.Checkpoint{}, + JustificationBits: bitfield.Bitvector4{0x00}, + PreviousJustifiedCheckpoint: target, + } + + res, err := epoch.ComputeValidatorParticipation(s, e-1) + if err != nil { + t.Fatal(err) + } + + wanted := ðpb.ValidatorParticipation{ + VotedEther: attestedBalance, + EligibleEther: validatorCount * params.BeaconConfig().MaxEffectiveBalance, + GlobalParticipationRate: float32(attestedBalance) / float32(validatorCount*params.BeaconConfig().MaxEffectiveBalance), + } + + if !reflect.DeepEqual(res, wanted) { + t.Errorf("Incorrect validator participation, wanted %v received %v", wanted, res) + } +} + +func TestComputeValidatorParticipation_CurrentEpoch(t *testing.T) { + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + e := uint64(1) + attestedBalance := uint64(16) * params.BeaconConfig().MaxEffectiveBalance + validatorCount := uint64(100) + + validators := make([]*ethpb.Validator, validatorCount) + balances := make([]uint64, validatorCount) + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + } + balances[i] = params.BeaconConfig().MaxEffectiveBalance + } + + slot := e*params.BeaconConfig().SlotsPerEpoch + 4 + blockRoots := make([][]byte, 256) + for i := 0; i < len(blockRoots); i++ { + slot := bytesutil.Bytes32(uint64(i)) + blockRoots[i] = slot + } + target := ðpb.Checkpoint{ + Epoch: e, + Root: blockRoots[params.BeaconConfig().SlotsPerEpoch], + } + + atts := []*pb.PendingAttestation{ + { + Data: ðpb.AttestationData{Target: target, Slot: slot - 4}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: slot - 3}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: slot - 2}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + Data: ðpb.AttestationData{Target: target, Slot: slot - 1}, + AggregationBits: []byte{0xFF, 0xFF, 0xFF, 0xFF}, + }, + } + + s := &pb.BeaconState{ + Slot: slot, + Validators: validators, + Balances: balances, + BlockRoots: blockRoots, + Slashings: []uint64{0, 1e9, 1e9}, + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + CurrentEpochAttestations: atts, + FinalizedCheckpoint: ðpb.Checkpoint{}, + JustificationBits: bitfield.Bitvector4{0x00}, + CurrentJustifiedCheckpoint: target, + } + + res, err := epoch.ComputeValidatorParticipation(s, e) if err != nil { t.Fatal(err) } diff --git a/beacon-chain/rpc/beacon/validators.go b/beacon-chain/rpc/beacon/validators.go index 2a2afe321f..2c5b2d8add 100644 --- a/beacon-chain/rpc/beacon/validators.go +++ b/beacon-chain/rpc/beacon/validators.go @@ -252,6 +252,7 @@ func (bs *Server) GetValidatorParticipation( ) (*ethpb.ValidatorParticipationResponse, error) { headState := bs.HeadFetcher.HeadState() currentEpoch := helpers.SlotToEpoch(headState.Slot) + prevEpoch := helpers.PrevEpoch(headState) var requestedEpoch uint64 var isGenesis bool @@ -261,7 +262,7 @@ func (bs *Server) GetValidatorParticipation( case *ethpb.GetValidatorParticipationRequest_Epoch: requestedEpoch = q.Epoch default: - requestedEpoch = currentEpoch + requestedEpoch = prevEpoch } if requestedEpoch > helpers.SlotToEpoch(headState.Slot) { @@ -285,7 +286,7 @@ func (bs *Server) GetValidatorParticipation( Finalized: true, Participation: participation, }, nil - } else if requestedEpoch < helpers.SlotToEpoch(headState.Slot) { + } else if requestedEpoch < prevEpoch { participation, err := bs.BeaconDB.ArchivedValidatorParticipation(ctx, requestedEpoch) if err != nil { return nil, status.Errorf(codes.Internal, "could not fetch archived participation: %v", err) @@ -304,7 +305,7 @@ func (bs *Server) GetValidatorParticipation( } // Else if the request is for the current epoch, we compute validator participation // right away and return the result based on the head state. - participation, err := epoch.ComputeValidatorParticipation(headState) + participation, err := epoch.ComputeValidatorParticipation(headState, requestedEpoch) if err != nil { return nil, status.Errorf(codes.Internal, "could not compute participation: %v", err) } diff --git a/beacon-chain/rpc/beacon/validators_test.go b/beacon-chain/rpc/beacon/validators_test.go index 539d084bb7..efb8553943 100644 --- a/beacon-chain/rpc/beacon/validators_test.go +++ b/beacon-chain/rpc/beacon/validators_test.go @@ -811,7 +811,7 @@ func TestServer_GetValidatorsParticipation_FromArchive(t *testing.T) { VotedEther: 20, EligibleEther: 20, } - if err := db.SaveArchivedValidatorParticipation(ctx, epoch, part); err != nil { + if err := db.SaveArchivedValidatorParticipation(ctx, epoch-2, part); err != nil { t.Fatal(err) } @@ -843,13 +843,13 @@ func TestServer_GetValidatorsParticipation_FromArchive(t *testing.T) { } want := ðpb.ValidatorParticipationResponse{ - Epoch: epoch, + Epoch: epoch - 2, Finalized: true, Participation: part, } res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ - Epoch: epoch, + Epoch: epoch - 2, }, }) if err != nil { @@ -900,7 +900,11 @@ func TestServer_GetValidatorsParticipation_CurrentEpoch(t *testing.T) { HeadFetcher: &mock.ChainService{State: s}, } - res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{}) + res, err := bs.GetValidatorParticipation(ctx, ðpb.GetValidatorParticipationRequest{ + QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{ + Epoch: epoch, + }, + }) if err != nil { t.Fatal(err) } diff --git a/tools/forkchecker/forkchecker.go b/tools/forkchecker/forkchecker.go index f5d96e050f..9229f40308 100644 --- a/tools/forkchecker/forkchecker.go +++ b/tools/forkchecker/forkchecker.go @@ -109,7 +109,11 @@ func compareHeads(clients map[string]pb.BeaconChainClient) { logHead(endpt2, head2) if (head1.BlockSlot+1)%params.BeaconConfig().SlotsPerEpoch == 0 { - p, err := clients[endpt2].GetValidatorParticipation(context.Background(), &pb.GetValidatorParticipationRequest{}) + p, err := clients[endpt2].GetValidatorParticipation(context.Background(), &pb.GetValidatorParticipationRequest{ + QueryFilter: &pb.GetValidatorParticipationRequest_Epoch{ + Epoch: head2.BlockSlot / params.BeaconConfig().SlotsPerEpoch, + }, + }) if err != nil { log.Fatal(err) }