diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index a6c98ef4c..ce9e01bb0 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -13,7 +13,6 @@ go_library( "//beacon-chain/builder:go_default_library", "//beacon-chain/cache:go_default_library", "//beacon-chain/core/helpers:go_default_library", - "//beacon-chain/core/transition:go_default_library", "//beacon-chain/db/kv:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/synccommittee:go_default_library", diff --git a/beacon-chain/rpc/eth/validator/validator.go b/beacon-chain/rpc/eth/validator/validator.go index bd40911ac..b3e5b8d1b 100644 --- a/beacon-chain/rpc/eth/validator/validator.go +++ b/beacon-chain/rpc/eth/validator/validator.go @@ -15,7 +15,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/builder" "github.com/prysmaticlabs/prysm/v3/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition" "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/kv" rpchelpers "github.com/prysmaticlabs/prysm/v3/beacon-chain/rpc/eth/helpers" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" @@ -61,13 +60,13 @@ func (vs *Server) GetAttesterDuties(ctx context.Context, req *ethpbv1.AttesterDu return nil, status.Errorf(codes.Internal, "Could not check optimistic status: %v", err) } - s, err := vs.HeadFetcher.HeadState(ctx) + startSlot, err := slots.EpochStart(req.Epoch) if err != nil { - return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) + return nil, status.Errorf(codes.Internal, "Could not get start slot from epoch: %v", err) } - s, err = advanceState(ctx, s, req.Epoch, currentEpoch) + s, err := vs.StateFetcher.StateBySlot(ctx, startSlot) if err != nil { - return nil, status.Errorf(codes.Internal, "Could not advance state to requested epoch start slot: %v", err) + return nil, status.Errorf(codes.Internal, "Could not get state: %v", err) } committeeAssignments, _, err := helpers.CommitteeAssignments(ctx, s, req.Epoch) @@ -144,13 +143,13 @@ func (vs *Server) GetProposerDuties(ctx context.Context, req *ethpbv1.ProposerDu return nil, status.Errorf(codes.Internal, "Could not check optimistic status: %v", err) } - s, err := vs.HeadFetcher.HeadState(ctx) + startSlot, err := slots.EpochStart(req.Epoch) if err != nil { - return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) + return nil, status.Errorf(codes.Internal, "Could not get start slot from epoch: %v", err) } - s, err = advanceState(ctx, s, req.Epoch, currentEpoch) + s, err := vs.StateFetcher.StateBySlot(ctx, startSlot) if err != nil { - return nil, status.Errorf(codes.Internal, "Could not advance state to requested epoch start slot: %v", err) + return nil, status.Errorf(codes.Internal, "Could not get state: %v", err) } _, proposals, err := helpers.CommitteeAssignments(ctx, s, req.Epoch) @@ -179,7 +178,7 @@ func (vs *Server) GetProposerDuties(ctx context.Context, req *ethpbv1.ProposerDu return duties[i].Slot < duties[j].Slot }) - root, err := vs.proposalDependentRoot(ctx, s, req.Epoch) + root, err := proposalDependentRoot(s, req.Epoch) if err != nil { return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err) } @@ -1059,7 +1058,7 @@ func attestationDependentRoot(s state.BeaconState, epoch types.Epoch) ([]byte, e // proposalDependentRoot is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1) // or the genesis block root in the case of underflow. -func (vs *Server) proposalDependentRoot(ctx context.Context, s state.BeaconState, epoch types.Epoch) ([]byte, error) { +func proposalDependentRoot(s state.BeaconState, epoch types.Epoch) ([]byte, error) { var dependentRootSlot types.Slot if epoch == 0 { dependentRootSlot = 0 @@ -1070,47 +1069,11 @@ func (vs *Server) proposalDependentRoot(ctx context.Context, s state.BeaconState } dependentRootSlot = epochStartSlot.Sub(1) } - var root []byte - var err error - // Per spec, if the dependent root epoch is greater than current epoch, use the head root. - if dependentRootSlot >= s.Slot() { - root, err = vs.HeadFetcher.HeadRoot(ctx) - if err != nil { - return nil, err - } - } else { - root, err = helpers.BlockRootAtSlot(s, dependentRootSlot) - if err != nil { - return nil, errors.Wrap(err, "could not get block root") - } - } - - return root, nil -} - -// advanceState advances state with empty transitions up to the requested epoch start slot. -// In case 1 epoch ahead was requested, we take the start slot of the current epoch. -// Taking the start slot of the next epoch would result in an error inside transition.ProcessSlots. -func advanceState(ctx context.Context, s state.BeaconState, requestedEpoch, currentEpoch types.Epoch) (state.BeaconState, error) { - var epochStartSlot types.Slot - var err error - if requestedEpoch == currentEpoch+1 { - epochStartSlot, err = slots.EpochStart(requestedEpoch.Sub(1)) - if err != nil { - return nil, errors.Wrap(err, "Could not obtain epoch's start slot") - } - } else { - epochStartSlot, err = slots.EpochStart(requestedEpoch) - if err != nil { - return nil, errors.Wrap(err, "Could not obtain epoch's start slot") - } - } - s, err = transition.ProcessSlotsIfPossible(ctx, s, epochStartSlot) + root, err := helpers.BlockRootAtSlot(s, dependentRootSlot) if err != nil { - return nil, errors.Wrapf(err, "Could not process slots up to %d", epochStartSlot) + return nil, errors.Wrap(err, "could not get block root") } - - return s, nil + return root, nil } // Logic based on https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ diff --git a/beacon-chain/rpc/eth/validator/validator_test.go b/beacon-chain/rpc/eth/validator/validator_test.go index 075fd3b26..acc3996c1 100644 --- a/beacon-chain/rpc/eth/validator/validator_test.go +++ b/beacon-chain/rpc/eth/validator/validator_test.go @@ -85,7 +85,7 @@ func TestGetAttesterDuties(t *testing.T) { State: bs, Root: genesisRoot[:], Slot: &chainSlot, } vs := &Server{ - HeadFetcher: chain, + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, TimeFetcher: chain, SyncChecker: &mockSync.Sync{IsSyncing: false}, OptimisticModeFetcher: chain, @@ -139,55 +139,6 @@ func TestGetAttesterDuties(t *testing.T) { assert.Equal(t, types.CommitteeIndex(110), duty.ValidatorCommitteeIndex) }) - t.Run("Require slot processing", func(t *testing.T) { - // We create local variables to not interfere with other tests. - // Slot processing might have unexpected side-effects. - - bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) - require.NoError(t, err, "Could not set up genesis state") - // Set state to non-epoch start slot. - require.NoError(t, bs.SetSlot(5)) - genesisRoot, err := genesis.Block.HashTreeRoot() - require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, fieldparams.BlockRootsLength) - roots[0] = genesisRoot[:] - require.NoError(t, bs.SetBlockRoots(roots)) - - pubKeys := make([][]byte, len(deposits)) - indices := make([]uint64, len(deposits)) - for i := 0; i < len(deposits); i++ { - pubKeys[i] = deposits[i].Data.PublicKey - indices[i] = uint64(i) - } - chainSlot := params.BeaconConfig().SlotsPerEpoch.Mul(2) - chain := &mockChain.ChainService{ - State: bs, Root: genesisRoot[:], Slot: &chainSlot, - } - vs := &Server{ - HeadFetcher: chain, - TimeFetcher: chain, - OptimisticModeFetcher: chain, - SyncChecker: &mockSync.Sync{IsSyncing: false}, - } - - req := ðpbv1.AttesterDutiesRequest{ - Epoch: 2, - Index: []types.ValidatorIndex{0}, - } - resp, err := vs.GetAttesterDuties(ctx, req) - require.NoError(t, err) - assert.DeepEqual(t, bs.BlockRoots()[31], resp.DependentRoot) - require.Equal(t, 1, len(resp.Data)) - duty := resp.Data[0] - assert.Equal(t, types.CommitteeIndex(1), duty.CommitteeIndex) - assert.Equal(t, types.Slot(86), duty.Slot) - assert.Equal(t, types.ValidatorIndex(0), duty.ValidatorIndex) - assert.DeepEqual(t, pubKeys[0], duty.Pubkey) - assert.Equal(t, uint64(128), duty.CommitteeLength) - assert.Equal(t, uint64(4), duty.CommitteesAtSlot) - assert.Equal(t, types.CommitteeIndex(44), duty.ValidatorCommitteeIndex) - }) - t.Run("Epoch out of bound", func(t *testing.T) { currentEpoch := slots.ToEpoch(bs.Slot()) req := ðpbv1.AttesterDutiesRequest{ @@ -234,7 +185,7 @@ func TestGetAttesterDuties(t *testing.T) { State: bs, Root: genesisRoot[:], Slot: &chainSlot, Optimistic: true, } vs := &Server{ - HeadFetcher: chain, + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, TimeFetcher: chain, OptimisticModeFetcher: chain, SyncChecker: &mockSync.Sync{IsSyncing: false}, @@ -271,15 +222,10 @@ func TestGetProposerDuties(t *testing.T) { require.NoError(t, err) eth1Data, err := util.DeterministicEth1Data(len(deposits)) require.NoError(t, err) - bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) - require.NoError(t, err, "Could not set up genesis state") - // Set state to non-epoch start slot. - require.NoError(t, bs.SetSlot(5)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") roots := make([][]byte, fieldparams.BlockRootsLength) roots[0] = genesisRoot[:] - require.NoError(t, bs.SetBlockRoots(roots)) db := dbutil.SetupDB(t) pubKeys := make([][]byte, len(deposits)) @@ -287,19 +233,24 @@ func TestGetProposerDuties(t *testing.T) { pubKeys[i] = deposits[i].Data.PublicKey } - chainSlot := types.Slot(0) - chain := &mockChain.ChainService{ - State: bs, Root: genesisRoot[:], Slot: &chainSlot, - } - vs := &Server{ - HeadFetcher: chain, - TimeFetcher: chain, - OptimisticModeFetcher: chain, - SyncChecker: &mockSync.Sync{IsSyncing: false}, - ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), - } - t.Run("Ok", func(t *testing.T) { + bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) + require.NoError(t, err, "Could not set up genesis state") + require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch)) + require.NoError(t, bs.SetBlockRoots(roots)) + chainSlot := types.Slot(0) + chain := &mockChain.ChainService{ + State: bs, Root: genesisRoot[:], Slot: &chainSlot, + } + vs := &Server{ + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, + HeadFetcher: chain, + TimeFetcher: chain, + OptimisticModeFetcher: chain, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), + } + req := ðpbv1.ProposerDutiesRequest{ Epoch: 0, } @@ -323,6 +274,23 @@ func TestGetProposerDuties(t *testing.T) { }) t.Run("Prune payload ID cache ok", func(t *testing.T) { + bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) + require.NoError(t, err, "Could not set up genesis state") + require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch)) + require.NoError(t, bs.SetBlockRoots(roots)) + chainSlot := types.Slot(0) + chain := &mockChain.ChainService{ + State: bs, Root: genesisRoot[:], Slot: &chainSlot, + } + vs := &Server{ + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, + HeadFetcher: chain, + TimeFetcher: chain, + OptimisticModeFetcher: chain, + SyncChecker: &mockSync.Sync{IsSyncing: false}, + ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), + } + req := ðpbv1.ProposerDutiesRequest{ Epoch: 1, } @@ -330,7 +298,7 @@ func TestGetProposerDuties(t *testing.T) { vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(31, 2, [8]byte{2}, [32]byte{3}) vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(32, 4309, [8]byte{3}, [32]byte{4}) - _, err := vs.GetProposerDuties(ctx, req) + _, err = vs.GetProposerDuties(ctx, req) require.NoError(t, err) vid, _, has := vs.ProposerSlotIndexCache.GetProposerPayloadIDs(1, [32]byte{}) @@ -344,31 +312,18 @@ func TestGetProposerDuties(t *testing.T) { require.Equal(t, types.ValidatorIndex(4309), vid) }) - t.Run("Require slot processing", func(t *testing.T) { - // We create local variables to not interfere with other tests. - // Slot processing might have unexpected side-effects. - + t.Run("Epoch out of bound", func(t *testing.T) { bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) require.NoError(t, err, "Could not set up genesis state") // Set state to non-epoch start slot. require.NoError(t, bs.SetSlot(5)) - genesisRoot, err := genesis.Block.HashTreeRoot() - require.NoError(t, err, "Could not get signing root") - roots := make([][]byte, fieldparams.BlockRootsLength) - roots[0] = genesisRoot[:] require.NoError(t, bs.SetBlockRoots(roots)) - - pubKeys := make([][]byte, len(deposits)) - indices := make([]uint64, len(deposits)) - for i := 0; i < len(deposits); i++ { - pubKeys[i] = deposits[i].Data.PublicKey - indices[i] = uint64(i) - } - chainSlot := params.BeaconConfig().SlotsPerEpoch.Mul(2) + chainSlot := types.Slot(0) chain := &mockChain.ChainService{ State: bs, Root: genesisRoot[:], Slot: &chainSlot, } vs := &Server{ + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, HeadFetcher: chain, TimeFetcher: chain, OptimisticModeFetcher: chain, @@ -376,36 +331,21 @@ func TestGetProposerDuties(t *testing.T) { ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), } - req := ðpbv1.ProposerDutiesRequest{ - Epoch: 2, - } - resp, err := vs.GetProposerDuties(ctx, req) - require.NoError(t, err) - assert.DeepEqual(t, bs.BlockRoots()[31], resp.DependentRoot) - assert.Equal(t, 32, len(resp.Data)) - // We expect a proposer duty for slot 74. - var expectedDuty *ethpbv1.ProposerDuty - for _, duty := range resp.Data { - if duty.Slot == 74 { - expectedDuty = duty - } - } - require.NotNil(t, expectedDuty, "Expected duty for slot 74 not found") - assert.Equal(t, types.ValidatorIndex(11741), expectedDuty.ValidatorIndex) - assert.DeepEqual(t, pubKeys[11741], expectedDuty.Pubkey) - }) - - t.Run("Epoch out of bound", func(t *testing.T) { currentEpoch := slots.ToEpoch(bs.Slot()) req := ðpbv1.ProposerDutiesRequest{ Epoch: currentEpoch + 2, } - _, err := vs.GetProposerDuties(ctx, req) + _, err = vs.GetProposerDuties(ctx, req) require.NotNil(t, err) assert.ErrorContains(t, fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", currentEpoch+2, currentEpoch+1), err) }) t.Run("execution optimistic", func(t *testing.T) { + bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) + require.NoError(t, err, "Could not set up genesis state") + // Set state to non-epoch start slot. + require.NoError(t, bs.SetSlot(5)) + require.NoError(t, bs.SetBlockRoots(roots)) parentRoot := [32]byte{'a'} blk := util.NewBeaconBlock() blk.Block.ParentRoot = parentRoot[:] @@ -420,6 +360,7 @@ func TestGetProposerDuties(t *testing.T) { State: bs, Root: genesisRoot[:], Slot: &chainSlot, Optimistic: true, } vs := &Server{ + StateFetcher: &testutil.MockFetcher{BeaconState: bs}, HeadFetcher: chain, TimeFetcher: chain, OptimisticModeFetcher: chain,