diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index 4cfcce8416ec..de1c9d780299 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -635,6 +635,8 @@ func (s *Service) retrievePayloadFromExecutionHash(ctx context.Context, executio return fullPayloadFromPayloadBody(header, bdy, version) } +// This method assumes that the provided execution hashes are all valid and part of the +// canonical chain. func (s *Service) retrievePayloadsFromExecutionHashes( ctx context.Context, executionHashes []common.Hash, @@ -649,6 +651,10 @@ func (s *Service) retrievePayloadsFromExecutionHashes( return nil, fmt.Errorf("could not fetch payload bodies by hash %#x: %v", executionHashes, err) } + if len(payloadBodies) != len(executionHashes) { + return nil, fmt.Errorf("mismatch of payloads retrieved from the execution client: %d vs %d", len(payloadBodies), len(executionHashes)) + } + // For each valid payload, we reconstruct the full block from it with the // blinded block. for sliceIdx, realIdx := range validExecPayloads { diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 9a7002312edf..ad0a09dfab0c 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -912,6 +912,73 @@ func TestReconstructFullBellatrixBlockBatch(t *testing.T) { require.NoError(t, err) require.DeepEqual(t, payload, got.Proto()) }) + t.Run("handles invalid response from EL", func(t *testing.T) { + fix := fixtures() + payload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload) + require.Equal(t, true, ok) + + jsonPayload := make(map[string]interface{}) + tx := gethtypes.NewTransaction( + 0, + common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + big.NewInt(0), 0, big.NewInt(0), + nil, + ) + txs := []*gethtypes.Transaction{tx} + encodedBinaryTxs := make([][]byte, 1) + var err error + encodedBinaryTxs[0], err = txs[0].MarshalBinary() + require.NoError(t, err) + payload.Transactions = encodedBinaryTxs + jsonPayload["transactions"] = []hexutil.Bytes{encodedBinaryTxs[0]} + + wrappedPayload, err := blocks.WrappedExecutionPayload(payload) + require.NoError(t, err) + header, err := blocks.PayloadToHeader(wrappedPayload) + require.NoError(t, err) + + bellatrixBlock := util.NewBlindedBeaconBlockBellatrix() + wanted := util.NewBeaconBlockBellatrix() + wanted.Block.Slot = 1 + // Make sure block hash is the zero hash. + bellatrixBlock.Block.Body.ExecutionPayloadHeader.BlockHash = make([]byte, 32) + bellatrixBlock.Block.Slot = 1 + wrappedEmpty, err := blocks.NewSignedBeaconBlock(bellatrixBlock) + require.NoError(t, err) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + defer func() { + require.NoError(t, r.Body.Close()) + }() + + respJSON := map[string]interface{}{ + "jsonrpc": "2.0", + "id": 1, + "result": []map[string]interface{}{}, + } + require.NoError(t, json.NewEncoder(w).Encode(respJSON)) + + })) + defer srv.Close() + + rpcClient, err := rpc.DialHTTP(srv.URL) + require.NoError(t, err) + defer rpcClient.Close() + + service := &Service{} + service.rpcClient = rpcClient + blindedBlock := util.NewBlindedBeaconBlockBellatrix() + + blindedBlock.Block.Body.ExecutionPayloadHeader = header + wrapped, err := blocks.NewSignedBeaconBlock(blindedBlock) + require.NoError(t, err) + copiedWrapped, err := wrapped.Copy() + require.NoError(t, err) + + _, err = service.ReconstructFullBellatrixBlockBatch(ctx, []interfaces.ReadOnlySignedBeaconBlock{wrappedEmpty, wrapped, copiedWrapped}) + require.ErrorContains(t, "mismatch of payloads retrieved from the execution client", err) + }) } func TestServer_getPowBlockHashAtTerminalTotalDifficulty(t *testing.T) {