From 5983d0a397d33954bf98eb655b5fa7f6f6b2cd9a Mon Sep 17 00:00:00 2001 From: Potuz Date: Sun, 28 Nov 2021 14:34:24 -0300 Subject: [PATCH] Allow requests for next sync committee (#9945) * Allow requests for next sync committee * fix deepsource and variable rename * Minor cleanup * Potuz's comments Co-authored-by: terence tsao --- beacon-chain/rpc/eth/beacon/sync_committee.go | 75 +++++++++++++++- .../rpc/eth/beacon/sync_committee_test.go | 86 +++++++++++++++++++ beacon-chain/rpc/testutil/BUILD.bazel | 11 ++- .../rpc/testutil/mock_genesis_timefetcher.go | 21 +++++ 4 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 beacon-chain/rpc/testutil/mock_genesis_timefetcher.go diff --git a/beacon-chain/rpc/eth/beacon/sync_committee.go b/beacon-chain/rpc/eth/beacon/sync_committee.go index 97e89ac45b..d2e435704b 100644 --- a/beacon-chain/rpc/eth/beacon/sync_committee.go +++ b/beacon-chain/rpc/eth/beacon/sync_committee.go @@ -16,6 +16,7 @@ import ( "github.com/prysmaticlabs/prysm/proto/eth/v2" ethpbv2 "github.com/prysmaticlabs/prysm/proto/eth/v2" ethpbalpha "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/time/slots" "go.opencensus.io/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -27,6 +28,40 @@ func (bs *Server) ListSyncCommittees(ctx context.Context, req *ethpbv2.StateSync ctx, span := trace.StartSpan(ctx, "beacon.ListSyncCommittees") defer span.End() + currentSlot := bs.GenesisTimeFetcher.CurrentSlot() + currentEpoch := slots.ToEpoch(currentSlot) + currentPeriodStartEpoch, err := slots.SyncCommitteePeriodStartEpoch(currentEpoch) + if err != nil { + return nil, status.Errorf( + codes.Internal, + "Could not calculate start period for slot %d: %v", + currentSlot, + err, + ) + } + + var reqPeriodStartEpoch types.Epoch + if req.Epoch == nil { + reqPeriodStartEpoch = currentPeriodStartEpoch + } else { + reqPeriodStartEpoch, err = slots.SyncCommitteePeriodStartEpoch(*req.Epoch) + if err != nil { + return nil, status.Errorf( + codes.Internal, + "Could not calculate start period for epoch %d: %v", + *req.Epoch, + err, + ) + } + if reqPeriodStartEpoch > currentPeriodStartEpoch+params.BeaconConfig().EpochsPerSyncCommitteePeriod { + return nil, status.Errorf( + codes.Internal, + "Could not fetch sync committee too far in the future. Requested epoch: %d, current epoch: %d", + *req.Epoch, currentEpoch, + ) + } + } + st, err := bs.stateFromRequest(ctx, &stateRequest{ epoch: req.Epoch, stateId: req.StateId, @@ -35,10 +70,20 @@ func (bs *Server) ListSyncCommittees(ctx context.Context, req *ethpbv2.StateSync return nil, status.Errorf(codes.Internal, "Could not fetch beacon state using request: %v", err) } - // Get the current sync committee and sync committee indices from the state. - committeeIndices, committee, err := currentCommitteeIndicesFromState(st) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not get sync committee indices from state: %v", err) + var committeeIndices []types.ValidatorIndex + var committee *ethpbalpha.SyncCommittee + if reqPeriodStartEpoch > currentPeriodStartEpoch { + // Get the next sync committee and sync committee indices from the state. + committeeIndices, committee, err = nextCommitteeIndicesFromState(st) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not get next sync committee indices: %v", err) + } + } else { + // Get the current sync committee and sync committee indices from the state. + committeeIndices, committee, err = currentCommitteeIndicesFromState(st) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not get current sync committee indices: %v", err) + } } subcommittees, err := extractSyncSubcommittees(st, committee) if err != nil { @@ -75,6 +120,28 @@ func currentCommitteeIndicesFromState(st state.BeaconState) ([]types.ValidatorIn return committeeIndices, committee, nil } +func nextCommitteeIndicesFromState(st state.BeaconState) ([]types.ValidatorIndex, *ethpbalpha.SyncCommittee, error) { + committee, err := st.NextSyncCommittee() + if err != nil { + return nil, nil, fmt.Errorf( + "could not get sync committee: %v", err, + ) + } + + committeeIndices := make([]types.ValidatorIndex, len(committee.Pubkeys)) + for i, key := range committee.Pubkeys { + index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(key)) + if !ok { + return nil, nil, fmt.Errorf( + "validator index not found for pubkey %#x", + bytesutil.Trunc(key), + ) + } + committeeIndices[i] = index + } + return committeeIndices, committee, nil +} + func extractSyncSubcommittees(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([]*eth.SyncSubcommitteeValidators, error) { subcommitteeCount := params.BeaconConfig().SyncCommitteeSubnetCount subcommittees := make([]*ethpbv2.SyncSubcommitteeValidators, subcommitteeCount) diff --git a/beacon-chain/rpc/eth/beacon/sync_committee_test.go b/beacon-chain/rpc/eth/beacon/sync_committee_test.go index fb17b68caa..245066ada4 100644 --- a/beacon-chain/rpc/eth/beacon/sync_committee_test.go +++ b/beacon-chain/rpc/eth/beacon/sync_committee_test.go @@ -4,6 +4,7 @@ import ( "context" "strings" "testing" + "time" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" types "github.com/prysmaticlabs/eth2-types" @@ -55,6 +56,37 @@ func Test_currentCommitteeIndicesFromState(t *testing.T) { }) } +func Test_nextCommitteeIndicesFromState(t *testing.T) { + st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize) + vals := st.Validators() + wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize) + wantedIndices := make([]types.ValidatorIndex, len(wantedCommittee)) + for i := 0; i < len(wantedCommittee); i++ { + wantedIndices[i] = types.ValidatorIndex(i) + wantedCommittee[i] = vals[i].PublicKey + } + require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{ + Pubkeys: wantedCommittee, + AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength), + })) + + t.Run("OK", func(t *testing.T) { + indices, committee, err := nextCommitteeIndicesFromState(st) + require.NoError(t, err) + require.DeepEqual(t, wantedIndices, indices) + require.DeepEqual(t, wantedCommittee, committee.Pubkeys) + }) + t.Run("validator in committee not found in state", func(t *testing.T) { + wantedCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48) + require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{ + Pubkeys: wantedCommittee, + AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength), + })) + _, _, err := nextCommitteeIndicesFromState(st) + require.ErrorContains(t, "index not found for pubkey", err) + }) +} + func Test_extractSyncSubcommittees(t *testing.T) { st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize) vals := st.Validators() @@ -123,6 +155,9 @@ func TestListSyncCommittees(t *testing.T) { require.NoError(t, err) s := &Server{ + GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{ + Genesis: time.Now(), + }, StateFetcher: &testutil.MockFetcher{ BeaconState: st, }, @@ -150,6 +185,57 @@ func TestListSyncCommittees(t *testing.T) { } } +func TestListSyncCommitteesFuture(t *testing.T) { + ctx := context.Background() + st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize) + syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize) + vals := st.Validators() + for i := 0; i < len(syncCommittee); i++ { + syncCommittee[i] = vals[i].PublicKey + } + require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{ + Pubkeys: syncCommittee, + AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength), + })) + + s := &Server{ + GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{ + Genesis: time.Now(), + }, + StateFetcher: &testutil.MockFetcher{ + BeaconState: st, + }, + } + req := ðpbv2.StateSyncCommitteesRequest{} + epoch := 2 * params.BeaconConfig().EpochsPerSyncCommitteePeriod + req.Epoch = &epoch + _, err := s.ListSyncCommittees(ctx, req) + require.ErrorContains(t, "Could not fetch sync committee too far in the future", err) + + epoch = 2*params.BeaconConfig().EpochsPerSyncCommitteePeriod - 1 + resp, err := s.ListSyncCommittees(ctx, req) + require.NoError(t, err) + + require.NotNil(t, resp.Data) + committeeVals := resp.Data.Validators + require.NotNil(t, committeeVals) + require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(committeeVals)), "incorrect committee size") + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ { + assert.Equal(t, types.ValidatorIndex(i), committeeVals[i]) + } + require.NotNil(t, resp.Data.ValidatorAggregates) + assert.Equal(t, params.BeaconConfig().SyncCommitteeSubnetCount, uint64(len(resp.Data.ValidatorAggregates))) + for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ { + vStartIndex := types.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount * i) + vEndIndex := types.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize/params.BeaconConfig().SyncCommitteeSubnetCount*(i+1) - 1) + j := 0 + for vIndex := vStartIndex; vIndex <= vEndIndex; vIndex++ { + assert.Equal(t, vIndex, resp.Data.ValidatorAggregates[i].Validators[j]) + j++ + } + } +} + func TestSubmitPoolSyncCommitteeSignatures(t *testing.T) { ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{}) st, _ := util.DeterministicGenesisStateAltair(t, 10) diff --git a/beacon-chain/rpc/testutil/BUILD.bazel b/beacon-chain/rpc/testutil/BUILD.bazel index 904cc55dd1..4714040101 100644 --- a/beacon-chain/rpc/testutil/BUILD.bazel +++ b/beacon-chain/rpc/testutil/BUILD.bazel @@ -3,8 +3,15 @@ load("@prysm//tools/go:def.bzl", "go_library") go_library( name = "go_default_library", testonly = True, - srcs = ["mock_state_fetcher.go"], + srcs = [ + "mock_genesis_timefetcher.go", + "mock_state_fetcher.go", + ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/rpc/testutil", visibility = ["//beacon-chain:__subpackages__"], - deps = ["//beacon-chain/state:go_default_library"], + deps = [ + "//beacon-chain/state:go_default_library", + "//config/params:go_default_library", + "@com_github_prysmaticlabs_eth2_types//:go_default_library", + ], ) diff --git a/beacon-chain/rpc/testutil/mock_genesis_timefetcher.go b/beacon-chain/rpc/testutil/mock_genesis_timefetcher.go new file mode 100644 index 0000000000..7d54c7a049 --- /dev/null +++ b/beacon-chain/rpc/testutil/mock_genesis_timefetcher.go @@ -0,0 +1,21 @@ +package testutil + +import ( + "time" + + types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/config/params" +) + +// MockGenesisTimeFetcher is a fake implementation of the blockchain.TimeFetcher +type MockGenesisTimeFetcher struct { + Genesis time.Time +} + +func (m *MockGenesisTimeFetcher) GenesisTime() time.Time { + return m.Genesis +} + +func (m *MockGenesisTimeFetcher) CurrentSlot() types.Slot { + return types.Slot(uint64(time.Now().Unix()-m.Genesis.Unix()) / params.BeaconConfig().SecondsPerSlot) +}