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

fix: add workaround to fix fetching state from checkpointz #6874

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
Copy link
Member Author

@nflaig nflaig Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is anyways better imo, we should allow the user to override any header manually via request init

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
Loading