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

feat: include EL client info in graffiti #6753

Merged
merged 36 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c6c5ffb
Define ClientCode and engine_getClientVersionV1
ensi321 May 9, 2024
e51c618
Default graffiti in beacon node
ensi321 Jun 2, 2024
59fc851
Update packages/beacon-node/src/api/impl/validator/index.ts
ensi321 Jun 10, 2024
1780cf4
Fix rebase
ensi321 Jun 21, 2024
5b3f398
Make graffiti optional in validator store
ensi321 Jun 24, 2024
324b586
Merge branch 'unstable' into nc/graffiti
ensi321 Jun 24, 2024
1cb3c0c
Fix merge
ensi321 Jun 24, 2024
65385b9
Fix lint
ensi321 Jun 24, 2024
2f07060
Update packages/beacon-node/src/execution/engine/types.ts
ensi321 Jun 24, 2024
0da0020
Add fallback graffiti
ensi321 Jun 24, 2024
8cebfae
Address comment
ensi321 Jun 24, 2024
5afa36e
Address comment
ensi321 Jun 24, 2024
467de59
Cache client version in ExecutionEngine
ensi321 Jun 26, 2024
42bafc8
Hide graffiti if private flag is set
ensi321 Jun 26, 2024
70358e9
Improve readability
ensi321 Jun 26, 2024
fc70f03
Partially address comment
ensi321 Jun 27, 2024
b2d2238
Merge branch 'unstable' into nc/graffiti
ensi321 Jul 18, 2024
455bddd
Partially address comment
ensi321 Jul 18, 2024
1c91dc4
Partially address comment
ensi321 Jul 18, 2024
e666c97
Refactor
ensi321 Jul 18, 2024
6257dcf
Update packages/beacon-node/src/execution/engine/http.ts
ensi321 Jul 24, 2024
fc4eaa0
Partial address comment
ensi321 Jul 24, 2024
5aefdb9
Add unit test
ensi321 Jul 25, 2024
0734b9e
Fix unit test
ensi321 Jul 25, 2024
44a3c8b
Review PR, mostly cosmetic
nflaig Jul 29, 2024
b6dbc27
Merge branch 'unstable' into nc/graffiti
nflaig Jul 29, 2024
52edf2b
Fix graffiti tests
nflaig Jul 29, 2024
35b8e95
Add workaround to test code instead of src
nflaig Jul 29, 2024
defa1ea
Merge branch 'unstable' into nc/graffiti
nflaig Jul 29, 2024
5fd6b4f
Set client version to null if not supported by EL
nflaig Jul 29, 2024
8538988
Log failed client version updates as debug
nflaig Jul 29, 2024
1c8352e
Throw error if EL client returns empty client versions array
nflaig Jul 29, 2024
c781797
Update engine mock
nflaig Jul 29, 2024
038e980
Set client version to null initially to avoid fetching multiple times
nflaig Jul 29, 2024
f273632
Reorder statements
nflaig Jul 29, 2024
cb7a5d4
Merge branch 'unstable' into nc/graffiti
ensi321 Jul 30, 2024
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
12 changes: 6 additions & 6 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,13 @@ export type Endpoints = {
/** The validator's randao reveal value */
randaoReveal: BLSSignature;
/** Arbitrary data validator wants to include in block */
graffiti: string;
graffiti?: string;
} & Omit<ExtraProduceBlockOpts, "blindedLocal">,
{
params: {slot: number};
query: {
randao_reveal: string;
graffiti: string;
graffiti?: string;
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
fee_recipient?: string;
builder_selection?: string;
strict_fee_recipient_check?: boolean;
Expand All @@ -333,15 +333,15 @@ export type Endpoints = {
/** The validator's randao reveal value */
randaoReveal: BLSSignature;
/** Arbitrary data validator wants to include in block */
graffiti: string;
graffiti?: string;
skipRandaoVerification?: boolean;
builderBoostFactor?: UintBn64;
} & ExtraProduceBlockOpts,
{
params: {slot: number};
query: {
randao_reveal: string;
graffiti: string;
graffiti?: string;
skip_randao_verification?: string;
fee_recipient?: string;
builder_selection?: string;
Expand All @@ -359,9 +359,9 @@ export type Endpoints = {
{
slot: Slot;
randaoReveal: BLSSignature;
graffiti: string;
graffiti?: string;
},
{params: {slot: number}; query: {randao_reveal: string; graffiti: string}},
{params: {slot: number}; query: {randao_reveal: string; graffiti?: string}},
BlindedBeaconBlock,
VersionMeta
>;
Expand Down
11 changes: 9 additions & 2 deletions packages/api/src/utils/serdes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ export function fromValidatorIdsStr(ids?: string[]): (string | number)[] | undef

const GRAFFITI_HEX_LENGTH = 66;

export function toGraffitiHex(utf8: string): string {
export function toGraffitiHex(utf8?: string): string | undefined {
if (utf8 === undefined) {
return undefined;
}

const hex = toHexString(new TextEncoder().encode(utf8));

if (hex.length > GRAFFITI_HEX_LENGTH) {
Expand All @@ -93,7 +97,10 @@ export function toGraffitiHex(utf8: string): string {
return hex;
}

export function fromGraffitiHex(hex: string): string {
export function fromGraffitiHex(hex?: string): string | undefined {
if (hex === undefined) {
return undefined;
}
try {
return new TextDecoder("utf8").decode(fromHexString(hex));
} catch {
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ export function getApi(opts: ApiOptions, modules: ApiModules): BeaconApiMethods
lodestar: getLodestarApi(modules),
node: getNodeApi(opts, modules),
proof: getProofApi(opts, modules),
validator: getValidatorApi(modules),
validator: getValidatorApi(opts, modules),
};
}
27 changes: 12 additions & 15 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
import {ZERO_HASH} from "../../../constants/index.js";
import {SyncState} from "../../../sync/index.js";
import {isOptimisticBlock} from "../../../util/forkChoice.js";
import {toGraffitiBuffer} from "../../../util/graffiti.js";
import {getDefaultGraffiti, toGraffitiBuffer} from "../../../util/graffiti.js";
import {ApiError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
import {CommitteeSubscription} from "../../../network/subnets/index.js";
Expand All @@ -63,6 +63,7 @@ import {getValidatorStatus} from "../beacon/state/utils.js";
import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js";
import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js";
import {ApiOptions} from "../../options.js";
import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";

/**
Expand Down Expand Up @@ -110,14 +111,10 @@ type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedB
* Server implementation for handling validator duties.
* See `@lodestar/validator/src/api` for the client implementation).
*/
export function getValidatorApi({
chain,
config,
logger,
metrics,
network,
sync,
}: ApiModules): ApplicationMethods<routes.validator.Endpoints> {
export function getValidatorApi(
opts: ApiOptions,
{chain, config, logger, metrics, network, sync}: ApiModules
): ApplicationMethods<routes.validator.Endpoints> {
let genesisBlockRoot: Root | null = null;

/**
Expand Down Expand Up @@ -348,7 +345,7 @@ export function getValidatorApi({
async function produceBuilderBlindedBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
graffiti?: string,
// as of now fee recipient checks can not be performed because builder does not return bid recipient
{
skipHeadChecksAndUpdate,
Expand Down Expand Up @@ -406,7 +403,7 @@ export function getValidatorApi({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
graffiti: toGraffitiBuffer(graffiti ?? getDefaultGraffiti(opts, chain.executionEngine.clientVersion)),
commonBlockBody,
});

Expand All @@ -432,7 +429,7 @@ export function getValidatorApi({
async function produceEngineFullBlockOrContents(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
graffiti?: string,
{
feeRecipient,
strictFeeRecipientCheck,
Expand Down Expand Up @@ -474,7 +471,7 @@ export function getValidatorApi({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
graffiti: toGraffitiBuffer(graffiti ?? getDefaultGraffiti(opts, chain.executionEngine.clientVersion)),
feeRecipient,
commonBlockBody,
});
Expand Down Expand Up @@ -522,7 +519,7 @@ export function getValidatorApi({
async function produceEngineOrBuilderBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
graffiti?: string,
// TODO deneb: skip randao verification
_skipRandaoVerification?: boolean,
builderBoostFactor?: bigint,
Expand Down Expand Up @@ -585,7 +582,7 @@ export function getValidatorApi({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
graffiti: toGraffitiBuffer(graffiti ?? getDefaultGraffiti(opts, chain.executionEngine.clientVersion)),
});
logger.debug("Produced common block body", loggerContext);

Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/api/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type ApiOptions = {
maxGindicesInProof?: number;
rest: BeaconRestApiServerOpts;
version?: string;
private?: boolean;
};

export const defaultApiOptions: ApiOptions = {
Expand Down
14 changes: 13 additions & 1 deletion packages/beacon-node/src/execution/engine/disabled.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ExecutionEngineState, IExecutionEngine, PayloadIdCache} from "./interface.js";
import {ClientVersion, ExecutionEngineState, IExecutionEngine, PayloadIdCache} from "./interface.js";

export class ExecutionEngineDisabled implements IExecutionEngine {
state = ExecutionEngineState.OFFLINE;
Expand Down Expand Up @@ -28,4 +28,16 @@ export class ExecutionEngineDisabled implements IExecutionEngine {
getPayloadBodiesByRange(): Promise<never> {
throw Error("Execution engine disabled");
}

getClientVersion(): Promise<ClientVersion[]> {
throw Error("Execution engine disabled");
}

getState(): ExecutionEngineState {
throw Error("Execution engine disabled");
}

getExecutionClientVersion(): ClientVersion {
throw Error("Execution engine disabled");
}
}
54 changes: 53 additions & 1 deletion packages/beacon-node/src/execution/engine/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {Metrics} from "../../metrics/index.js";
import {JobItemQueue} from "../../util/queue/index.js";
import {EPOCHS_PER_BATCH} from "../../sync/constants.js";
import {numToQuantity} from "../../eth1/provider/utils.js";
import {getLodestarClientVersion} from "../../util/graffiti.js";
import {
ExecutionPayloadStatus,
ExecutePayloadResponse,
Expand All @@ -21,6 +22,8 @@ import {
BlobsBundle,
VersionedHashes,
ExecutionEngineState,
ClientVersion,
ClientCode,
} from "./interface.js";
import {PayloadIdCache} from "./payloadIdCache.js";
import {
Expand Down Expand Up @@ -63,6 +66,14 @@ export type ExecutionEngineHttpOpts = {
* A version string that will be set in `clv` field of jwt claims
*/
jwtVersion?: string;
/**
* Lodestar version to be used for `ClientVersion`
*/
version?: string;
/**
* Lodestar commit to be used for `ClientVersion`
*/
commit?: string;
};

export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = {
Expand Down Expand Up @@ -105,6 +116,9 @@ export class ExecutionEngineHttp implements IExecutionEngine {
// It's safer to to avoid false positives and assume that the EL is syncing until we receive the first payload
state: ExecutionEngineState = ExecutionEngineState.ONLINE;

// Cache EL client version from the latest getClientVersion call
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
clientVersion?: ClientVersion;

readonly payloadIdCache = new PayloadIdCache();
/**
* A queue to serialize the fcUs and newPayloads calls:
Expand All @@ -126,7 +140,8 @@ export class ExecutionEngineHttp implements IExecutionEngine {

constructor(
private readonly rpc: IJsonRpcHttpClient,
{metrics, signal, logger}: ExecutionEngineModules
{metrics, signal, logger}: ExecutionEngineModules,
private readonly opts?: ExecutionEngineHttpOpts
) {
this.rpcFetchQueue = new JobItemQueue<[EngineRequest], EngineResponse>(
this.jobQueueProcessor,
Expand All @@ -142,6 +157,11 @@ export class ExecutionEngineHttp implements IExecutionEngine {
this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => {
this.updateEngineState(getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state}));
});

// Initial engine state is ONLINE. Need to explicitly call `getClientVersion` once on startup
this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
this.logger.error("Unable to get client version", {caller: "ExecutionEngineHttp constructor"}, e);
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
});
}

/**
Expand Down Expand Up @@ -417,6 +437,35 @@ export class ExecutionEngineHttp implements IExecutionEngine {
return response.map(deserializeExecutionPayloadBody);
}

getState(): ExecutionEngineState {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
return this.state;
}

async getClientVersion(clientVersion: ClientVersion): Promise<ClientVersion[]> {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
const method = "engine_getClientVersionV1";

const response = await this.rpc.fetchWithRetries<
EngineApiRpcReturnTypes[typeof method],
EngineApiRpcParamTypes[typeof method]
>({method, params: [clientVersion]});

const clientVersions = response.map((cv) => {
const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX;
return {code, name: cv.name, version: cv.version, commit: cv.commit};
});

if (clientVersions.length > 0) {
this.clientVersion = clientVersions[0];
this.logger.info("Execution client version is updated", this.clientVersion);
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
}

return clientVersions;
}

get executionClientVersion(): ClientVersion | undefined {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
return this.clientVersion;
}

private updateEngineState(newState: ExecutionEngineState): void {
const oldState = this.state;

Expand All @@ -425,6 +474,9 @@ export class ExecutionEngineHttp implements IExecutionEngine {
switch (newState) {
case ExecutionEngineState.ONLINE:
this.logger.info("Execution client became online", {oldState, newState});
this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
this.logger.error("Unable to get client version", {caller: "updateEngineState"}, e);
});
break;
case ExecutionEngineState.OFFLINE:
this.logger.error("Execution client went offline", {oldState, newState});
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/execution/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function getExecutionEngineHttp(
jwtVersion: opts.jwtVersion,
});
modules.logger.info("Execution client", {urls: opts.urls.map(toSafePrintableUrl).toString()});
return new ExecutionEngineHttp(rpc, modules);
return new ExecutionEngineHttp(rpc, modules, opts);
}

export function initializeExecutionEngine(
Expand Down
32 changes: 32 additions & 0 deletions packages/beacon-node/src/execution/engine/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ export enum ExecutionEngineState {
AUTH_FAILED = "AUTH_FAILED",
}

/**
* Client code as defined in https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.4/src/engine/identification.md#clientcode
* ClientCode.XX is dedicated to other clients which do not have their own code
*/
export enum ClientCode {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
BU = "besu",
EJ = "ethereumJS",
EG = "erigon",
GE = "go-ethereum",
GR = "grandine",
LH = "lighthouse",
LS = "lodestar",
NM = "nethermind",
NB = "nimbus",
TK = "teku",
PM = "prysm",
RH = "reth",
XX = "unknown",
}

export type ExecutePayloadResponse =
| {
status: ExecutionPayloadStatus.SYNCING | ExecutionPayloadStatus.ACCEPTED;
Expand Down Expand Up @@ -80,6 +100,13 @@ export type BlobsBundle = {
proofs: KZGProof[];
};

export type ClientVersion = {
code: ClientCode;
name: string;
version: string;
commit: string;
};

export type VersionedHashes = Uint8Array[];

/**
Expand All @@ -91,6 +118,7 @@ export type VersionedHashes = Uint8Array[];
export interface IExecutionEngine {
readonly state: ExecutionEngineState;

readonly clientVersion?: ClientVersion;
payloadIdCache: PayloadIdCache;
/**
* A state transition function which applies changes to the self.execution_state.
Expand Down Expand Up @@ -148,4 +176,8 @@ export interface IExecutionEngine {
getPayloadBodiesByHash(blockHash: DATA[]): Promise<(ExecutionPayloadBody | null)[]>;

getPayloadBodiesByRange(start: number, count: number): Promise<(ExecutionPayloadBody | null)[]>;

getClientVersion(clientVersion: ClientVersion): Promise<ClientVersion[]>;

getState(): ExecutionEngineState;
}
1 change: 1 addition & 0 deletions packages/beacon-node/src/execution/engine/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
engine_getPayloadV3: this.getPayload.bind(this),
engine_getPayloadBodiesByHashV1: this.getPayloadBodiesByHash.bind(this),
engine_getPayloadBodiesByRangeV1: this.getPayloadBodiesByRange.bind(this),
engine_getClientVersionV1: () => [],
};
}

Expand Down
Loading
Loading