Skip to content

Commit

Permalink
fix: add workaround to fix fetching state from checkpointz (#6874)
Browse files Browse the repository at this point in the history
  • Loading branch information
nflaig committed Jun 10, 2024
1 parent ff253a7 commit 26e9bc4
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 29 deletions.
1 change: 1 addition & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * from "./beacon/index.js";
export {HttpStatusCode} from "./utils/httpStatusCode.js";
export {WireFormat} from "./utils/wireFormat.js";
export {HttpHeader, MediaType} from "./utils/headers.js";
export type {HttpErrorCodes, HttpSuccessCodes} from "./utils/httpStatusCode.js";
export {ApiResponse, HttpClient, FetchError, isFetchError, fetch, defaultInit} from "./utils/client/index.js";
export type {ApiRequestInit} from "./utils/client/request.js";
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/utils/client/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function createApiRequest<E extends Endpoint>(
args: E["args"],
init: ApiRequestInitRequired
): Request {
const headers = new Headers(init.headers);
const headers = new Headers();

let req: E["request"];

Expand Down Expand Up @@ -102,7 +102,7 @@ export function createApiRequest<E extends Endpoint>(
return new Request(url, {
...init,
method: definition.method,
headers: mergeHeaders(headers, req.headers),
headers: mergeHeaders(headers, req.headers, init.headers),
body: req.body as BodyInit,
});
}
39 changes: 20 additions & 19 deletions packages/api/src/utils/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,28 +90,29 @@ export function setAuthorizationHeader(url: URL, headers: Headers, {bearerToken}
}
}

export function mergeHeaders(a: HeadersInit | undefined, b: HeadersInit | undefined): Headers {
if (!a) {
return new Headers(b);
}
const headers = new Headers(a);
if (!b) {
return headers;
}
if (Array.isArray(b)) {
for (const [key, value] of b) {
headers.set(key, value);
}
} else if (b instanceof Headers) {
for (const [key, value] of b as unknown as Iterable<[string, string]>) {
headers.set(key, value);
export function mergeHeaders(...headersList: (HeadersInit | undefined)[]): Headers {
const mergedHeaders = new Headers();

for (const headers of headersList) {
if (!headers) {
continue;
}
} else {
for (const [key, value] of Object.entries(b)) {
headers.set(key, value);
if (Array.isArray(headers)) {
for (const [key, value] of headers) {
mergedHeaders.set(key, value);
}
} else if (headers instanceof Headers) {
for (const [key, value] of headers as unknown as Iterable<[string, string]>) {
mergedHeaders.set(key, value);
}
} else {
for (const [key, value] of Object.entries(headers)) {
mergedHeaders.set(key, value);
}
}
}
return headers;

return mergedHeaders;
}

/**
Expand Down
58 changes: 57 additions & 1 deletion packages/api/test/unit/utils/headers.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, it, expect} from "vitest";
import {MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader} from "../../../src/utils/headers.js";
import {MediaType, SUPPORTED_MEDIA_TYPES, mergeHeaders, parseAcceptHeader} from "../../../src/utils/headers.js";

describe("utils / headers", () => {
describe("parseAcceptHeader", () => {
Expand Down Expand Up @@ -32,4 +32,60 @@ describe("utils / headers", () => {
expect(parseAcceptHeader(header, SUPPORTED_MEDIA_TYPES)).toBe(expected);
});
});

describe("mergeHeaders", () => {
const testCases: {id: string; input: (HeadersInit | undefined)[]; expected: Headers}[] = [
{
id: "empty headers",
input: [{}, [], new Headers()],
expected: new Headers(),
},
{
id: "undefined headers",
input: [undefined, undefined],
expected: new Headers(),
},
{
id: "different headers",
input: [{a: "1"}, {b: "2"}],
expected: new Headers({a: "1", b: "2"}),
},
{
id: "override on single header",
input: [{a: "1"}, {b: "2"}, {a: "3"}],
expected: new Headers({a: "3", b: "2"}),
},
{
id: "multiple overrides on same header",
input: [{a: "1"}, {b: "2"}, {a: "3"}, {a: "4"}],
expected: new Headers({a: "4", b: "2"}),
},
{
id: "multiple overrides on different headers",
input: [{a: "1"}, {b: "2"}, {b: "3"}, {a: "4"}, {c: "5"}],
expected: new Headers({a: "4", b: "3", c: "5"}),
},
{
id: "headers from array into plain object",
input: [{a: "1"}, [["b", "2"]]],
expected: new Headers({a: "1", b: "2"}),
},
{
id: "headers from plain object into array",
input: [[["a", "1"]], {b: "2"}],
expected: new Headers({a: "1", b: "2"}),
},
{
id: "headers from all input types",
input: [[["a", "1"]], {b: "2"}, new Headers({c: "3"}), {d: "4"}],
expected: new Headers({a: "1", b: "2", c: "3", d: "4"}),
},
];

for (const {id, input, expected} of testCases) {
it(`should correctly merge ${id}`, () => {
expect(mergeHeaders(...input)).toEqual(expected);
});
}
});
});
27 changes: 20 additions & 7 deletions packages/cli/src/networks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import fs from "node:fs";
import got from "got";
import {ENR} from "@chainsafe/enr";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {WireFormat, getClient} from "@lodestar/api";
import {HttpHeader, MediaType, WireFormat, getClient} from "@lodestar/api";
import {getStateTypeFromBytes} from "@lodestar/beacon-node";
import {ChainConfig, ChainForkConfig} from "@lodestar/config";
import {Checkpoint} from "@lodestar/types/phase0";
import {Slot, ssz} from "@lodestar/types";
import {Slot} from "@lodestar/types";
import {fromHex, callFnWhenAwait, Logger} from "@lodestar/utils";
import {BeaconStateAllForks, getLatestBlockRoot, computeCheckpointEpochAtStateSlot} from "@lodestar/state-transition";
import {parseBootnodesFile} from "../util/format.js";
Expand Down Expand Up @@ -156,18 +157,30 @@ export async function fetchWeakSubjectivityState(
}

// getStateV2 should be available for all forks including phase0
const getStatePromise = api.debug.getStateV2({stateId}, {responseWireFormat: WireFormat.ssz});

const {stateBytes, fork} = await callFnWhenAwait(
const getStatePromise = api.debug.getStateV2(
{stateId},
{
responseWireFormat: WireFormat.ssz,
headers: {
// Set Accept header explicitly to fix Checkpointz incompatibility
// See https://github.com/ethpandaops/checkpointz/issues/165
[HttpHeader.Accept]: MediaType.ssz,
},
}
);

const stateBytes = await callFnWhenAwait(
getStatePromise,
() => logger.info("Download in progress, please wait..."),
GET_STATE_LOG_INTERVAL
).then((res) => {
return {stateBytes: res.ssz(), fork: res.meta().version};
return res.ssz();
});

logger.info("Download completed", {stateId});
const wsState = ssz.allForks[fork].BeaconState.deserializeToViewDU(stateBytes);
// It should not be required to get fork type from bytes but Checkpointz does not return
// Eth-Consensus-Version header, see https://github.com/ethpandaops/checkpointz/issues/164
const wsState = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes);

return {
wsState,
Expand Down

0 comments on commit 26e9bc4

Please sign in to comment.