diff --git a/beacon-chain/rpc/eth/beacon/BUILD.bazel b/beacon-chain/rpc/eth/beacon/BUILD.bazel index 45e905b16..097fd2ca2 100644 --- a/beacon-chain/rpc/eth/beacon/BUILD.bazel +++ b/beacon-chain/rpc/eth/beacon/BUILD.bazel @@ -23,6 +23,7 @@ go_library( "//beacon-chain/core/feed/block:go_default_library", "//beacon-chain/core/feed/operation:go_default_library", "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/transition:go_default_library", "//beacon-chain/db:go_default_library", "//beacon-chain/db/filters:go_default_library", "//beacon-chain/execution:go_default_library", @@ -82,6 +83,7 @@ go_test( "//api/grpc:go_default_library", "//beacon-chain/blockchain/testing:go_default_library", "//beacon-chain/core/signing:go_default_library", + "//beacon-chain/core/transition:go_default_library", "//beacon-chain/db:go_default_library", "//beacon-chain/db/testing:go_default_library", "//beacon-chain/execution/testing:go_default_library", diff --git a/beacon-chain/rpc/eth/beacon/pool.go b/beacon-chain/rpc/eth/beacon/pool.go index 1e9f9ab22..88d765e45 100644 --- a/beacon-chain/rpc/eth/beacon/pool.go +++ b/beacon-chain/rpc/eth/beacon/pool.go @@ -8,6 +8,7 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/feed" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/feed/operation" corehelpers "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/rpc/eth/helpers" "github.com/prysmaticlabs/prysm/v3/config/features" "github.com/prysmaticlabs/prysm/v3/crypto/bls" @@ -164,6 +165,10 @@ func (bs *Server) SubmitAttesterSlashing(ctx context.Context, req *ethpbv1.Attes if err != nil { return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) } + headState, err = transition.ProcessSlotsIfPossible(ctx, headState, req.Attestation_1.Data.Slot) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not process slots: %v", err) + } alphaSlashing := migration.V1AttSlashingToV1Alpha1(req) err = blocks.VerifyAttesterSlashing(ctx, headState, alphaSlashing) @@ -216,6 +221,10 @@ func (bs *Server) SubmitProposerSlashing(ctx context.Context, req *ethpbv1.Propo if err != nil { return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) } + headState, err = transition.ProcessSlotsIfPossible(ctx, headState, req.SignedHeader_1.Message.Slot) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not process slots: %v", err) + } alphaSlashing := migration.V1ProposerSlashingToV1Alpha1(req) err = blocks.VerifyProposerSlashing(headState, alphaSlashing) @@ -269,6 +278,14 @@ func (bs *Server) SubmitVoluntaryExit(ctx context.Context, req *ethpbv1.SignedVo if err != nil { return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err) } + s, err := slots.EpochStart(req.Message.Epoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not get epoch from message: %v", err) + } + headState, err = transition.ProcessSlotsIfPossible(ctx, headState, s) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not process slots: %v", err) + } validator, err := headState.ValidatorAtIndexReadOnly(req.Message.ValidatorIndex) if err != nil { diff --git a/beacon-chain/rpc/eth/beacon/pool_test.go b/beacon-chain/rpc/eth/beacon/pool_test.go index e0248f1ab..c9c9cfb24 100644 --- a/beacon-chain/rpc/eth/beacon/pool_test.go +++ b/beacon-chain/rpc/eth/beacon/pool_test.go @@ -11,6 +11,7 @@ import ( grpcutil "github.com/prysmaticlabs/prysm/v3/api/grpc" blockchainmock "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/signing" + "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" slashingsmock "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/slashings/mock" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/voluntaryexits/mock" @@ -444,6 +445,80 @@ func TestSubmitAttesterSlashing_Ok(t *testing.T) { assert.Equal(t, true, broadcaster.BroadcastCalled) } +func TestSubmitAttesterSlashing_AcrossFork(t *testing.T) { + ctx := context.Background() + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 1 + params.OverrideBeaconConfig(config) + + bs, keys := util.DeterministicGenesisState(t, 1) + + slashing := ðpbv1.AttesterSlashing{ + Attestation_1: ðpbv1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: ðpbv1.AttestationData{ + Slot: params.BeaconConfig().SlotsPerEpoch, + Index: 1, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32), + Source: ðpbv1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot1"), 32), + }, + Target: ðpbv1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot1"), 32), + }, + }, + Signature: make([]byte, 96), + }, + Attestation_2: ðpbv1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: ðpbv1.AttestationData{ + Slot: params.BeaconConfig().SlotsPerEpoch, + Index: 1, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32), + Source: ðpbv1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot2"), 32), + }, + Target: ðpbv1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot2"), 32), + }, + }, + Signature: make([]byte, 96), + }, + } + + newBs := bs.Copy() + newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch) + require.NoError(t, err) + + for _, att := range []*ethpbv1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} { + sb, err := signing.ComputeDomainAndSign(newBs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0]) + require.NoError(t, err) + sig, err := bls.SignatureFromBytes(sb) + require.NoError(t, err) + att.Signature = sig.Marshal() + } + + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + } + + _, err = s.SubmitAttesterSlashing(ctx, slashing) + require.NoError(t, err) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) + require.Equal(t, 1, len(pendingSlashings)) + assert.DeepEqual(t, migration.V1AttSlashingToV1Alpha1(slashing), pendingSlashings[0]) + assert.Equal(t, true, broadcaster.BroadcastCalled) +} + func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) { ctx := context.Background() bs, err := util.NewBeaconState() @@ -551,6 +626,68 @@ func TestSubmitProposerSlashing_Ok(t *testing.T) { assert.Equal(t, true, broadcaster.BroadcastCalled) } +func TestSubmitProposerSlashing_AcrossFork(t *testing.T) { + ctx := context.Background() + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 1 + params.OverrideBeaconConfig(config) + + bs, keys := util.DeterministicGenesisState(t, 1) + + slashing := ðpbv1.ProposerSlashing{ + SignedHeader_1: ðpbv1.SignedBeaconBlockHeader{ + Message: ðpbv1.BeaconBlockHeader{ + Slot: params.BeaconConfig().SlotsPerEpoch, + ProposerIndex: 0, + ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32), + StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32), + BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32), + }, + Signature: make([]byte, 96), + }, + SignedHeader_2: ðpbv1.SignedBeaconBlockHeader{ + Message: ðpbv1.BeaconBlockHeader{ + Slot: params.BeaconConfig().SlotsPerEpoch, + ProposerIndex: 0, + ParentRoot: bytesutil.PadTo([]byte("parentroot2"), 32), + StateRoot: bytesutil.PadTo([]byte("stateroot2"), 32), + BodyRoot: bytesutil.PadTo([]byte("bodyroot2"), 32), + }, + Signature: make([]byte, 96), + }, + } + + newBs := bs.Copy() + newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch) + require.NoError(t, err) + + for _, h := range []*ethpbv1.SignedBeaconBlockHeader{slashing.SignedHeader_1, slashing.SignedHeader_2} { + sb, err := signing.ComputeDomainAndSign( + newBs, + slots.ToEpoch(h.Message.Slot), + h.Message, + params.BeaconConfig().DomainBeaconProposer, + keys[0], + ) + require.NoError(t, err) + sig, err := bls.SignatureFromBytes(sb) + require.NoError(t, err) + h.Signature = sig.Marshal() + } + + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + } + + _, err = s.SubmitProposerSlashing(ctx, slashing) + require.NoError(t, err) +} + func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) { ctx := context.Background() bs, err := util.NewBeaconState() @@ -630,6 +767,47 @@ func TestSubmitVoluntaryExit_Ok(t *testing.T) { assert.Equal(t, true, broadcaster.BroadcastCalled) } +func TestSubmitVoluntaryExit_AcrossFork(t *testing.T) { + ctx := context.Background() + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = params.BeaconConfig().ShardCommitteePeriod + 1 + params.OverrideBeaconConfig(config) + + bs, keys := util.DeterministicGenesisState(t, 1) + // Satisfy activity time required before exiting. + require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().ShardCommitteePeriod)))) + + exit := ðpbv1.SignedVoluntaryExit{ + Message: ðpbv1.VoluntaryExit{ + Epoch: params.BeaconConfig().ShardCommitteePeriod + 1, + ValidatorIndex: 0, + }, + Signature: make([]byte, 96), + } + + newBs := bs.Copy() + newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().ShardCommitteePeriod)+1)) + require.NoError(t, err) + + sb, err := signing.ComputeDomainAndSign(newBs, exit.Message.Epoch, exit.Message, params.BeaconConfig().DomainVoluntaryExit, keys[0]) + require.NoError(t, err) + sig, err := bls.SignatureFromBytes(sb) + require.NoError(t, err) + exit.Signature = sig.Marshal() + + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + VoluntaryExitsPool: &mock.PoolMock{}, + Broadcaster: broadcaster, + } + + _, err = s.SubmitVoluntaryExit(ctx, exit) + require.NoError(t, err) +} + func TestSubmitVoluntaryExit_InvalidValidatorIndex(t *testing.T) { ctx := context.Background()