diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a8acb9be6..4738828c4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Add Discovery Rebooter Feature. - Added GetBlockAttestationsV2 endpoint. - Light client support: Consensus types for Electra +- Added SubmitPoolAttesterSlashingV2 endpoint. ### Changed diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 09358123e6f..ac51e8b2586 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -690,12 +690,22 @@ func (s *Service) beaconEndpoints( }, { template: "/eth/v1/beacon/pool/attester_slashings", - name: namespace + ".SubmitAttesterSlashing", + name: namespace + ".SubmitAttesterSlashings", middleware: []middleware.Middleware{ middleware.ContentTypeHandler([]string{api.JsonMediaType}), middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), }, - handler: server.SubmitAttesterSlashing, + handler: server.SubmitAttesterSlashings, + methods: []string{http.MethodPost}, + }, + { + template: "/eth/v2/beacon/pool/attester_slashings", + name: namespace + ".SubmitAttesterSlashingsV2", + middleware: []middleware.Middleware{ + middleware.ContentTypeHandler([]string{api.JsonMediaType}), + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.SubmitAttesterSlashingsV2, methods: []string{http.MethodPost}, }, { diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index b2f286cb5fb..185182ff49c 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -42,6 +42,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet}, "/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost}, "/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost}, + "/eth/v2/beacon/pool/attester_slashings": {http.MethodPost}, "/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost}, "/eth/v1/beacon/pool/sync_committees": {http.MethodPost}, "/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost}, diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool.go b/beacon-chain/rpc/eth/beacon/handlers_pool.go index 9af9a0320f8..0d92b94b0bb 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" @@ -482,10 +483,10 @@ func (s *Server) GetAttesterSlashings(w http.ResponseWriter, r *http.Request) { httputil.WriteJson(w, &structs.GetAttesterSlashingsResponse{Data: slashings}) } -// SubmitAttesterSlashing submits an attester slashing object to node's pool and +// SubmitAttesterSlashings submits an attester slashing object to node's pool and // if passes validation node MUST broadcast it to network. -func (s *Server) SubmitAttesterSlashing(w http.ResponseWriter, r *http.Request) { - ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashing") +func (s *Server) SubmitAttesterSlashings(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashings") defer span.End() var req structs.AttesterSlashing @@ -504,16 +505,80 @@ func (s *Server) SubmitAttesterSlashing(w http.ResponseWriter, r *http.Request) httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest) return } + s.submitAttesterSlashing(w, ctx, slashing) +} + +// SubmitAttesterSlashingsV2 submits an attester slashing object to node's pool and +// if passes validation node MUST broadcast it to network. +func (s *Server) SubmitAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashingsV2") + defer span.End() + + versionHeader := r.Header.Get(api.VersionHeader) + if versionHeader == "" { + httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) + } + v, err := version.FromString(versionHeader) + if err != nil { + httputil.HandleError(w, "Invalid version: "+err.Error(), http.StatusBadRequest) + return + } + + if v >= version.Electra { + var req structs.AttesterSlashingElectra + err := json.NewDecoder(r.Body).Decode(&req) + switch { + case errors.Is(err, io.EOF): + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + + slashing, err := req.ToConsensus() + if err != nil { + httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest) + return + } + s.submitAttesterSlashing(w, ctx, slashing) + } else { + var req structs.AttesterSlashing + err := json.NewDecoder(r.Body).Decode(&req) + switch { + case errors.Is(err, io.EOF): + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + case err != nil: + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + return + } + + slashing, err := req.ToConsensus() + if err != nil { + httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest) + return + } + s.submitAttesterSlashing(w, ctx, slashing) + } +} + +func (s *Server) submitAttesterSlashing( + w http.ResponseWriter, + ctx context.Context, + slashing eth.AttSlashing, +) { headState, err := s.ChainInfoFetcher.HeadState(ctx) if err != nil { httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError) return } - headState, err = transition.ProcessSlotsIfPossible(ctx, headState, slashing.Attestation_1.Data.Slot) + headState, err = transition.ProcessSlotsIfPossible(ctx, headState, slashing.FirstAttestation().GetData().Slot) if err != nil { httputil.HandleError(w, "Could not process slots: "+err.Error(), http.StatusInternalServerError) return } + err = blocks.VerifyAttesterSlashing(ctx, headState, slashing) if err != nil { httputil.HandleError(w, "Invalid attester slashing: "+err.Error(), http.StatusBadRequest) diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go index d2ccfbfde25..593eb8a3d85 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server" "github.com/prysmaticlabs/prysm/v5/api/server/structs" blockchainmock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" @@ -37,6 +38,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/encoding/ssz" "github.com/prysmaticlabs/prysm/v5/network/httputil" ethpbv1alpha1 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" @@ -1142,383 +1144,350 @@ func TestGetProposerSlashings(t *testing.T) { assert.Equal(t, 2, len(resp.Data)) } -func TestSubmitAttesterSlashing_Ok(t *testing.T) { +func TestSubmitAttesterSlashings(t *testing.T) { ctx := context.Background() transition.SkipSlotCache.Disable() defer transition.SkipSlotCache.Enable() - _, keys, err := util.DeterministicDepositsAndKeys(1) - require.NoError(t, err) - validator := ðpbv1alpha1.Validator{ - PublicKey: keys[0].PublicKey().Marshal(), + attestationData1 := ðpbv1alpha1.AttestationData{ + CommitteeIndex: 1, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot1"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot1"), 32), + }, + } + attestationData2 := ðpbv1alpha1.AttestationData{ + CommitteeIndex: 1, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot2"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot2"), 32), + }, } - bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { - state.Validators = []*ethpbv1alpha1.Validator{validator} - return nil - }) - require.NoError(t, err) - slashing := ðpbv1alpha1.AttesterSlashing{ - Attestation_1: ðpbv1alpha1.IndexedAttestation{ - AttestingIndices: []uint64{0}, - Data: ðpbv1alpha1.AttestationData{ - Slot: 1, - CommitteeIndex: 1, - BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32), - Source: ðpbv1alpha1.Checkpoint{ - Epoch: 1, - Root: bytesutil.PadTo([]byte("sourceroot1"), 32), + t.Run("V1", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + attestationData1.Slot = 1 + attestationData2.Slot = 1 + slashing := ðpbv1alpha1.AttesterSlashing{ + Attestation_1: ðpbv1alpha1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: attestationData1, + Signature: make([]byte, 96), }, - Target: ðpbv1alpha1.Checkpoint{ - Epoch: 10, - Root: bytesutil.PadTo([]byte("targetroot1"), 32), + Attestation_2: ðpbv1alpha1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: attestationData2, + Signature: make([]byte, 96), }, - }, - Signature: make([]byte, 96), - }, - Attestation_2: ðpbv1alpha1.IndexedAttestation{ - AttestingIndices: []uint64{0}, - Data: ðpbv1alpha1.AttestationData{ - Slot: 1, - CommitteeIndex: 1, - BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32), - Source: ðpbv1alpha1.Checkpoint{ - Epoch: 1, - Root: bytesutil.PadTo([]byte("sourceroot2"), 32), + } + + _, keys, err := util.DeterministicDepositsAndKeys(1) + require.NoError(t, err) + validator := ðpbv1alpha1.Validator{ + PublicKey: keys[0].PublicKey().Marshal(), + } + + bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { + state.Validators = []*ethpbv1alpha1.Validator{validator} + return nil + }) + require.NoError(t, err) + + for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} { + sb, err := signing.ComputeDomainAndSign(bs, 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() + } + + chainmock := &blockchainmock.ChainService{State: bs} + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: chainmock, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + OperationNotifier: chainmock.OperationNotifier(), + } + + toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing}) + b, err := json.Marshal(toSubmit[0]) + require.NoError(t, err) + var body bytes.Buffer + _, err = body.Write(b) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAttesterSlashings(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) + require.Equal(t, 1, len(pendingSlashings)) + assert.DeepEqual(t, slashing, pendingSlashings[0]) + require.Equal(t, 1, broadcaster.NumMessages()) + assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) + _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing) + assert.Equal(t, true, ok) + }) + t.Run("accross-fork", func(t *testing.T) { + attestationData1.Slot = params.BeaconConfig().SlotsPerEpoch + attestationData2.Slot = params.BeaconConfig().SlotsPerEpoch + slashing := ðpbv1alpha1.AttesterSlashing{ + Attestation_1: ðpbv1alpha1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: attestationData1, + Signature: make([]byte, 96), }, - Target: ðpbv1alpha1.Checkpoint{ - Epoch: 10, - Root: bytesutil.PadTo([]byte("targetroot2"), 32), + Attestation_2: ðpbv1alpha1.IndexedAttestation{ + AttestingIndices: []uint64{0}, + Data: attestationData2, + Signature: make([]byte, 96), }, - }, - Signature: make([]byte, 96), - }, - } - - for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} { - sb, err := signing.ComputeDomainAndSign(bs, 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{} - chainmock := &blockchainmock.ChainService{State: bs} - s := &Server{ - ChainInfoFetcher: chainmock, - SlashingsPool: &slashingsmock.PoolMock{}, - Broadcaster: broadcaster, - OperationNotifier: chainmock.OperationNotifier(), - } - - toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing}) - b, err := json.Marshal(toSubmit[0]) - require.NoError(t, err) - var body bytes.Buffer - _, err = body.Write(b) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.SubmitAttesterSlashing(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) - require.Equal(t, 1, len(pendingSlashings)) - assert.DeepEqual(t, slashing, pendingSlashings[0]) - assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) - require.Equal(t, 1, broadcaster.NumMessages()) - _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing) - assert.Equal(t, true, ok) -} - -func TestSubmitAttesterSlashing_AcrossFork(t *testing.T) { - ctx := context.Background() - - transition.SkipSlotCache.Disable() - defer transition.SkipSlotCache.Enable() - - params.SetupTestConfigCleanup(t) - config := params.BeaconConfig() - config.AltairForkEpoch = 1 - params.OverrideBeaconConfig(config) - - bs, keys := util.DeterministicGenesisState(t, 1) - - slashing := ðpbv1alpha1.AttesterSlashing{ - Attestation_1: ðpbv1alpha1.IndexedAttestation{ - AttestingIndices: []uint64{0}, - Data: ðpbv1alpha1.AttestationData{ - Slot: params.BeaconConfig().SlotsPerEpoch, - CommitteeIndex: 1, - BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32), - Source: ðpbv1alpha1.Checkpoint{ - Epoch: 1, - Root: bytesutil.PadTo([]byte("sourceroot1"), 32), + } + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 1 + params.OverrideBeaconConfig(config) + + bs, keys := util.DeterministicGenesisState(t, 1) + newBs := bs.Copy() + newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch) + require.NoError(t, err) + + for _, att := range []*ethpbv1alpha1.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{} + chainmock := &blockchainmock.ChainService{State: bs} + s := &Server{ + ChainInfoFetcher: chainmock, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + OperationNotifier: chainmock.OperationNotifier(), + } + + toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing}) + b, err := json.Marshal(toSubmit[0]) + require.NoError(t, err) + var body bytes.Buffer + _, err = body.Write(b) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAttesterSlashings(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) + require.Equal(t, 1, len(pendingSlashings)) + assert.DeepEqual(t, slashing, pendingSlashings[0]) + require.Equal(t, 1, broadcaster.NumMessages()) + assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) + _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing) + assert.Equal(t, true, ok) + }) + t.Run("invalid-slashing", func(t *testing.T) { + bs, err := util.NewBeaconState() + require.NoError(t, err) + + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + } + + var body bytes.Buffer + _, err = body.WriteString(invalidAttesterSlashing) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAttesterSlashings(writer, request) + require.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "Invalid attester slashing", e.Message) + }) + }) + t.Run("V2", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + attestationData1.Slot = 1 + attestationData2.Slot = 1 + electraSlashing := ðpbv1alpha1.AttesterSlashingElectra{ + Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{ + AttestingIndices: []uint64{0}, + Data: attestationData1, + Signature: make([]byte, 96), }, - Target: ðpbv1alpha1.Checkpoint{ - Epoch: 10, - Root: bytesutil.PadTo([]byte("targetroot1"), 32), + Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{ + AttestingIndices: []uint64{0}, + Data: attestationData2, + Signature: make([]byte, 96), }, - }, - Signature: make([]byte, 96), - }, - Attestation_2: ðpbv1alpha1.IndexedAttestation{ - AttestingIndices: []uint64{0}, - Data: ðpbv1alpha1.AttestationData{ - Slot: params.BeaconConfig().SlotsPerEpoch, - CommitteeIndex: 1, - BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32), - Source: ðpbv1alpha1.Checkpoint{ - Epoch: 1, - Root: bytesutil.PadTo([]byte("sourceroot2"), 32), + } + + _, keys, err := util.DeterministicDepositsAndKeys(1) + require.NoError(t, err) + validator := ðpbv1alpha1.Validator{ + PublicKey: keys[0].PublicKey().Marshal(), + } + + ebs, err := util.NewBeaconStateElectra(func(state *ethpbv1alpha1.BeaconStateElectra) error { + state.Validators = []*ethpbv1alpha1.Validator{validator} + return nil + }) + require.NoError(t, err) + + for _, att := range []*ethpbv1alpha1.IndexedAttestationElectra{electraSlashing.Attestation_1, electraSlashing.Attestation_2} { + sb, err := signing.ComputeDomainAndSign(ebs, 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() + } + + chainmock := &blockchainmock.ChainService{State: ebs} + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: chainmock, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + OperationNotifier: chainmock.OperationNotifier(), + } + + toSubmit := structs.AttesterSlashingsElectraFromConsensus([]*ethpbv1alpha1.AttesterSlashingElectra{electraSlashing}) + b, err := json.Marshal(toSubmit[0]) + require.NoError(t, err) + var body bytes.Buffer + _, err = body.Write(b) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_electras", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAttesterSlashingsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, ebs, true) + require.Equal(t, 1, len(pendingSlashings)) + require.Equal(t, 1, broadcaster.NumMessages()) + assert.DeepEqual(t, electraSlashing, pendingSlashings[0]) + assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) + _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashingElectra) + assert.Equal(t, true, ok) + }) + t.Run("accross-fork", func(t *testing.T) { + attestationData1.Slot = params.BeaconConfig().SlotsPerEpoch + attestationData2.Slot = params.BeaconConfig().SlotsPerEpoch + slashing := ðpbv1alpha1.AttesterSlashingElectra{ + Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{ + AttestingIndices: []uint64{0}, + Data: attestationData1, + Signature: make([]byte, 96), }, - Target: ðpbv1alpha1.Checkpoint{ - Epoch: 10, - Root: bytesutil.PadTo([]byte("targetroot2"), 32), + Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{ + AttestingIndices: []uint64{0}, + Data: attestationData2, + Signature: make([]byte, 96), }, - }, - Signature: make([]byte, 96), - }, - } - - newBs := bs.Copy() - newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch) - require.NoError(t, err) - - for _, att := range []*ethpbv1alpha1.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{} - chainmock := &blockchainmock.ChainService{State: bs} - s := &Server{ - ChainInfoFetcher: chainmock, - SlashingsPool: &slashingsmock.PoolMock{}, - Broadcaster: broadcaster, - OperationNotifier: chainmock.OperationNotifier(), - } - - toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing}) - b, err := json.Marshal(toSubmit[0]) - require.NoError(t, err) - var body bytes.Buffer - _, err = body.Write(b) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.SubmitAttesterSlashing(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) - require.Equal(t, 1, len(pendingSlashings)) - assert.DeepEqual(t, slashing, pendingSlashings[0]) - assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) - require.Equal(t, 1, broadcaster.NumMessages()) - _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing) - assert.Equal(t, true, ok) -} - -func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) { - bs, err := util.NewBeaconState() - require.NoError(t, err) - - broadcaster := &p2pMock.MockBroadcaster{} - s := &Server{ - ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, - SlashingsPool: &slashingsmock.PoolMock{}, - Broadcaster: broadcaster, - } - - var body bytes.Buffer - _, err = body.WriteString(invalidAttesterSlashing) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.SubmitAttesterSlashing(writer, request) - require.Equal(t, http.StatusBadRequest, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusBadRequest, e.Code) - assert.StringContains(t, "Invalid attester slashing", e.Message) -} - -func TestSubmitProposerSlashing_Ok(t *testing.T) { - ctx := context.Background() - - transition.SkipSlotCache.Disable() - defer transition.SkipSlotCache.Enable() - - _, keys, err := util.DeterministicDepositsAndKeys(1) - require.NoError(t, err) - validator := ðpbv1alpha1.Validator{ - PublicKey: keys[0].PublicKey().Marshal(), - WithdrawableEpoch: primitives.Epoch(1), - } - bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error { - state.Validators = []*ethpbv1alpha1.Validator{validator} - return nil + } + + params.SetupTestConfigCleanup(t) + config := params.BeaconConfig() + config.AltairForkEpoch = 1 + params.OverrideBeaconConfig(config) + + bs, keys := util.DeterministicGenesisState(t, 1) + newBs := bs.Copy() + newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch) + require.NoError(t, err) + + for _, att := range []*ethpbv1alpha1.IndexedAttestationElectra{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{} + chainmock := &blockchainmock.ChainService{State: bs} + s := &Server{ + ChainInfoFetcher: chainmock, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + OperationNotifier: chainmock.OperationNotifier(), + } + + toSubmit := structs.AttesterSlashingsElectraFromConsensus([]*ethpbv1alpha1.AttesterSlashingElectra{slashing}) + b, err := json.Marshal(toSubmit[0]) + require.NoError(t, err) + var body bytes.Buffer + _, err = body.Write(b) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAttesterSlashingsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true) + require.Equal(t, 1, len(pendingSlashings)) + assert.DeepEqual(t, slashing, pendingSlashings[0]) + require.Equal(t, 1, broadcaster.NumMessages()) + assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) + _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashingElectra) + assert.Equal(t, true, ok) + }) }) - require.NoError(t, err) - - slashing := ðpbv1alpha1.ProposerSlashing{ - Header_1: ðpbv1alpha1.SignedBeaconBlockHeader{ - Header: ðpbv1alpha1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 0, - ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32), - StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32), - BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32), - }, - Signature: make([]byte, 96), - }, - Header_2: ðpbv1alpha1.SignedBeaconBlockHeader{ - Header: ðpbv1alpha1.BeaconBlockHeader{ - Slot: 1, - ProposerIndex: 0, - ParentRoot: bytesutil.PadTo([]byte("parentroot2"), 32), - StateRoot: bytesutil.PadTo([]byte("stateroot2"), 32), - BodyRoot: bytesutil.PadTo([]byte("bodyroot2"), 32), - }, - Signature: make([]byte, 96), - }, - } - - for _, h := range []*ethpbv1alpha1.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} { - sb, err := signing.ComputeDomainAndSign( - bs, - slots.ToEpoch(h.Header.Slot), - h.Header, - params.BeaconConfig().DomainBeaconProposer, - keys[0], - ) + t.Run("invalid-slashing", func(t *testing.T) { + bs, err := util.NewBeaconStateElectra() require.NoError(t, err) - sig, err := bls.SignatureFromBytes(sb) - require.NoError(t, err) - h.Signature = sig.Marshal() - } - broadcaster := &p2pMock.MockBroadcaster{} - chainmock := &blockchainmock.ChainService{State: bs} - s := &Server{ - ChainInfoFetcher: chainmock, - SlashingsPool: &slashingsmock.PoolMock{}, - Broadcaster: broadcaster, - OperationNotifier: chainmock.OperationNotifier(), - } - - toSubmit := structs.ProposerSlashingsFromConsensus([]*ethpbv1alpha1.ProposerSlashing{slashing}) - b, err := json.Marshal(toSubmit[0]) - require.NoError(t, err) - var body bytes.Buffer - _, err = body.Write(b) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/proposer_slashings", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.SubmitProposerSlashing(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, bs, true) - require.Equal(t, 1, len(pendingSlashings)) - assert.DeepEqual(t, slashing, pendingSlashings[0]) - assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) - require.Equal(t, 1, broadcaster.NumMessages()) - _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.ProposerSlashing) - assert.Equal(t, true, ok) -} - -func TestSubmitProposerSlashing_AcrossFork(t *testing.T) { - ctx := context.Background() - - transition.SkipSlotCache.Disable() - defer transition.SkipSlotCache.Enable() - - params.SetupTestConfigCleanup(t) - config := params.BeaconConfig() - config.AltairForkEpoch = 1 - params.OverrideBeaconConfig(config) - - bs, keys := util.DeterministicGenesisState(t, 1) - - slashing := ðpbv1alpha1.ProposerSlashing{ - Header_1: ðpbv1alpha1.SignedBeaconBlockHeader{ - Header: ðpbv1alpha1.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), - }, - Header_2: ðpbv1alpha1.SignedBeaconBlockHeader{ - Header: ðpbv1alpha1.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) + broadcaster := &p2pMock.MockBroadcaster{} + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + SlashingsPool: &slashingsmock.PoolMock{}, + Broadcaster: broadcaster, + } - for _, h := range []*ethpbv1alpha1.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} { - sb, err := signing.ComputeDomainAndSign( - newBs, - slots.ToEpoch(h.Header.Slot), - h.Header, - params.BeaconConfig().DomainBeaconProposer, - keys[0], - ) - require.NoError(t, err) - sig, err := bls.SignatureFromBytes(sb) + var body bytes.Buffer + _, err = body.WriteString(invalidAttesterSlashing) require.NoError(t, err) - h.Signature = sig.Marshal() - } - - broadcaster := &p2pMock.MockBroadcaster{} - chainmock := &blockchainmock.ChainService{State: bs} - s := &Server{ - ChainInfoFetcher: chainmock, - SlashingsPool: &slashingsmock.PoolMock{}, - Broadcaster: broadcaster, - OperationNotifier: chainmock.OperationNotifier(), - } - - toSubmit := structs.ProposerSlashingsFromConsensus([]*ethpbv1alpha1.ProposerSlashing{slashing}) - b, err := json.Marshal(toSubmit[0]) - require.NoError(t, err) - var body bytes.Buffer - _, err = body.Write(b) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/proposer_slashings", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.SubmitProposerSlashing(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, bs, true) - require.Equal(t, 1, len(pendingSlashings)) - assert.DeepEqual(t, slashing, pendingSlashings[0]) - assert.Equal(t, true, broadcaster.BroadcastCalled.Load()) - require.Equal(t, 1, broadcaster.NumMessages()) - _, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.ProposerSlashing) - assert.Equal(t, true, ok) + s.SubmitAttesterSlashingsV2(writer, request) + require.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.StringContains(t, "Invalid attester slashing", e.Message) + }) } func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) { diff --git a/testing/mock/beacon_service_mock.go b/testing/mock/beacon_service_mock.go index f5e506ba34d..49b8b66d46d 100644 --- a/testing/mock/beacon_service_mock.go +++ b/testing/mock/beacon_service_mock.go @@ -429,7 +429,7 @@ func (m *MockBeaconChainClient) SubmitAttesterSlashing(arg0 context.Context, arg for _, a := range arg2 { varargs = append(varargs, a) } - ret := m.ctrl.Call(m, "SubmitAttesterSlashing", varargs...) + ret := m.ctrl.Call(m, "SubmitAttesterSlashings", varargs...) ret0, _ := ret[0].(*eth.SubmitSlashingResponse) ret1, _ := ret[1].(error) return ret0, ret1 @@ -439,7 +439,7 @@ func (m *MockBeaconChainClient) SubmitAttesterSlashing(arg0 context.Context, arg func (mr *MockBeaconChainClientMockRecorder) SubmitAttesterSlashing(arg0, arg1 any, arg2 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]any{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitAttesterSlashing", reflect.TypeOf((*MockBeaconChainClient)(nil).SubmitAttesterSlashing), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitAttesterSlashings", reflect.TypeOf((*MockBeaconChainClient)(nil).SubmitAttesterSlashing), varargs...) } // SubmitAttesterSlashingElectra mocks base method.