From 1778fc4eaf4d334a905497a36666de55c9bd6f43 Mon Sep 17 00:00:00 2001 From: g11tech Date: Sun, 14 Aug 2022 17:54:23 +0530 Subject: [PATCH] Add and heartbeat to engine exchangeTransitionConfigurationV1 method (#4411) --- .../beacon-node/src/chain/prepareNextSlot.ts | 36 +++++++++++++++++++ .../src/execution/engine/disabled.ts | 3 ++ .../beacon-node/src/execution/engine/http.ts | 33 +++++++++++++++++ .../src/execution/engine/interface.ts | 11 ++++++ .../beacon-node/src/execution/engine/mock.ts | 12 +++++++ 5 files changed, 95 insertions(+) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 47c002f5fba9..5aa8c6c8a38e 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -5,6 +5,8 @@ import {Slot} from "@lodestar/types"; import {ILogger, sleep} from "@lodestar/utils"; import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js"; import {IMetrics} from "../metrics/index.js"; +import {bytesToData, numToQuantity} from "../eth1/provider/utils.js"; +import {TransitionConfigurationV1} from "../execution/engine/interface.js"; import {ChainEvent} from "./emitter.js"; import {prepareExecutionPayload} from "./factory/block/body.js"; import {IBeaconChain} from "./interface.js"; @@ -28,6 +30,7 @@ const PREPARE_EPOCH_LIMIT = 1; * */ export class PrepareNextSlotScheduler { + private transitionConfig: TransitionConfigurationV1 | null = null; constructor( private readonly chain: IBeaconChain, private readonly config: IChainForkConfig, @@ -36,11 +39,24 @@ export class PrepareNextSlotScheduler { private readonly signal: AbortSignal ) { this.chain.emitter.on(ChainEvent.clockSlot, this.prepareForNextSlot); + // If the merge is configured + if (isFinite(this.config.BELLATRIX_FORK_EPOCH)) { + this.transitionConfig = { + terminalTotalDifficulty: numToQuantity(this.config.TERMINAL_TOTAL_DIFFICULTY), + terminalBlockHash: bytesToData(this.config.TERMINAL_BLOCK_HASH), + /** terminalBlockNumber has to be set to zero for now as per specs */ + terminalBlockNumber: numToQuantity(0), + }; + this.chain.emitter.on(ChainEvent.clockSlot, this.performExchangeTransitionHB); + } this.signal.addEventListener( "abort", () => { this.chain.emitter.off(ChainEvent.clockSlot, this.prepareForNextSlot); + if (this.transitionConfig) { + this.chain.emitter.off(ChainEvent.clockSlot, this.performExchangeTransitionHB); + } }, {once: true} ); @@ -138,4 +154,24 @@ export class PrepareNextSlotScheduler { this.logger.error("Failed to run prepareForNextSlot", {nextEpoch, isEpochTransition, prepareSlot}, e as Error); } }; + + /** + * perform heart beat for EL lest it logs warning that CL is not connected + */ + performExchangeTransitionHB = async (_clockSlot: Slot): Promise => { + const transitionConfig = this.transitionConfig; + if (transitionConfig) { + const elTransitionConfig = await this.chain.executionEngine.exchangeTransitionConfigurationV1(transitionConfig); + if ( + elTransitionConfig.terminalTotalDifficulty !== transitionConfig.terminalTotalDifficulty || + elTransitionConfig.terminalBlockHash !== transitionConfig.terminalBlockHash + ) { + this.logger.error( + `Transition config mismatch, actual=${JSON.stringify(elTransitionConfig)}, expected=${JSON.stringify( + transitionConfig + )}` + ); + } + } + }; } diff --git a/packages/beacon-node/src/execution/engine/disabled.ts b/packages/beacon-node/src/execution/engine/disabled.ts index 5af9eb2112d2..ca7bad91038b 100644 --- a/packages/beacon-node/src/execution/engine/disabled.ts +++ b/packages/beacon-node/src/execution/engine/disabled.ts @@ -14,4 +14,7 @@ export class ExecutionEngineDisabled implements IExecutionEngine { async getPayload(): Promise { throw Error("Execution engine disabled"); } + async exchangeTransitionConfigurationV1(): Promise { + throw Error("Execution engine disabled"); + } } diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index 7775b188136a..333fe6849c2f 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -22,6 +22,7 @@ import { PayloadId, PayloadAttributes, ApiPayloadAttributes, + TransitionConfigurationV1, } from "./interface.js"; import {PayloadIdCache} from "./payloadIdCache.js"; @@ -60,6 +61,7 @@ export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = { const notifyNewPayloadOpts: ReqOpts = {routeId: "notifyNewPayload"}; const forkchoiceUpdatedV1Opts: ReqOpts = {routeId: "forkchoiceUpdated"}; const getPayloadOpts: ReqOpts = {routeId: "getPayload"}; +const exchageTransitionConfigOpts: ReqOpts = {routeId: "exchangeTransitionConfiguration"}; /** * based on Ethereum JSON-RPC API and inherits the following properties of this standard: @@ -283,6 +285,29 @@ export class ExecutionEngineHttp implements IExecutionEngine { return parseExecutionPayload(executionPayloadRpc); } + /** + * `engine_exchangeTransitionConfigurationV1` + * + * An api method for EL<>CL transition config matching and heartbeat + */ + + async exchangeTransitionConfigurationV1( + transitionConfiguration: TransitionConfigurationV1 + ): Promise { + const method = "engine_exchangeTransitionConfigurationV1"; + const exchangeTransitionConfigurationV1Res = await this.rpc.fetchWithRetries< + EngineApiRpcReturnTypes[typeof method], + EngineApiRpcParamTypes[typeof method] + >( + { + method, + params: [transitionConfiguration], + }, + exchageTransitionConfigOpts + ); + return exchangeTransitionConfigurationV1Res; + } + async prunePayloadIdCache(): Promise { this.payloadIdCache.prune(); } @@ -308,6 +333,10 @@ type EngineApiRpcParamTypes = { * 1. payloadId: QUANTITY, 64 Bits - Identifier of the payload building process */ engine_getPayloadV1: [QUANTITY]; + /** + * 1. Object - Instance of TransitionConfigurationV1 + */ + engine_exchangeTransitionConfigurationV1: [TransitionConfigurationV1]; }; type EngineApiRpcReturnTypes = { @@ -328,6 +357,10 @@ type EngineApiRpcReturnTypes = { * payloadId | Error: QUANTITY, 64 Bits - Identifier of the payload building process */ engine_getPayloadV1: ExecutionPayloadRpc; + /** + * Object - Instance of TransitionConfigurationV1 + */ + engine_exchangeTransitionConfigurationV1: TransitionConfigurationV1; }; type ExecutionPayloadRpc = { diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index 051ade3465d8..ec36a8700ff2 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,4 +1,5 @@ import {bellatrix, RootHex} from "@lodestar/types"; +import {DATA, QUANTITY} from "../../eth1/provider/utils.js"; import {PayloadIdCache, PayloadId, ApiPayloadAttributes} from "./payloadIdCache.js"; export {PayloadIdCache, PayloadId, ApiPayloadAttributes}; @@ -47,6 +48,12 @@ export type PayloadAttributes = { suggestedFeeRecipient: string; }; +export type TransitionConfigurationV1 = { + terminalTotalDifficulty: QUANTITY; + terminalBlockHash: DATA; + terminalBlockNumber: QUANTITY; +}; + /** * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: * - JSON RPC over network @@ -93,4 +100,8 @@ export interface IExecutionEngine { * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/validator.md#get_payload */ getPayload(payloadId: PayloadId): Promise; + + exchangeTransitionConfigurationV1( + transitionConfiguration: TransitionConfigurationV1 + ): Promise; } diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index 2fe0a44bc6d5..15e1424f9544 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -10,6 +10,7 @@ import { PayloadId, PayloadAttributes, PayloadIdCache, + TransitionConfigurationV1, } from "./interface.js"; const INTEROP_GAS_LIMIT = 30e6; @@ -228,6 +229,17 @@ export class ExecutionEngineMock implements IExecutionEngine { return payload; } + async exchangeTransitionConfigurationV1( + transitionConfiguration: TransitionConfigurationV1 + ): Promise { + const resTransitionConfig = { + terminalTotalDifficulty: transitionConfiguration.terminalTotalDifficulty, + terminalBlockHash: transitionConfiguration.terminalBlockHash, + terminalBlockNumber: transitionConfiguration.terminalBlockNumber, + }; + return resTransitionConfig; + } + /** * Non-spec method just to add more known blocks to this mock. */