Skip to content

Commit

Permalink
Merge branch 'unstable' into nflaig/no-unused-expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
nflaig committed Feb 22, 2024
2 parents 9730efc + 8959bda commit e8e091a
Show file tree
Hide file tree
Showing 160 changed files with 607 additions and 599 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
],
"npmClient": "yarn",
"useNx": true,
"version": "1.15.1",
"version": "1.16.0",
"stream": true,
"command": {
"version": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"build": "lerna run build",
"build:watch": "lerna exec --parallel -- 'yarn run build:watch'",
"build:ifchanged": "lerna exec -- ../../scripts/build_if_changed.sh",
"lint": "eslint --color --ext .ts packages/*/src packages/*/test",
"lint": "eslint --report-unused-disable-directives --color --ext .ts packages/*/src packages/*/test",
"lint:fix": "yarn lint --fix",
"lint-dashboards": "node scripts/lint-grafana-dashboards.mjs ./dashboards",
"check-build": "lerna run check-build",
Expand Down
10 changes: 5 additions & 5 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.15.1",
"version": "1.16.0",
"type": "module",
"exports": {
".": {
Expand Down Expand Up @@ -69,10 +69,10 @@
"dependencies": {
"@chainsafe/persistent-merkle-tree": "^0.6.1",
"@chainsafe/ssz": "^0.14.0",
"@lodestar/config": "^1.15.1",
"@lodestar/params": "^1.15.1",
"@lodestar/types": "^1.15.1",
"@lodestar/utils": "^1.15.1",
"@lodestar/config": "^1.16.0",
"@lodestar/params": "^1.16.0",
"@lodestar/types": "^1.16.0",
"@lodestar/utils": "^1.16.0",
"eventsource": "^2.0.2",
"qs": "^6.11.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/client/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function getClient(config: ChainForkConfig, httpClient: IHttpClient): Api
const reqSerializers = getReqSerializers(config);
const returnTypes = getReturnTypes();
// Some routes return JSON, use a client auto-generator
const client = generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
const client = generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient) as Api;
const fetchOptsSerializer = getFetchOptsSerializers<Api, ReqTypes>(routesData, reqSerializers);

return {
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/builder/client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {ChainForkConfig} from "@lodestar/config";
import {IHttpClient, generateGenericJsonClient} from "../utils/client/index.js";
import {IHttpClient, generateGenericJsonClient, ApiWithExtraOpts} from "../utils/client/index.js";
import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "./routes.js";

/**
* REST HTTP client for builder routes
*/
export function getClient(config: ChainForkConfig, httpClient: IHttpClient): Api {
export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiWithExtraOpts<Api> {
const reqSerializers = getReqSerializers(config);
const returnTypes = getReturnTypes();
// All routes return JSON, use a client auto-generator
Expand Down
13 changes: 9 additions & 4 deletions packages/api/src/builder/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {ChainForkConfig} from "@lodestar/config";
import {HttpClient, HttpClientModules, HttpClientOptions, IHttpClient} from "../utils/client/httpClient.js";
import {Api} from "./routes.js";
import {
HttpClient,
HttpClientModules,
HttpClientOptions,
IHttpClient,
ApiWithExtraOpts,
} from "../utils/client/index.js";
import {Api as BuilderApi} from "../builder/routes.js";
import * as builder from "./client.js";

// NOTE: Don't export server here so it's not bundled to all consumers

export type {Api};

// Note: build API does not have namespaces as routes are declared at the "root" namespace

export type Api = ApiWithExtraOpts<BuilderApi>;
type ClientModules = HttpClientModules & {
config: ChainForkConfig;
httpClient?: IHttpClient;
Expand Down
37 changes: 30 additions & 7 deletions packages/api/src/utils/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ import {APIClientHandler} from "../../interfaces.js";
import {FetchOpts, HttpError, IHttpClient} from "./httpClient.js";
import {HttpStatusCode} from "./httpStatusCode.js";

// See /packages/api/src/routes/index.ts for reasoning

/* eslint-disable @typescript-eslint/no-explicit-any */

type ExtraOpts = {retries?: number};
type ParametersWithOptionalExtraOpts<T extends (...args: any) => any> = [...Parameters<T>, ExtraOpts] | Parameters<T>;

export type ApiWithExtraOpts<T extends Record<string, APIClientHandler>> = {
[K in keyof T]: (...args: ParametersWithOptionalExtraOpts<T[K]>) => ReturnType<T[K]>;
};

// See /packages/api/src/routes/index.ts for reasoning

/**
* Format FetchFn opts from Fn arguments given a route definition and request serializer.
* For routes that return only JSOn use @see getGenericJsonClient
Expand Down Expand Up @@ -58,22 +65,38 @@ export function generateGenericJsonClient<
reqSerializers: ReqSerializers<Api, ReqTypes>,
returnTypes: ReturnTypes<Api>,
fetchFn: IHttpClient
): Api {
): ApiWithExtraOpts<Api> {
return mapValues(routesData, (routeDef, routeId) => {
const fetchOptsSerializer = getFetchOptsSerializer(routeDef, reqSerializers[routeId], routeId as string);
const returnType = returnTypes[routeId as keyof ReturnTypes<Api>] as TypeJson<any> | null;

return async function request(...args: Parameters<Api[keyof Api]>): Promise<ReturnType<Api[keyof Api]>> {
return async function request(
...args: ParametersWithOptionalExtraOpts<Api[keyof Api]>
): Promise<ReturnType<Api[keyof Api]>> {
try {
// extract the extraOpts if provided
//
const argLen = (args as any[])?.length ?? 0;
const lastArg = (args as any[])[argLen] as ExtraOpts | undefined;
const retries = lastArg?.retries;
const extraOpts = {retries};

if (returnType) {
const res = await fetchFn.json<unknown>(fetchOptsSerializer(...args));
// open extraOpts first if some serializer wants to add some overriding param
const res = await fetchFn.json<unknown>({
...extraOpts,
...fetchOptsSerializer(...(args as Parameters<Api[keyof Api]>)),
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/return-await
return {ok: true, response: returnType.fromJson(res.body), status: res.status} as ReturnType<Api[keyof Api]>;
} else {
// We need to avoid parsing the response as the servers might just
// response status 200 and close the request instead of writing an
// empty json response. We return the status code.
const res = await fetchFn.request(fetchOptsSerializer(...args));
const res = await fetchFn.request({
...extraOpts,
...fetchOptsSerializer(...(args as Parameters<Api[keyof Api]>)),
});

// eslint-disable-next-line @typescript-eslint/return-await
return {ok: true, response: undefined, status: res.status} as ReturnType<Api[keyof Api]>;
Expand All @@ -98,5 +121,5 @@ export function generateGenericJsonClient<
throw err;
}
};
}) as unknown as Api;
}) as unknown as ApiWithExtraOpts<Api>;
}
27 changes: 26 additions & 1 deletion packages/api/src/utils/client/httpClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ErrorAborted, Logger, TimeoutError, isValidHttpUrl, toBase64} from "@lodestar/utils";
import {ErrorAborted, Logger, TimeoutError, isValidHttpUrl, toBase64, retry} from "@lodestar/utils";
import {ReqGeneric, RouteDef} from "../index.js";
import {ApiClientResponse, ApiClientSuccessResponse} from "../../interfaces.js";
import {fetch, isFetchError} from "./fetch.js";
Expand Down Expand Up @@ -70,6 +70,7 @@ export type FetchOpts = {
/** Optional, for metrics */
routeId?: string;
timeoutMs?: number;
retries?: number;
};

export interface IHttpClient {
Expand Down Expand Up @@ -181,6 +182,30 @@ export class HttpClient implements IHttpClient {
private async requestWithBodyWithRetries<T>(
opts: FetchOpts,
getBody: (res: Response) => Promise<T>
): Promise<{status: HttpStatusCode; body: T}> {
if (opts.retries !== undefined) {
const routeId = opts.routeId ?? DEFAULT_ROUTE_ID;

return retry(
async (_attempt) => {
return this.requestWithBodyWithFallbacks<T>(opts, getBody);
},
{
retries: opts.retries,
retryDelay: 200,
onRetry: (e, attempt) => {
this.logger?.debug("Retrying request", {routeId, attempt, lastError: e.message});
},
}
);
} else {
return this.requestWithBodyWithFallbacks<T>(opts, getBody);
}
}

private async requestWithBodyWithFallbacks<T>(
opts: FetchOpts,
getBody: (res: Response) => Promise<T>
): Promise<{status: HttpStatusCode; body: T}> {
// Early return when no fallback URLs are setup
if (this.urlsOpts.length === 1) {
Expand Down
2 changes: 0 additions & 2 deletions packages/api/src/utils/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import type {FastifyInstance, FastifyContextConfig} from "fastify";
// eslint-disable-next-line import/no-extraneous-dependencies
import type * as fastify from "fastify";
import {ReqGeneric} from "../types.js";

Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/unit/beacon/testData/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {GenericServerTestCases} from "../../../utils/genericServerTest.js";

const abortController = new AbortController();

/* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/naming-convention */

export const testData: GenericServerTestCases<Api> = {
eventstream: {
Expand Down
4 changes: 2 additions & 2 deletions packages/api/test/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {MockedObject, vi} from "vitest";
import qs from "qs";
import {parse as parseQueryString} from "qs";
import {FastifyInstance, fastify} from "fastify";
import {mapValues} from "@lodestar/utils";
import {ServerApi} from "../../src/interfaces.js";

export function getTestServer(): {server: FastifyInstance; start: () => Promise<string>} {
const server = fastify({
ajv: {customOptions: {coerceTypes: "array"}},
querystringParser: (str) => qs.parse(str, {comma: true, parseArrays: false}),
querystringParser: (str) => parseQueryString(str, {comma: true, parseArrays: false}),
});

server.addHook("onError", (request, reply, error, done) => {
Expand Down
28 changes: 14 additions & 14 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.15.1",
"version": "1.16.0",
"type": "module",
"exports": {
".": {
Expand Down Expand Up @@ -100,8 +100,8 @@
"@chainsafe/discv5": "^9.0.0",
"@chainsafe/enr": "^3.0.0",
"@chainsafe/libp2p-gossipsub": "^11.2.1",
"@chainsafe/libp2p-noise": "^14.1.0",
"@chainsafe/libp2p-identify": "^1.0.0",
"@chainsafe/libp2p-noise": "^14.1.0",
"@chainsafe/persistent-merkle-tree": "^0.6.1",
"@chainsafe/prometheus-gc-stats": "^1.0.0",
"@chainsafe/ssz": "^0.14.0",
Expand All @@ -120,18 +120,18 @@
"@libp2p/peer-id-factory": "^4.0.3",
"@libp2p/prometheus-metrics": "^3.0.10",
"@libp2p/tcp": "9.0.10",
"@lodestar/api": "^1.15.1",
"@lodestar/config": "^1.15.1",
"@lodestar/db": "^1.15.1",
"@lodestar/fork-choice": "^1.15.1",
"@lodestar/light-client": "^1.15.1",
"@lodestar/logger": "^1.15.1",
"@lodestar/params": "^1.15.1",
"@lodestar/reqresp": "^1.15.1",
"@lodestar/state-transition": "^1.15.1",
"@lodestar/types": "^1.15.1",
"@lodestar/utils": "^1.15.1",
"@lodestar/validator": "^1.15.1",
"@lodestar/api": "^1.16.0",
"@lodestar/config": "^1.16.0",
"@lodestar/db": "^1.16.0",
"@lodestar/fork-choice": "^1.16.0",
"@lodestar/light-client": "^1.16.0",
"@lodestar/logger": "^1.16.0",
"@lodestar/params": "^1.16.0",
"@lodestar/reqresp": "^1.16.0",
"@lodestar/state-transition": "^1.16.0",
"@lodestar/types": "^1.16.0",
"@lodestar/utils": "^1.16.0",
"@lodestar/validator": "^1.16.0",
"@multiformats/multiaddr": "^12.1.3",
"@types/datastore-level": "^3.0.0",
"buffer-xor": "^2.0.2",
Expand Down
9 changes: 8 additions & 1 deletion packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {BlockSource, getBlockInput, ImportBlockOpts, BlockInput} from "../../../
import {promiseAllMaybeAsync} from "../../../../util/promises.js";
import {isOptimisticBlock} from "../../../../util/forkChoice.js";
import {computeBlobSidecars} from "../../../../util/blobs.js";
import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js";
import {BlockError, BlockErrorCode, BlockGossipError} from "../../../../chain/errors/index.js";
import {OpSource} from "../../../../metrics/validatorMonitor.js";
import {NetworkEvent} from "../../../../network/index.js";
import {ApiModules} from "../../types.js";
Expand Down Expand Up @@ -83,6 +83,13 @@ export function getBeaconBlockApi({
try {
await validateGossipBlock(config, chain, signedBlock, fork);
} catch (error) {
if (error instanceof BlockGossipError && error.type.code === BlockErrorCode.ALREADY_KNOWN) {
chain.logger.debug("Ignoring known block during publishing", valLogMeta);
// Blocks might already be published by another node as part of a fallback setup or DVT cluster
// and can reach our node by gossip before the api. The error can be ignored and should not result in a 500 response.
return;
}

chain.logger.error("Gossip validations failed while publishing the block", valLogMeta, error as Error);
chain.persistInvalidSszValue(
chain.config.getForkTypes(slot).SignedBeaconBlock,
Expand Down
11 changes: 8 additions & 3 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function importBlock(
const parentEpoch = computeEpochAtSlot(parentBlockSlot);
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
const blockDelaySec = (fullyVerifiedBlock.seenTimestampSec - postState.genesisTime) % this.config.SECONDS_PER_SLOT;
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);

// 1. Persist block to hot DB (pre-emptively)
// If eagerPersistBlock = true we do that in verifyBlocksInEpoch to batch all I/O operations to save block time to head
Expand Down Expand Up @@ -447,9 +448,13 @@ export async function importBlock(
}, 0);

if (opts.seenTimestampSec !== undefined) {
const recvToImportedBlock = Date.now() / 1000 - opts.seenTimestampSec;
this.metrics?.gossipBlock.receivedToBlockImport.observe(recvToImportedBlock);
this.logger.verbose("Imported block", {slot: blockSlot, recvToImportedBlock});
const recvToValidation = Date.now() / 1000 - opts.seenTimestampSec;
const validationTime = recvToValidation - recvToValLatency;

this.metrics?.gossipBlock.blockImport.recvToValidation.observe(recvToValidation);
this.metrics?.gossipBlock.blockImport.validationTime.observe(validationTime);

this.logger.debug("Imported block", {slot: blockSlot, recvToValLatency, recvToValidation, validationTime});
}

this.logger.verbose("Block processed", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export async function verifyBlocksExecutionPayload(
): Promise<SegmentExecStatus> {
const executionStatuses: MaybeValidExecutionStatus[] = [];
let mergeBlockFound: bellatrix.BeaconBlock | null = null;
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);

// Error in the same way as verifyBlocksSanityChecks if empty blocks
if (blocks.length === 0) {
Expand Down Expand Up @@ -246,11 +247,17 @@ export async function verifyBlocksExecutionPayload(

const executionTime = Date.now();
if (blocks.length === 1 && opts.seenTimestampSec !== undefined && executionStatuses[0] === ExecutionStatus.Valid) {
const recvToVerifiedExecPayload = executionTime / 1000 - opts.seenTimestampSec;
chain.metrics?.gossipBlock.receivedToExecutionPayloadVerification.observe(recvToVerifiedExecPayload);
chain.logger.verbose("Verified execution payload", {
const recvToValidation = executionTime / 1000 - opts.seenTimestampSec;
const validationTime = recvToValidation - recvToValLatency;

chain.metrics?.gossipBlock.executionPayload.recvToValidation.observe(recvToValidation);
chain.metrics?.gossipBlock.executionPayload.validationTime.observe(validationTime);

chain.logger.debug("Verified execution payload", {
slot: blocks[0].message.slot,
recvToVerifiedExecPayload,
recvToValLatency,
recvToValidation,
validationTime,
});
}

Expand Down
16 changes: 13 additions & 3 deletions packages/beacon-node/src/chain/blocks/verifyBlocksSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function verifyBlocksSignatures(
opts: ImportBlockOpts
): Promise<{verifySignaturesTime: number}> {
const isValidPromises: Promise<boolean>[] = [];
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);

// Verifies signatures after running state transition, so all SyncCommittee signed roots are known at this point.
// We must ensure block.slot <= state.slot before running getAllBlockSignatureSets().
Expand Down Expand Up @@ -54,9 +55,18 @@ export async function verifyBlocksSignatures(

const verifySignaturesTime = Date.now();
if (blocks.length === 1 && opts.seenTimestampSec !== undefined) {
const recvToSigVer = verifySignaturesTime / 1000 - opts.seenTimestampSec;
metrics?.gossipBlock.receivedToSignaturesVerification.observe(recvToSigVer);
logger.verbose("Verified block signatures", {slot: blocks[0].message.slot, recvToSigVer});
const recvToValidation = verifySignaturesTime / 1000 - opts.seenTimestampSec;
const validationTime = recvToValidation - recvToValLatency;

metrics?.gossipBlock.signatureVerification.recvToValidation.observe(recvToValidation);
metrics?.gossipBlock.signatureVerification.validationTime.observe(validationTime);

logger.debug("Verified block signatures", {
slot: blocks[0].message.slot,
recvToValLatency,
recvToValidation,
validationTime,
});
}

return {verifySignaturesTime};
Expand Down

0 comments on commit e8e091a

Please sign in to comment.