diff --git a/packages/api/src/utils/headers.ts b/packages/api/src/utils/headers.ts index 5ef5346612fa..5f3c6e3e1dfc 100644 --- a/packages/api/src/utils/headers.ts +++ b/packages/api/src/utils/headers.ts @@ -4,6 +4,12 @@ export enum HttpHeader { ContentType = "content-type", Accept = "accept", Authorization = "authorization", + /** + * Used to indicate which response headers should be made available to + * scripts running in the browser, in response to a cross-origin request. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers + */ + ExposeHeaders = "access-control-expose-headers", } export enum MediaType { diff --git a/packages/api/src/utils/metadata.ts b/packages/api/src/utils/metadata.ts index 8376113efec8..1eaa4132119f 100644 --- a/packages/api/src/utils/metadata.ts +++ b/packages/api/src/utils/metadata.ts @@ -5,6 +5,7 @@ import {StringType, ssz, stringType} from "@lodestar/types"; import {ResponseMetadataCodec} from "./types.js"; import {toBoolean} from "./serdes.js"; import {toForkName} from "./fork.js"; +import {HttpHeader} from "./headers.js"; export const VersionType = new ContainerType({ /** @@ -90,6 +91,7 @@ export const ExecutionOptimisticCodec: ResponseMetadataCodec ExecutionOptimisticType.fromJson(val), toHeadersObject: (val) => ({ [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + [HttpHeader.ExposeHeaders]: MetaHeader.ExecutionOptimistic, }), fromHeaders: (headers) => ({ executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), @@ -101,6 +103,7 @@ export const VersionCodec: ResponseMetadataCodec = { fromJson: (val) => VersionType.fromJson(val), toHeadersObject: (val) => ({ [MetaHeader.Version]: val.version, + [HttpHeader.ExposeHeaders]: MetaHeader.Version, }), fromHeaders: (headers) => ({ version: toForkName(headers.getRequired(MetaHeader.Version)), @@ -113,6 +116,7 @@ export const ExecutionOptimisticAndVersionCodec: ResponseMetadataCodec ({ [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), [MetaHeader.Version]: val.version, + [HttpHeader.ExposeHeaders]: [MetaHeader.ExecutionOptimistic, MetaHeader.Version].toString(), }), fromHeaders: (headers) => ({ executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), @@ -126,6 +130,7 @@ export const ExecutionOptimisticAndFinalizedCodec: ResponseMetadataCodec ({ [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), [MetaHeader.Finalized]: val.finalized.toString(), + [HttpHeader.ExposeHeaders]: [MetaHeader.ExecutionOptimistic, MetaHeader.Finalized].toString(), }), fromHeaders: (headers) => ({ executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), @@ -141,6 +146,7 @@ export const ExecutionOptimisticFinalizedAndVersionCodec: ResponseMetadataCodec< [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), [MetaHeader.Finalized]: val.finalized.toString(), [MetaHeader.Version]: val.version, + [HttpHeader.ExposeHeaders]: [MetaHeader.ExecutionOptimistic, MetaHeader.Finalized, MetaHeader.Version].toString(), }), fromHeaders: (headers) => ({ executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), @@ -156,6 +162,7 @@ export const ExecutionOptimisticAndDependentRootCodec: ResponseMetadataCodec ({ [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), [MetaHeader.DependentRoot]: val.dependentRoot, + [HttpHeader.ExposeHeaders]: [MetaHeader.ExecutionOptimistic, MetaHeader.DependentRoot].toString(), }), fromHeaders: (headers) => ({ executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), diff --git a/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts b/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts index 0f13575a5ec4..afb4863dbe44 100644 --- a/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts @@ -2,7 +2,7 @@ import {describe, it, beforeEach, afterEach, expect} from "vitest"; import bls from "@chainsafe/bls"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; -import {getClient, routes} from "@lodestar/api"; +import {getClient, HttpHeader, routes} from "@lodestar/api"; import {sleep} from "@lodestar/utils"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {Validator} from "@lodestar/validator"; @@ -102,6 +102,8 @@ describe("lightclient api", function () { expect(update.attestedHeader.beacon.slot).toBe(slot - 1); // version is set expect(res.meta().version).toBe(ForkName.altair); + // Ensure version header is made available to scripts running in the browser + expect(res.headers.get(HttpHeader.ExposeHeaders)?.includes("Eth-Consensus-Version")).toBe(true); }); it.skip("getLightClientFinalityUpdate()", async function () {