Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GET /eth/v2/beacon/pool/attester_slashings #14479

Merged
merged 14 commits into from
Oct 16, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Updating to this release is recommended at your convenience.
- Add Electra support and tests for light client functions.
- fastssz version bump (better error messages).
- SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413)
- Added GetPoolAttesterSlashingsV2 endpoint.

### Changed

Expand Down
3 changes: 2 additions & 1 deletion api/server/structs/endpoints_beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ type BLSToExecutionChangesPoolResponse struct {
}

type GetAttesterSlashingsResponse struct {
Data []*AttesterSlashing `json:"data"`
Version string `json:"version,omitempty"`
Data json.RawMessage `json:"data"` // Accepts both `[]*AttesterSlashing` and `[]*AttesterSlashingElectra` types
}

type GetProposerSlashingsResponse struct {
Expand Down
9 changes: 9 additions & 0 deletions beacon-chain/rpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,15 @@ func (s *Service) beaconEndpoints(
handler: server.GetAttesterSlashings,
methods: []string{http.MethodGet},
},
{
template: "/eth/v2/beacon/pool/attester_slashings",
name: namespace + ".GetAttesterSlashingsV2",
middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.GetAttesterSlashingsV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/attester_slashings",
name: namespace + ".SubmitAttesterSlashings",
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/rpc/endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +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/v2/beacon/pool/attester_slashings": {http.MethodGet, 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},
Expand Down
64 changes: 55 additions & 9 deletions beacon-chain/rpc/eth/beacon/handlers_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,19 +468,65 @@ func (s *Server) GetAttesterSlashings(w http.ResponseWriter, r *http.Request) {
return
}
sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */)
ss := make([]*eth.AttesterSlashing, 0, len(sourceSlashings))
for _, slashing := range sourceSlashings {
s, ok := slashing.(*eth.AttesterSlashing)
if ok {
ss = append(ss, s)
} else {
httputil.HandleError(w, fmt.Sprintf("unable to convert slashing of type %T", slashing), http.StatusInternalServerError)
slashings := make([]*structs.AttesterSlashing, len(sourceSlashings))
for i, slashing := range sourceSlashings {
as, ok := slashing.(*eth.AttesterSlashing)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T", slashing), http.StatusInternalServerError)
return
}
slashings[i] = structs.AttesterSlashingFromConsensus(as)
}
slashings := structs.AttesterSlashingsFromConsensus(ss)
attBytes, err := json.Marshal(slashings)
if err != nil {
httputil.HandleError(w, fmt.Sprintf("Failed to marshal slashings: %v", err), http.StatusInternalServerError)
return
}
httputil.WriteJson(w, &structs.GetAttesterSlashingsResponse{Data: attBytes})
}

httputil.WriteJson(w, &structs.GetAttesterSlashingsResponse{Data: slashings})
// GetAttesterSlashingsV2 retrieves attester slashings known by the node but
// not necessarily incorporated into any block, supporting both AttesterSlashing and AttesterSlashingElectra.
func (s *Server) GetAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetAttesterSlashingsV2")
defer span.End()

headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
if err != nil {
httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError)
return
}
var attStructs []interface{}
sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */)
for _, slashing := range sourceSlashings {
if slashing.Version() >= version.Electra {
a, ok := slashing.(*eth.AttesterSlashingElectra)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert electra slashing of type %T to an Electra slashing", slashing), http.StatusInternalServerError)
return
}
attStruct := structs.AttesterSlashingElectraFromConsensus(a)
attStructs = append(attStructs, attStruct)
} else {
a, ok := slashing.(*eth.AttesterSlashing)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T to a Phase0 slashing", slashing), http.StatusInternalServerError)
return
}
attStruct := structs.AttesterSlashingFromConsensus(a)
attStructs = append(attStructs, attStruct)
}
}
attBytes, err := json.Marshal(attStructs)
if err != nil {
httputil.HandleError(w, fmt.Sprintf("Failed to marshal slashing: %v", err), http.StatusInternalServerError)
return
}
resp := &structs.GetAttesterSlashingsResponse{
Version: version.String(sourceSlashings[0].Version()),
Data: attBytes,
}
httputil.WriteJson(w, resp)
}

// SubmitAttesterSlashings submits an attester slashing object to node's pool and
Expand Down
179 changes: 161 additions & 18 deletions beacon-chain/rpc/eth/beacon/handlers_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,9 +985,7 @@ func TestSubmitSignedBLSToExecutionChanges_Failures(t *testing.T) {
}

func TestGetAttesterSlashings(t *testing.T) {
bs, err := util.NewBeaconState()
require.NoError(t, err)
slashing1 := &ethpbv1alpha1.AttesterSlashing{
slashing1PreElectra := &ethpbv1alpha1.AttesterSlashing{
Attestation_1: &ethpbv1alpha1.IndexedAttestation{
AttestingIndices: []uint64{1, 10},
Data: &ethpbv1alpha1.AttestationData{
Expand Down Expand Up @@ -1023,7 +1021,7 @@ func TestGetAttesterSlashings(t *testing.T) {
Signature: bytesutil.PadTo([]byte("signature2"), 96),
},
}
slashing2 := &ethpbv1alpha1.AttesterSlashing{
slashing2PreElectra := &ethpbv1alpha1.AttesterSlashing{
Attestation_1: &ethpbv1alpha1.IndexedAttestation{
AttestingIndices: []uint64{3, 30},
Data: &ethpbv1alpha1.AttestationData{
Expand Down Expand Up @@ -1059,23 +1057,168 @@ func TestGetAttesterSlashings(t *testing.T) {
Signature: bytesutil.PadTo([]byte("signature4"), 96),
},
}

s := &Server{
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1, slashing2}},
slashing1PostElectra := &ethpbv1alpha1.AttesterSlashingElectra{
Attestation_1: &ethpbv1alpha1.IndexedAttestationElectra{
AttestingIndices: []uint64{1, 10},
Data: &ethpbv1alpha1.AttestationData{
Slot: 1,
CommitteeIndex: 1,
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32),
Source: &ethpbv1alpha1.Checkpoint{
Epoch: 1,
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
},
Target: &ethpbv1alpha1.Checkpoint{
Epoch: 10,
Root: bytesutil.PadTo([]byte("targetroot1"), 32),
},
},
Signature: bytesutil.PadTo([]byte("signature1"), 96),
},
Attestation_2: &ethpbv1alpha1.IndexedAttestationElectra{
AttestingIndices: []uint64{2, 20},
Data: &ethpbv1alpha1.AttestationData{
Slot: 2,
CommitteeIndex: 2,
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32),
Source: &ethpbv1alpha1.Checkpoint{
Epoch: 2,
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
},
Target: &ethpbv1alpha1.Checkpoint{
Epoch: 20,
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
},
},
Signature: bytesutil.PadTo([]byte("signature2"), 96),
},
}
slashing2PostElectra := &ethpbv1alpha1.AttesterSlashingElectra{
Attestation_1: &ethpbv1alpha1.IndexedAttestationElectra{
AttestingIndices: []uint64{3, 30},
Data: &ethpbv1alpha1.AttestationData{
Slot: 3,
CommitteeIndex: 3,
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot3"), 32),
Source: &ethpbv1alpha1.Checkpoint{
Epoch: 3,
Root: bytesutil.PadTo([]byte("sourceroot3"), 32),
},
Target: &ethpbv1alpha1.Checkpoint{
Epoch: 30,
Root: bytesutil.PadTo([]byte("targetroot3"), 32),
},
},
Signature: bytesutil.PadTo([]byte("signature3"), 96),
},
Attestation_2: &ethpbv1alpha1.IndexedAttestationElectra{
AttestingIndices: []uint64{4, 40},
Data: &ethpbv1alpha1.AttestationData{
Slot: 4,
CommitteeIndex: 4,
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot4"), 32),
Source: &ethpbv1alpha1.Checkpoint{
Epoch: 4,
Root: bytesutil.PadTo([]byte("sourceroot4"), 32),
},
Target: &ethpbv1alpha1.Checkpoint{
Epoch: 40,
Root: bytesutil.PadTo([]byte("targetroot4"), 32),
},
},
Signature: bytesutil.PadTo([]byte("signature4"), 96),
},
}

request := httptest.NewRequest(http.MethodGet, "http://example.com/beacon/pool/attester_slashings", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
t.Run("V1", func(t *testing.T) {
bs, err := util.NewBeaconState()
require.NoError(t, err)

s.GetAttesterSlashings(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetAttesterSlashingsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
assert.Equal(t, 2, len(resp.Data))
s := &Server{
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PreElectra, slashing2PreElectra}},
}

request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/pool/attester_slashings", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetAttesterSlashings(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetAttesterSlashingsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)

var slashings []*structs.AttesterSlashing
require.NoError(t, json.Unmarshal(resp.Data, &slashings))

ss, err := structs.AttesterSlashingsToConsensus(slashings)
require.NoError(t, err)

require.DeepEqual(t, slashing1PreElectra, ss[0])
require.DeepEqual(t, slashing2PreElectra, ss[1])
})
t.Run("V2-post-electra", func(t *testing.T) {
bs, err := util.NewBeaconStateElectra()
require.NoError(t, err)

s := &Server{
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PostElectra, slashing2PostElectra}},
}

request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/beacon/pool/attester_slashings", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetAttesterSlashingsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetAttesterSlashingsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
assert.Equal(t, "electra", resp.Version)

// Unmarshal resp.Data into a slice of slashings
var slashings []*structs.AttesterSlashingElectra
require.NoError(t, json.Unmarshal(resp.Data, &slashings))

ss, err := structs.AttesterSlashingsElectraToConsensus(slashings)
require.NoError(t, err)

require.DeepEqual(t, slashing1PostElectra, ss[0])
require.DeepEqual(t, slashing2PostElectra, ss[1])
})
t.Run("V2-pre-electra", func(t *testing.T) {
bs, err := util.NewBeaconState()
require.NoError(t, err)

s := &Server{
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PreElectra, slashing2PreElectra}},
}

request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/pool/attester_slashings", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

s.GetAttesterSlashingsV2(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetAttesterSlashingsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)

var slashings []*structs.AttesterSlashing
require.NoError(t, json.Unmarshal(resp.Data, &slashings))

ss, err := structs.AttesterSlashingsToConsensus(slashings)
require.NoError(t, err)

require.DeepEqual(t, slashing1PreElectra, ss[0])
require.DeepEqual(t, slashing2PreElectra, ss[1])
})
}

func TestGetProposerSlashings(t *testing.T) {
Expand Down
Loading