From 57cc4950c007acfad465d3e06fbbe383f2f1579b Mon Sep 17 00:00:00 2001 From: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:19:26 +0200 Subject: [PATCH] Add `/eth/v2/beacon/blocks/{block_id}/attestations` (#14478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add endpoint * changelog * fix response * improvement * fix test * James' review * fix test * fix version check * add test for non electra V2 * Review items * James' review * Radek' review * Update CHANGELOG.md --------- Co-authored-by: RadosÅ‚aw Kapka --- CHANGELOG.md | 14 +- api/server/structs/endpoints_beacon.go | 7 + beacon-chain/rpc/endpoints.go | 9 + beacon-chain/rpc/endpoints_test.go | 1 + beacon-chain/rpc/eth/beacon/handlers.go | 88 +++- beacon-chain/rpc/eth/beacon/handlers_test.go | 399 ++++++++++++++----- 6 files changed, 391 insertions(+), 127 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ae66962580..8403ad5395da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,16 +16,16 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Light client support: Implement `BlockToLightClientHeader` function. - Light client support: Consensus types. - GetBeaconStateV2: add Electra case. -- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875) -- Tests to ensure sepolia config matches the official upstream yaml -- HTTP endpoint for PublishBlobs +- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875). +- Tests to ensure sepolia config matches the official upstream yaml. +- HTTP endpoint for PublishBlobs. - GetBlockV2, GetBlindedBlock, ProduceBlockV2, ProduceBlockV3: add Electra case. -- Add Electra support and tests for light client functions +- 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) -- Add Bellatrix tests for light client functions -- Add Discovery Rebooter Feature - +- Add Bellatrix tests for light client functions. +- Add Discovery Rebooter Feature. +- Added GetBlockAttestationsV2 endpoint. ### Changed diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index 3fa8a4bc8f47..9073dbc6456f 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -133,6 +133,13 @@ type GetBlockAttestationsResponse struct { Data []*Attestation `json:"data"` } +type GetBlockAttestationsV2Response struct { + Version string `json:"version"` + ExecutionOptimistic bool `json:"execution_optimistic"` + Finalized bool `json:"finalized"` + Data json.RawMessage `json:"data"` // Accepts both `Attestation` and `AttestationElectra` types +} + type GetStateRootResponse struct { ExecutionOptimistic bool `json:"execution_optimistic"` Finalized bool `json:"finalized"` diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index ff53041cc79f..09358123e6f5 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -585,6 +585,15 @@ func (s *Service) beaconEndpoints( handler: server.GetBlockAttestations, methods: []string{http.MethodGet}, }, + { + template: "/eth/v2/beacon/blocks/{block_id}/attestations", + name: namespace + ".GetBlockAttestationsV2", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.GetBlockAttestations, + methods: []string{http.MethodGet}, + }, { template: "/eth/v1/beacon/blinded_blocks/{block_id}", name: namespace + ".GetBlindedBlock", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index ad26b9ba2e68..b2f286cb5fb0 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -36,6 +36,7 @@ func Test_endpoints(t *testing.T) { "/eth/v2/beacon/blocks/{block_id}": {http.MethodGet}, "/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet}, "/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet}, + "/eth/v2/beacon/blocks/{block_id}/attestations": {http.MethodGet}, "/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet}, "/eth/v1/beacon/deposit_snapshot": {http.MethodGet}, "/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet}, diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go index e5167752473b..33eeb9dbae24 100644 --- a/beacon-chain/rpc/eth/beacon/handlers.go +++ b/beacon-chain/rpc/eth/beacon/handlers.go @@ -200,16 +200,10 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) { ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestations") defer span.End() - blockId := r.PathValue("block_id") - if blockId == "" { - httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest) - return - } - blk, err := s.Blocker.Block(ctx, []byte(blockId)) - if !shared.WriteBlockFetchError(w, blk, err) { + blk, isOptimistic, root := s.blockData(ctx, w, r) + if blk == nil { return } - consensusAtts := blk.Block().Body().Attestations() atts := make([]*structs.Attestation, len(consensusAtts)) for i, att := range consensusAtts { @@ -221,25 +215,87 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) { return } } - root, err := blk.Block().HashTreeRoot() - if err != nil { - httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError) + resp := &structs.GetBlockAttestationsResponse{ + Data: atts, + ExecutionOptimistic: isOptimistic, + Finalized: s.FinalizationFetcher.IsFinalized(ctx, root), + } + httputil.WriteJson(w, resp) +} + +// GetBlockAttestationsV2 retrieves attestation included in requested block. +func (s *Server) GetBlockAttestationsV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestationsV2") + defer span.End() + + blk, isOptimistic, root := s.blockData(ctx, w, r) + if blk == nil { return } - isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root) + consensusAtts := blk.Block().Body().Attestations() + + v := blk.Block().Version() + var attStructs []interface{} + if v >= version.Electra { + for _, att := range consensusAtts { + a, ok := att.(*eth.AttestationElectra) + if !ok { + httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestations electra of type %T", att), http.StatusInternalServerError) + return + } + attStruct := structs.AttElectraFromConsensus(a) + attStructs = append(attStructs, attStruct) + } + } else { + for _, att := range consensusAtts { + a, ok := att.(*eth.Attestation) + if !ok { + httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestation of type %T", att), http.StatusInternalServerError) + return + } + attStruct := structs.AttFromConsensus(a) + attStructs = append(attStructs, attStruct) + } + } + + attBytes, err := json.Marshal(attStructs) if err != nil { - httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, fmt.Sprintf("failed to marshal attestations: %v", err), http.StatusInternalServerError) return } - - resp := &structs.GetBlockAttestationsResponse{ - Data: atts, + resp := &structs.GetBlockAttestationsV2Response{ + Version: version.String(v), ExecutionOptimistic: isOptimistic, Finalized: s.FinalizationFetcher.IsFinalized(ctx, root), + Data: attBytes, } httputil.WriteJson(w, resp) } +func (s *Server) blockData(ctx context.Context, w http.ResponseWriter, r *http.Request) (interfaces.ReadOnlySignedBeaconBlock, bool, [32]byte) { + blockId := r.PathValue("block_id") + if blockId == "" { + httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest) + return nil, false, [32]byte{} + } + blk, err := s.Blocker.Block(ctx, []byte(blockId)) + if !shared.WriteBlockFetchError(w, blk, err) { + return nil, false, [32]byte{} + } + + root, err := blk.Block().HashTreeRoot() + if err != nil { + httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError) + return nil, false, [32]byte{} + } + isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root) + if err != nil { + httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError) + return nil, false, [32]byte{} + } + return blk, isOptimistic, root +} + // PublishBlindedBlock instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct // and publish a SignedBeaconBlock by swapping out the transactions_root for the corresponding full list of `transactions`. // The beacon node should broadcast a newly constructed SignedBeaconBlock to the beacon network, to be included in the diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index fb96b950044c..b0d61ba3020c 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -519,137 +519,141 @@ func TestGetBlockSSZV2(t *testing.T) { } func TestGetBlockAttestations(t *testing.T) { - t.Run("ok", func(t *testing.T) { - b := util.NewBeaconBlock() - b.Block.Body.Attestations = []*eth.Attestation{ - { - AggregationBits: bitfield.Bitlist{0x00}, - Data: ð.AttestationData{ - Slot: 123, - CommitteeIndex: 123, - BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), - Source: ð.Checkpoint{ - Epoch: 123, - Root: bytesutil.PadTo([]byte("root1"), 32), - }, - Target: ð.Checkpoint{ - Epoch: 123, - Root: bytesutil.PadTo([]byte("root1"), 32), - }, + preElectraAtts := []*eth.Attestation{ + { + AggregationBits: bitfield.Bitlist{0x00}, + Data: ð.AttestationData{ + Slot: 123, + CommitteeIndex: 123, + BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), + Source: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), }, - Signature: bytesutil.PadTo([]byte("sig1"), 96), }, - { - AggregationBits: bitfield.Bitlist{0x01}, - Data: ð.AttestationData{ - Slot: 456, - CommitteeIndex: 456, - BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), - Source: ð.Checkpoint{ - Epoch: 456, - Root: bytesutil.PadTo([]byte("root2"), 32), - }, - Target: ð.Checkpoint{ - Epoch: 456, - Root: bytesutil.PadTo([]byte("root2"), 32), - }, + Signature: bytesutil.PadTo([]byte("sig1"), 96), + }, + { + AggregationBits: bitfield.Bitlist{0x01}, + Data: ð.AttestationData{ + Slot: 456, + CommitteeIndex: 456, + BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), + Source: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), }, - Signature: bytesutil.PadTo([]byte("sig2"), 96), }, - } - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} - mockChainService := &chainMock.ChainService{ - FinalizedRoots: map[[32]byte]bool{}, - } - s := &Server{ - OptimisticModeFetcher: mockChainService, - FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, - } + Signature: bytesutil.PadTo([]byte("sig2"), 96), + }, + } + electraAtts := []*eth.AttestationElectra{ + { + AggregationBits: bitfield.Bitlist{0x00}, + Data: ð.AttestationData{ + Slot: 123, + CommitteeIndex: 123, + BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32), + Source: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 123, + Root: bytesutil.PadTo([]byte("root1"), 32), + }, + }, + Signature: bytesutil.PadTo([]byte("sig1"), 96), + CommitteeBits: primitives.NewAttestationCommitteeBits(), + }, + { + AggregationBits: bitfield.Bitlist{0x01}, + Data: ð.AttestationData{ + Slot: 456, + CommitteeIndex: 456, + BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32), + Source: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + Target: ð.Checkpoint{ + Epoch: 456, + Root: bytesutil.PadTo([]byte("root2"), 32), + }, + }, + Signature: bytesutil.PadTo([]byte("sig2"), 96), + CommitteeBits: primitives.NewAttestationCommitteeBits(), + }, + } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) - request.SetPathValue("block_id", "head") - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + b := util.NewBeaconBlock() + b.Block.Body.Attestations = preElectraAtts + sb, err := blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) - s.GetBlockAttestations(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - resp := &structs.GetBlockAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data)) - atts := make([]*eth.Attestation, len(b.Block.Body.Attestations)) - for i, a := range resp.Data { - atts[i], err = a.ToConsensus() - require.NoError(t, err) - } - assert.DeepEqual(t, b.Block.Body.Attestations, atts) - }) - t.Run("execution optimistic", func(t *testing.T) { - b := util.NewBeaconBlockBellatrix() - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - r, err := sb.Block().HashTreeRoot() - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} - mockChainService := &chainMock.ChainService{ - OptimisticRoots: map[[32]byte]bool{r: true}, - FinalizedRoots: map[[32]byte]bool{}, - } - s := &Server{ - OptimisticModeFetcher: mockChainService, - FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, - } + bb := util.NewBeaconBlockBellatrix() + bb.Block.Body.Attestations = preElectraAtts + bsb, err := blocks.NewSignedBeaconBlock(bb) + require.NoError(t, err) - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) - request.SetPathValue("block_id", "head") - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + eb := util.NewBeaconBlockElectra() + eb.Block.Body.Attestations = electraAtts + esb, err := blocks.NewSignedBeaconBlock(eb) + require.NoError(t, err) - s.GetBlockAttestations(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - resp := &structs.GetBlockAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, true, resp.ExecutionOptimistic) - }) - t.Run("finalized", func(t *testing.T) { - b := util.NewBeaconBlock() - sb, err := blocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - r, err := sb.Block().HashTreeRoot() - require.NoError(t, err) - mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb} + t.Run("v1", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } - t.Run("true", func(t *testing.T) { - mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} s := &Server{ OptimisticModeFetcher: mockChainService, FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) request.SetPathValue("block_id", "head") writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetBlockAttestations(writer, request) require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, true, resp.Finalized) + require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data)) + + atts := make([]*eth.Attestation, len(b.Block.Body.Attestations)) + for i, a := range resp.Data { + atts[i], err = a.ToConsensus() + require.NoError(t, err) + } + assert.DeepEqual(t, b.Block.Body.Attestations, atts) }) - t.Run("false", func(t *testing.T) { - mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + t.Run("execution-optimistic", func(t *testing.T) { + r, err := bsb.Block().HashTreeRoot() + require.NoError(t, err) + mockChainService := &chainMock.ChainService{ + OptimisticRoots: map[[32]byte]bool{r: true}, + FinalizedRoots: map[[32]byte]bool{}, + } s := &Server{ OptimisticModeFetcher: mockChainService, FinalizationFetcher: mockChainService, - Blocker: mockBlockFetcher, + Blocker: &testutil.MockBlocker{BlockToReturn: bsb}, } - request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) request.SetPathValue("block_id", "head") writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -658,7 +662,194 @@ func TestGetBlockAttestations(t *testing.T) { require.Equal(t, http.StatusOK, writer.Code) resp := &structs.GetBlockAttestationsResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - assert.Equal(t, false, resp.ExecutionOptimistic) + assert.Equal(t, true, resp.ExecutionOptimistic) + }) + t.Run("finalized", func(t *testing.T) { + r, err := sb.Block().HashTreeRoot() + require.NoError(t, err) + + t.Run("true", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestations(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.Finalized) + }) + t.Run("false", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestations(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, false, resp.ExecutionOptimistic) + }) + }) + }) + + t.Run("V2", func(t *testing.T) { + t.Run("ok-pre-electra", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } + + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + + var attStructs []structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attStructs)) + + atts := make([]*eth.Attestation, len(attStructs)) + for i, attStruct := range attStructs { + atts[i], err = attStruct.ToConsensus() + require.NoError(t, err) + } + + assert.DeepEqual(t, b.Block.Body.Attestations, atts) + assert.Equal(t, "phase0", resp.Version) + }) + t.Run("ok-post-electra", func(t *testing.T) { + mockChainService := &chainMock.ChainService{ + FinalizedRoots: map[[32]byte]bool{}, + } + + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: esb}, + } + + mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: esb} + s.Blocker = mockBlockFetcher + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + + var attStructs []structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attStructs)) + + atts := make([]*eth.AttestationElectra, len(attStructs)) + for i, attStruct := range attStructs { + atts[i], err = attStruct.ToConsensus() + require.NoError(t, err) + } + + assert.DeepEqual(t, eb.Block.Body.Attestations, atts) + assert.Equal(t, "electra", resp.Version) + }) + t.Run("execution-optimistic", func(t *testing.T) { + r, err := bsb.Block().HashTreeRoot() + require.NoError(t, err) + mockChainService := &chainMock.ChainService{ + OptimisticRoots: map[[32]byte]bool{r: true}, + FinalizedRoots: map[[32]byte]bool{}, + } + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: bsb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.ExecutionOptimistic) + assert.Equal(t, "bellatrix", resp.Version) + }) + t.Run("finalized", func(t *testing.T) { + r, err := sb.Block().HashTreeRoot() + require.NoError(t, err) + + t.Run("true", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, true, resp.Finalized) + assert.Equal(t, "phase0", resp.Version) + }) + t.Run("false", func(t *testing.T) { + mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}} + s := &Server{ + OptimisticModeFetcher: mockChainService, + FinalizationFetcher: mockChainService, + Blocker: &testutil.MockBlocker{BlockToReturn: sb}, + } + + request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil) + request.SetPathValue("block_id", "head") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetBlockAttestationsV2(writer, request) + require.Equal(t, http.StatusOK, writer.Code) + resp := &structs.GetBlockAttestationsV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + assert.Equal(t, false, resp.ExecutionOptimistic) + assert.Equal(t, "phase0", resp.Version) + }) }) }) }