From 76d3af7cca4528b4631099f46b49befe5b849fcc Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:41:51 +0200 Subject: [PATCH 1/7] Add merge block POW data to onBlock handler --- .../fork-choice/src/forkChoice/interface.ts | 7 ++----- .../lodestar/src/chain/blocks/importBlock.ts | 21 ++++++++++++++++--- packages/lodestar/src/chain/chain.ts | 1 + .../src/eth1/eth1MergeBlockTracker.ts | 16 ++++---------- packages/lodestar/src/eth1/index.ts | 11 +++++++++- packages/lodestar/src/eth1/interface.ts | 11 +++++++++- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index d443565e5987..f83b2a2eb0a5 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -135,12 +135,9 @@ export interface IForkChoice { } export type PowBlock = { - blockhash: Root; - parentHash: Root; - isProcessed: boolean; - isValid: boolean; + blockhash: RootHex; + parentHash: RootHex; totalDifficulty: bigint; - difficulty: bigint; }; export type OnBlockPrecachedData = { diff --git a/packages/lodestar/src/chain/blocks/importBlock.ts b/packages/lodestar/src/chain/blocks/importBlock.ts index 5e067cdf01c0..1cb4380a8800 100644 --- a/packages/lodestar/src/chain/blocks/importBlock.ts +++ b/packages/lodestar/src/chain/blocks/importBlock.ts @@ -5,11 +5,13 @@ import { CachedBeaconState, computeStartSlotAtEpoch, getEffectiveBalances, + merge, } from "@chainsafe/lodestar-beacon-state-transition"; import {IForkChoice, OnBlockPrecachedData} from "@chainsafe/lodestar-fork-choice"; import {ILogger} from "@chainsafe/lodestar-utils"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {IMetrics} from "../../metrics"; +import {IEth1ForBlockProduction} from "../../eth1"; import {IBeaconDb} from "../../db"; import {CheckpointStateCache, StateContextCache, toCheckpointHex} from "../stateCache"; import {ChainEvent} from "../emitter"; @@ -20,6 +22,7 @@ import {FullyVerifiedBlock} from "./types"; export type ImportBlockModules = { db: IBeaconDb; + eth1: IEth1ForBlockProduction; forkChoice: IForkChoice; stateCache: StateContextCache; checkpointStateCache: CheckpointStateCache; @@ -76,9 +79,21 @@ export async function importBlock(chain: ImportBlockModules, fullyVerifiedBlock: onBlockPrecachedData.justifiedBalances = getEffectiveBalances(state); } - // TODO: Figure out how to fetch for merge - // powBlock: undefined, - // powBlockParent: undefined, + if ( + merge.isMergeStateType(postState) && + merge.isMergeBlockBodyType(block.message.body) && + merge.isMergeBlock(postState, block.message.body) + ) { + // pow_block = get_pow_block(block.body.execution_payload.parent_hash) + const powBlockRootHex = toHexString(block.message.body.executionPayload.parentHash); + const powBlock = await chain.eth1.getPowBlock(powBlockRootHex); + if (!powBlock) throw Error(`merge block parent POW block not found ${powBlockRootHex}`); + // pow_parent = get_pow_block(pow_block.parent_hash) + const powBlockParent = await chain.eth1.getPowBlock(powBlock.parentHash); + if (!powBlockParent) throw Error(`merge block parent's parent POW block not found ${powBlock.parentHash}`); + onBlockPrecachedData.powBlock = powBlock; + onBlockPrecachedData.powBlockParent = powBlockParent; + } chain.forkChoice.onBlock(block.message, postState, onBlockPrecachedData); diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 7ad0ef814424..278ebcc96da6 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -144,6 +144,7 @@ export class BeaconChain implements IBeaconChain { clock, bls, regen, + eth1, db, forkChoice, stateCache, diff --git a/packages/lodestar/src/eth1/eth1MergeBlockTracker.ts b/packages/lodestar/src/eth1/eth1MergeBlockTracker.ts index 13e1cabc99cf..8c3626516e3f 100644 --- a/packages/lodestar/src/eth1/eth1MergeBlockTracker.ts +++ b/packages/lodestar/src/eth1/eth1MergeBlockTracker.ts @@ -1,18 +1,10 @@ import {AbortSignal} from "@chainsafe/abort-controller"; import {IChainConfig} from "@chainsafe/lodestar-config"; -import {Epoch} from "@chainsafe/lodestar-types"; -import {IEth1Provider, EthJsonRpcBlockRaw} from "./interface"; -import {hexToBigint, hexToNumber, validateHexRoot} from "./provider/utils"; +import {Epoch, RootHex} from "@chainsafe/lodestar-types"; import {ILogger, isErrorAborted, sleep} from "@chainsafe/lodestar-utils"; import {SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; - -type RootHexPow = string; -type PowMergeBlock = { - number: number; - blockhash: RootHexPow; - parentHash: RootHexPow; - totalDifficulty: bigint; -}; +import {IEth1Provider, EthJsonRpcBlockRaw, PowMergeBlock} from "./interface"; +import {hexToBigint, hexToNumber, validateHexRoot} from "./provider/utils"; export enum StatusCode { PRE_MERGE = "PRE_MERGE", @@ -57,7 +49,7 @@ export class Eth1MergeBlockTracker { * TODO: Accept multiple, but then handle long backwards searches properly after crossing TTD */ private mergeBlock: PowMergeBlock | null = null; - private readonly blocksByHashCache = new Map(); + private readonly blocksByHashCache = new Map(); private status: StatusCode = StatusCode.PRE_MERGE; private readonly intervals: NodeJS.Timeout[] = []; diff --git a/packages/lodestar/src/eth1/index.ts b/packages/lodestar/src/eth1/index.ts index d1df2d9c8315..54027ca6bea6 100644 --- a/packages/lodestar/src/eth1/index.ts +++ b/packages/lodestar/src/eth1/index.ts @@ -2,7 +2,7 @@ import {CachedBeaconState, computeEpochAtSlot, getCurrentSlot} from "@chainsafe/ import {allForks, Root} from "@chainsafe/lodestar-types"; import {merge} from "@chainsafe/lodestar-beacon-state-transition"; import {fromHexString} from "@chainsafe/ssz"; -import {IEth1ForBlockProduction, Eth1DataAndDeposits, IEth1Provider} from "./interface"; +import {IEth1ForBlockProduction, Eth1DataAndDeposits, IEth1Provider, PowMergeBlock} from "./interface"; import {Eth1DepositDataTracker, Eth1DepositDataTrackerModules} from "./eth1DepositDataTracker"; import {Eth1MergeBlockTracker, Eth1MergeBlockTrackerModules} from "./eth1MergeBlockTracker"; import {Eth1Options} from "./options"; @@ -86,6 +86,10 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { mergeCompleted(): void { this.eth1MergeBlockTracker.mergeCompleted(); } + + getPowBlock(powBlockHash: string): Promise { + return this.eth1MergeBlockTracker.getPowBlock(powBlockHash); + } } /** @@ -111,4 +115,9 @@ export class Eth1ForBlockProductionDisabled implements IEth1ForBlockProduction { mergeCompleted(): void { // Ignore } + + /** Will not be able to validate the merge block */ + async getPowBlock(): Promise { + throw Error("eth1 must be enabled to verify merge block"); + } } diff --git a/packages/lodestar/src/eth1/interface.ts b/packages/lodestar/src/eth1/interface.ts index 88ce59e07ec2..09e590dee804 100644 --- a/packages/lodestar/src/eth1/interface.ts +++ b/packages/lodestar/src/eth1/interface.ts @@ -3,7 +3,7 @@ */ import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {allForks, phase0, Root} from "@chainsafe/lodestar-types"; +import {allForks, phase0, Root, RootHex} from "@chainsafe/lodestar-types"; import {CachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition"; export type EthJsonRpcBlockRaw = { @@ -47,8 +47,17 @@ export interface IEth1ForBlockProduction { getPowBlockAtTotalDifficulty(): Root | null; /** Call when merge is irrevocably completed to stop polling unnecessary data from the eth1 node */ mergeCompleted(): void; + /** Get a POW block by hash checking the local cache first */ + getPowBlock(powBlockHash: string): Promise; } +export type PowMergeBlock = { + number: number; + blockhash: RootHex; + parentHash: RootHex; + totalDifficulty: bigint; +}; + export interface IBatchDepositEvents { depositEvents: phase0.DepositEvent[]; blockNumber: number; From ecb57010a2e80c0383287b50bad7113277ee1120 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:56:44 +0200 Subject: [PATCH 2/7] Add execution payload block hash to ProtoBlock --- .../fork-choice/src/forkChoice/forkChoice.ts | 3 ++ .../fork-choice/src/protoArray/interface.ts | 6 +++ .../fork-choice/src/protoArray/protoArray.ts | 46 ++++--------------- .../test/unit/forkChoice/forkChoice.test.ts | 6 ++- .../unit/protoArray/getCommonAncestor.test.ts | 2 + .../test/unit/protoArray/protoArray.test.ts | 3 ++ .../lodestar/src/chain/forkChoice/index.ts | 6 ++- .../unit/api/impl/beacon/blocks/utils.test.ts | 3 +- packages/lodestar/test/utils/block.ts | 1 + .../lodestar/test/utils/mocks/chain/chain.ts | 1 + .../test/utils/validationData/attestation.ts | 1 + 11 files changed, 38 insertions(+), 40 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index b081dcf89589..dd3f0884ddac 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -374,6 +374,9 @@ export class ForkChoice implements IForkChoice { parentRoot: parentRootHex, targetRoot: toHexString(targetRoot), stateRoot: toHexString(block.stateRoot), + executionPayloadBlockHash: merge.isMergeBlockBodyType(block.body) + ? toHexString(block.body.executionPayload.blockHash) + : null, justifiedEpoch: stateJustifiedEpoch, justifiedRoot: toHexString(state.currentJustifiedCheckpoint.root), finalizedEpoch: finalizedCheckpoint.epoch, diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index 3d098c7bbd03..47cf36bc0d49 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -39,6 +39,12 @@ export interface IProtoBlock { * it also just exists for upstream components (namely attestation verification) */ targetRoot: RootHex; + /** + * `executionPayloadBlockHash` is not necessary for ProtoArray either. + * Here to do ExecutionEngine.notify_forkchoice_updated() easier. + * TODO: Check with other teams if this is the optimal strategy + */ + executionPayloadBlockHash: RootHex | null; justifiedEpoch: Epoch; justifiedRoot: RootHex; finalizedEpoch: Epoch; diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index 141c74b5deb5..dca796e43fd1 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -1,4 +1,4 @@ -import {Epoch, Slot, RootHex} from "@chainsafe/lodestar-types"; +import {Epoch, RootHex} from "@chainsafe/lodestar-types"; import {IProtoBlock, IProtoNode, HEX_ZERO_HASH} from "./interface"; import {ProtoArrayError, ProtoArrayErrorCode} from "./errors"; @@ -38,45 +38,19 @@ export class ProtoArray { this.indices = new Map(); } - static initialize({ - slot, - parentRoot, - stateRoot, - blockRoot, - justifiedEpoch, - justifiedRoot, - finalizedEpoch, - finalizedRoot, - }: { - slot: Slot; - parentRoot: RootHex; - stateRoot: RootHex; - blockRoot: RootHex; - justifiedEpoch: Epoch; - justifiedRoot: RootHex; - finalizedEpoch: Epoch; - finalizedRoot: RootHex; - }): ProtoArray { + static initialize(block: Omit): ProtoArray { const protoArray = new ProtoArray({ pruneThreshold: DEFAULT_PRUNE_THRESHOLD, - justifiedEpoch, - justifiedRoot, - finalizedEpoch, - finalizedRoot, + justifiedEpoch: block.justifiedEpoch, + justifiedRoot: block.justifiedRoot, + finalizedEpoch: block.finalizedEpoch, + finalizedRoot: block.finalizedRoot, }); - const block: IProtoBlock = { - slot, - blockRoot, - parentRoot, - stateRoot, + protoArray.onBlock({ + ...block, // We are using the blockROot as the targetRoot, since it always lies on an epoch boundary - targetRoot: blockRoot, - justifiedEpoch, - justifiedRoot, - finalizedEpoch, - finalizedRoot, - }; - protoArray.onBlock(block); + targetRoot: block.blockRoot, + }); return protoArray; } diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index 566fd5152f82..aeb2a8752026 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -1,4 +1,4 @@ -import {ForkChoice, IForkChoiceStore, ProtoArray} from "../../../src"; +import {ForkChoice, IForkChoiceStore, IProtoBlock, ProtoArray} from "../../../src"; import {config} from "@chainsafe/lodestar-config/default"; import {expect} from "chai"; import {fromHexString} from "@chainsafe/ssz"; @@ -18,6 +18,7 @@ describe("Forkchoice", function () { stateRoot, parentRoot, blockRoot: finalizedRoot, + executionPayloadBlockHash: null, justifiedEpoch: genesisEpoch, justifiedRoot: genesisRoot, finalizedEpoch: genesisEpoch, @@ -25,12 +26,13 @@ describe("Forkchoice", function () { }); // Add block that is a finalized descendant. - const block = { + const block: IProtoBlock = { slot: genesisSlot + 1, blockRoot: finalizedDesc, parentRoot: finalizedRoot, stateRoot, targetRoot: finalizedRoot, + executionPayloadBlockHash: null, justifiedEpoch: genesisEpoch, justifiedRoot: genesisRoot, finalizedEpoch: genesisEpoch, diff --git a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts index 7b3f1f38518c..1ddf7cb1fda8 100644 --- a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts +++ b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts @@ -29,6 +29,7 @@ describe("getCommonAncestor", () => { stateRoot: "-", parentRoot: "-", blockRoot: "0", + executionPayloadBlockHash: null, justifiedEpoch: 0, justifiedRoot: "-", finalizedEpoch: 0, @@ -42,6 +43,7 @@ describe("getCommonAncestor", () => { parentRoot: block.parent, stateRoot: "-", targetRoot: "-", + executionPayloadBlockHash: null, justifiedEpoch: 0, justifiedRoot: "-", finalizedEpoch: 0, diff --git a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts index 64a85900dc2b..4b6c9d62bd1e 100644 --- a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts +++ b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts @@ -19,6 +19,7 @@ describe("ProtoArray", () => { stateRoot, parentRoot, blockRoot: finalizedRoot, + executionPayloadBlockHash: null, justifiedEpoch: genesisEpoch, justifiedRoot: stateRoot, finalizedEpoch: genesisEpoch, @@ -32,6 +33,7 @@ describe("ProtoArray", () => { parentRoot: finalizedRoot, stateRoot, targetRoot: finalizedRoot, + executionPayloadBlockHash: null, justifiedEpoch: genesisEpoch, justifiedRoot: stateRoot, finalizedEpoch: genesisEpoch, @@ -45,6 +47,7 @@ describe("ProtoArray", () => { parentRoot: unknown, stateRoot, targetRoot: finalizedRoot, + executionPayloadBlockHash: null, justifiedEpoch: genesisEpoch, justifiedRoot: stateRoot, finalizedEpoch: genesisEpoch, diff --git a/packages/lodestar/src/chain/forkChoice/index.ts b/packages/lodestar/src/chain/forkChoice/index.ts index c8b3bc75b1a8..cc36e8dd0ee3 100644 --- a/packages/lodestar/src/chain/forkChoice/index.ts +++ b/packages/lodestar/src/chain/forkChoice/index.ts @@ -6,10 +6,10 @@ import {toHexString} from "@chainsafe/ssz"; import {allForks, Slot} from "@chainsafe/lodestar-types"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {ForkChoice, ProtoArray, ForkChoiceStore} from "@chainsafe/lodestar-fork-choice"; +import {getEffectiveBalances, CachedBeaconState, merge} from "@chainsafe/lodestar-beacon-state-transition"; import {computeAnchorCheckpoint} from "../initState"; import {ChainEventEmitter} from "../emitter"; -import {getEffectiveBalances, CachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition"; import {IMetrics} from "../../metrics"; import {ChainEvent} from "../emitter"; @@ -53,6 +53,10 @@ export function initializeForkChoice( parentRoot: toHexString(blockHeader.parentRoot), stateRoot: toHexString(blockHeader.stateRoot), blockRoot: toHexString(checkpoint.root), + // TODO: Review if correct after merge interop + executionPayloadBlockHash: merge.isMergeStateType(state) + ? toHexString(state.latestExecutionPayloadHeader.blockHash) + : null, justifiedEpoch: justifiedCheckpoint.epoch, justifiedRoot: toHexString(justifiedCheckpoint.root), finalizedEpoch: finalizedCheckpoint.epoch, diff --git a/packages/lodestar/test/unit/api/impl/beacon/blocks/utils.test.ts b/packages/lodestar/test/unit/api/impl/beacon/blocks/utils.test.ts index aa0e57969e75..219243022f46 100644 --- a/packages/lodestar/test/unit/api/impl/beacon/blocks/utils.test.ts +++ b/packages/lodestar/test/unit/api/impl/beacon/blocks/utils.test.ts @@ -27,15 +27,16 @@ describe("block api utils", function () { expectedBuffer = Buffer.alloc(32, 2); expectedRootHex = toHexString(expectedBuffer); expectedSummary = { + slot: 0, blockRoot: expectedRootHex, parentRoot: expectedRootHex, targetRoot: expectedRootHex, stateRoot: expectedRootHex, + executionPayloadBlockHash: null, finalizedEpoch: 0, finalizedRoot: expectedRootHex, justifiedEpoch: 0, justifiedRoot: expectedRootHex, - slot: 0, }; }); diff --git a/packages/lodestar/test/utils/block.ts b/packages/lodestar/test/utils/block.ts index ef543b61396b..976d6b61db62 100644 --- a/packages/lodestar/test/utils/block.ts +++ b/packages/lodestar/test/utils/block.ts @@ -69,6 +69,7 @@ export function generateEmptyProtoBlock(): IProtoBlock { parentRoot: rootHex, stateRoot: rootHex, targetRoot: rootHex, + executionPayloadBlockHash: null, justifiedEpoch: 0, justifiedRoot: rootHex, finalizedEpoch: 0, diff --git a/packages/lodestar/test/utils/mocks/chain/chain.ts b/packages/lodestar/test/utils/mocks/chain/chain.ts index 12c4f647cd98..2b073a3c0917 100644 --- a/packages/lodestar/test/utils/mocks/chain/chain.ts +++ b/packages/lodestar/test/utils/mocks/chain/chain.ts @@ -195,6 +195,7 @@ function mockForkChoice(): IForkChoice { parentRoot: rootHex, stateRoot: rootHex, targetRoot: rootHex, + executionPayloadBlockHash: null, justifiedEpoch: 0, justifiedRoot: rootHex, finalizedEpoch: 0, diff --git a/packages/lodestar/test/utils/validationData/attestation.ts b/packages/lodestar/test/utils/validationData/attestation.ts index 1ac2a71841c6..b44b38c40093 100644 --- a/packages/lodestar/test/utils/validationData/attestation.ts +++ b/packages/lodestar/test/utils/validationData/attestation.ts @@ -57,6 +57,7 @@ export function getAttestationValidData( parentRoot: ZERO_HASH_HEX, stateRoot: ZERO_HASH_HEX, targetRoot: toHexString(targetRoot), + executionPayloadBlockHash: null, justifiedEpoch: 0, justifiedRoot: ZERO_HASH_HEX, finalizedEpoch: 0, From dc5538f74300edfa113bb08992d388c5a6b67657 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Mon, 27 Sep 2021 23:07:30 +0200 Subject: [PATCH 3/7] Send notifyForkchoiceUpdate on forkchoice events --- packages/lodestar/src/chain/eventHandlers.ts | 11 +++ packages/lodestar/src/executionEngine/http.ts | 6 +- .../lodestar/src/executionEngine/index.ts | 98 +------------------ .../lodestar/src/executionEngine/interface.ts | 4 +- 4 files changed, 19 insertions(+), 100 deletions(-) diff --git a/packages/lodestar/src/chain/eventHandlers.ts b/packages/lodestar/src/chain/eventHandlers.ts index 89687d4506a6..73ad241b11fc 100644 --- a/packages/lodestar/src/chain/eventHandlers.ts +++ b/packages/lodestar/src/chain/eventHandlers.ts @@ -9,6 +9,7 @@ import { computeStartSlotAtEpoch, } from "@chainsafe/lodestar-beacon-state-transition"; +import {ZERO_HASH_HEX} from "../constants"; import {AttestationError, BlockError, BlockErrorCode} from "./errors"; import {ChainEvent, IChainEvents} from "./emitter"; import {BeaconChain} from "./chain"; @@ -157,6 +158,16 @@ export async function onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWit this.opPool.pruneAll(headState); } + // Send event to consensus client + // TODO: Should send update with 0x0000.. when finalized block is still not merge block? + const headBlockHash = this.forkChoice.getHead().executionPayloadBlockHash; + const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash; + if (headBlockHash) { + this.executionEngine.notifyForkchoiceUpdate(headBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX).catch((e) => { + this.logger.error("Error pushing notifyForkchoiceUpdate()", {headBlockHash, finalizedBlockHash}, e); + }); + } + // Only after altair if (cp.epoch >= this.config.ALTAIR_FORK_EPOCH) { try { diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index a6b5389570ac..2856269bbe1c 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -1,4 +1,4 @@ -import {Bytes32, merge, Root, ExecutionAddress, PayloadId} from "@chainsafe/lodestar-types"; +import {Bytes32, merge, Root, ExecutionAddress, PayloadId, RootHex} from "@chainsafe/lodestar-types"; import {fromHexString, toHexString} from "@chainsafe/ssz"; import {JsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; import {hexToNumber, numberToHex} from "../eth1/provider/utils"; @@ -62,11 +62,11 @@ export class ExecutionEngineHttp implements IExecutionEngine { * 1. This method call maps on the POS_FORKCHOICE_UPDATED event of EIP-3675 and MUST be processed according to the specification defined in the EIP. * 2. Client software MUST respond with 4: Unknown block error if the payload identified by either the headBlockHash or the finalizedBlockHash is unknown. */ - notifyForkchoiceUpdate(headBlockHash: Root, finalizedBlockHash: Root): Promise { + notifyForkchoiceUpdate(headBlockHash: RootHex, finalizedBlockHash: RootHex): Promise { const method = "engine_forkchoiceUpdated"; return this.rpc.fetch({ method, - params: [{headBlockHash: toHexString(headBlockHash), finalizedBlockHash: toHexString(finalizedBlockHash)}], + params: [{headBlockHash, finalizedBlockHash}], }); } diff --git a/packages/lodestar/src/executionEngine/index.ts b/packages/lodestar/src/executionEngine/index.ts index 53f4bc90f0f8..b6f23d254e84 100644 --- a/packages/lodestar/src/executionEngine/index.ts +++ b/packages/lodestar/src/executionEngine/index.ts @@ -1,95 +1,3 @@ -import {Bytes32, merge, Root, ExecutionAddress, PayloadId} from "@chainsafe/lodestar-types"; - -/** - * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: - * - JSON RPC over network - * - IPC - * - Integrated code into the same binary - */ -export interface IExecutionEngine { - /** - * A state transition function which applies changes to the self.execution_state. - * Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. - * - * Required for block processing in the beacon state transition function. - * https://github.com/ethereum/consensus-specs/blob/0eb0a934a3/specs/merge/beacon-chain.md#on_payload - * - * Should be called in advance before, after or in parallel to block processing - */ - executePayload(executionPayload: merge.ExecutionPayload): Promise; - - /** - * Signals that the beacon block containing the execution payload is valid with respect to the consensus rule set. - * - * A call to notify_consensus_validated must be made after the state_transition function finishes. The value of the - * valid parameter must be set as follows: - * - True if state_transition function call succeeds - * - False if state_transition function call fails - * - * Note: The call of the notify_consensus_validated function with valid = True maps on the POS_CONSENSUS_VALIDATED event defined in the EIP-3675. - * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/beacon-chain.md#notify_consensus_validated - */ - notifyConsensusValidated(blockHash: Root, valid: boolean): Promise; - - /** - * Signal fork choice updates - * This function performs two actions atomically: - * - Re-organizes the execution payload chain and corresponding state to make head_block_hash the head. - * - Applies finality to the execution state: it irreversibly persists the chain of all execution payloads and - * corresponding state, up to and including finalized_block_hash. - * - * The call of the notify_forkchoice_updated function maps on the POS_FORKCHOICE_UPDATED event defined in the EIP-3675. - * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/fork-choice.md#notify_forkchoice_updated - * - * Should be called in response to fork-choice head and finalized events - */ - notifyForkchoiceUpdate(blockHash: Root): Promise; - - /** - * Given the set of execution payload attributes, prepare_payload initiates a process of building an execution - * payload on top of the execution chain tip identified by parent_hash. - * - * Required for block producing - * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/validator.md#prepare_payload - * - * Must be called close to the slot associated with the validator's block producing to get the blockHash and - * random correct - */ - preparePayload( - parentHash: Root, - timestamp: number, - random: Bytes32, - feeRecipient: ExecutionAddress - ): Promise; - - /** - * Given the payload_id, get_payload returns the most recent version of the execution payload that has been built - * since the corresponding call to prepare_payload method. - * - * Required for block producing - * https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/validator.md#get_payload - */ - getPayload(payloadId: PayloadId): Promise; -} - -export class ExecutionEngineDisabled implements IExecutionEngine { - async executePayload(): Promise { - throw Error("Execution engine disabled"); - } - - async notifyConsensusValidated(): Promise { - throw Error("Execution engine disabled"); - } - - async notifyForkchoiceUpdate(): Promise { - throw Error("Execution engine disabled"); - } - - async preparePayload(): Promise { - throw Error("Execution engine disabled"); - } - - async getPayload(): Promise { - throw Error("Execution engine disabled"); - } -} +export {IExecutionEngine} from "./interface"; +export {ExecutionEngineDisabled} from "./disabled"; +export {ExecutionEngineHttp} from "./http"; diff --git a/packages/lodestar/src/executionEngine/interface.ts b/packages/lodestar/src/executionEngine/interface.ts index 2e8a7e73245f..0380a0e38f97 100644 --- a/packages/lodestar/src/executionEngine/interface.ts +++ b/packages/lodestar/src/executionEngine/interface.ts @@ -1,4 +1,4 @@ -import {Bytes32, merge, Root, ExecutionAddress, PayloadId} from "@chainsafe/lodestar-types"; +import {Bytes32, merge, Root, ExecutionAddress, PayloadId, RootHex} from "@chainsafe/lodestar-types"; /** * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: @@ -43,7 +43,7 @@ export interface IExecutionEngine { * * Should be called in response to fork-choice head and finalized events */ - notifyForkchoiceUpdate(headBlockHash: Root, finalizedBlockHash: Root): Promise; + notifyForkchoiceUpdate(headBlockHash: RootHex, finalizedBlockHash: RootHex): Promise; /** * Given the set of execution payload attributes, prepare_payload initiates a process of building an execution From ea294a802915f146f1a5b1d16d0fe26248e6ac5e Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Mon, 27 Sep 2021 23:17:00 +0200 Subject: [PATCH 4/7] Verify executePayload in verifyBlock --- .../lodestar/src/chain/blocks/verifyBlock.ts | 16 +++++++++++++++- packages/lodestar/src/chain/chain.ts | 1 + packages/lodestar/src/chain/errors/blockError.ts | 5 ++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/lodestar/src/chain/blocks/verifyBlock.ts b/packages/lodestar/src/chain/blocks/verifyBlock.ts index 24f449d465f6..989b8fea7f28 100644 --- a/packages/lodestar/src/chain/blocks/verifyBlock.ts +++ b/packages/lodestar/src/chain/blocks/verifyBlock.ts @@ -1,9 +1,10 @@ import {ssz} from "@chainsafe/lodestar-types"; -import {CachedBeaconState, computeStartSlotAtEpoch, allForks} from "@chainsafe/lodestar-beacon-state-transition"; +import {CachedBeaconState, computeStartSlotAtEpoch, allForks, merge} from "@chainsafe/lodestar-beacon-state-transition"; import {toHexString} from "@chainsafe/ssz"; import {IForkChoice} from "@chainsafe/lodestar-fork-choice"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {IMetrics} from "../../metrics"; +import {IExecutionEngine} from "../../executionEngine"; import {BlockError, BlockErrorCode} from "../errors"; import {IBeaconClock} from "../clock"; import {BlockProcessOpts} from "../options"; @@ -13,6 +14,7 @@ import {FullyVerifiedBlock, PartiallyVerifiedBlock} from "./types"; export type VerifyBlockModules = { bls: IBlsVerifier; + executionEngine: IExecutionEngine; regen: IStateRegenerator; clock: IBeaconClock; forkChoice: IForkChoice; @@ -136,6 +138,18 @@ export async function verifyBlockStateTransition( } } + if ( + merge.isMergeStateType(postState) && + merge.isMergeBlockBodyType(block.message.body) && + merge.isExecutionEnabled(postState, block.message.body) + ) { + // TODO: Handle better executePayload() returning error is syncing + const isValid = await chain.executionEngine.executePayload(block.message.body.executionPayload); + if (!isValid) { + throw new BlockError(block, {code: BlockErrorCode.EXECUTION_PAYLOAD_NOT_VALID}); + } + } + // Check state root matches if (!ssz.Root.equals(block.message.stateRoot, postState.tree.root)) { throw new BlockError(block, {code: BlockErrorCode.INVALID_STATE_ROOT, preState, postState}); diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 278ebcc96da6..c28b5a68fb39 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -144,6 +144,7 @@ export class BeaconChain implements IBeaconChain { clock, bls, regen, + executionEngine, eth1, db, forkChoice, diff --git a/packages/lodestar/src/chain/errors/blockError.ts b/packages/lodestar/src/chain/errors/blockError.ts index 2de8cc0b2738..8baeff186721 100644 --- a/packages/lodestar/src/chain/errors/blockError.ts +++ b/packages/lodestar/src/chain/errors/blockError.ts @@ -55,6 +55,8 @@ export enum BlockErrorCode { SAME_PARENT_HASH = "BLOCK_ERROR_SAME_PARENT_HASH", /** Total size of executionPayload.transactions exceed a sane limit to prevent DOS attacks */ TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG", + /** Execution engine returned not valid after executePayload() call */ + EXECUTION_PAYLOAD_NOT_VALID = "BLOCK_ERROR_EXECUTION_PAYLOAD_NOT_VALID", } export type BlockErrorType = @@ -86,7 +88,8 @@ export type BlockErrorType = | {code: BlockErrorCode.INCORRECT_TIMESTAMP; timestamp: number; expectedTimestamp: number} | {code: BlockErrorCode.TOO_MUCH_GAS_USED; gasUsed: number; gasLimit: number} | {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex} - | {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}; + | {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number} + | {code: BlockErrorCode.EXECUTION_PAYLOAD_NOT_VALID}; export class BlockGossipError extends GossipActionError {} From b878b6c5551e0f0e5e8f888e3c1bbd37df97201f Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 28 Sep 2021 00:23:06 +0200 Subject: [PATCH 5/7] Call notifyConsensusValidated on verifyBlock --- .../lodestar/src/chain/blocks/verifyBlock.ts | 118 +++++++++++------- 1 file changed, 71 insertions(+), 47 deletions(-) diff --git a/packages/lodestar/src/chain/blocks/verifyBlock.ts b/packages/lodestar/src/chain/blocks/verifyBlock.ts index 989b8fea7f28..eb584859f2a2 100644 --- a/packages/lodestar/src/chain/blocks/verifyBlock.ts +++ b/packages/lodestar/src/chain/blocks/verifyBlock.ts @@ -3,6 +3,7 @@ import {CachedBeaconState, computeStartSlotAtEpoch, allForks, merge} from "@chai import {toHexString} from "@chainsafe/ssz"; import {IForkChoice} from "@chainsafe/lodestar-fork-choice"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; +import {ILogger} from "@chainsafe/lodestar-utils"; import {IMetrics} from "../../metrics"; import {IExecutionEngine} from "../../executionEngine"; import {BlockError, BlockErrorCode} from "../errors"; @@ -17,6 +18,7 @@ export type VerifyBlockModules = { executionEngine: IExecutionEngine; regen: IStateRegenerator; clock: IBeaconClock; + logger: ILogger; forkChoice: IForkChoice; config: IChainForkConfig; metrics: IMetrics | null; @@ -108,56 +110,78 @@ export async function verifyBlockStateTransition( throw new BlockError(block, {code: BlockErrorCode.PRESTATE_MISSING, error: e as Error}); }); - // STFN - per_slot_processing() + per_block_processing() - // NOTE: `regen.getPreState()` should have dialed forward the state already caching checkpoint states - const useBlsBatchVerify = !opts?.disableBlsBatchVerify; - const postState = allForks.stateTransition( - preState, - block, - { - // false because it's verified below with better error typing - verifyStateRoot: false, - // if block is trusted don't verify proposer or op signature - verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature, - verifySignatures: !useBlsBatchVerify && !validSignatures, - }, - chain.metrics - ); - - // Verify 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(). - // NOTE: If in the future multiple blocks signatures are verified at once, all blocks must be in the same epoch - // so the attester and proposer shufflings are correct. - if (useBlsBatchVerify && !validSignatures) { - const signatureSets = validProposerSignature - ? allForks.getAllBlockSignatureSetsExceptProposer(postState, block) - : allForks.getAllBlockSignatureSets(postState as CachedBeaconState, block); - - if (signatureSets.length > 0 && !(await chain.bls.verifySignatureSets(signatureSets))) { - throw new BlockError(block, {code: BlockErrorCode.INVALID_SIGNATURE, state: postState}); + // TODO: Review mergeBlock conditions + /** Not null if execution is enabled */ + const executionPayloadEnabled = + merge.isMergeStateType(preState) && + merge.isMergeBlockBodyType(block.message.body) && + merge.isExecutionEnabled(preState, block.message.body) + ? block.message.body.executionPayload + : null; + + // Wrap with try / catch to notify errors to execution client + try { + // STFN - per_slot_processing() + per_block_processing() + // NOTE: `regen.getPreState()` should have dialed forward the state already caching checkpoint states + const useBlsBatchVerify = !opts?.disableBlsBatchVerify; + const postState = allForks.stateTransition( + preState, + block, + { + // false because it's verified below with better error typing + verifyStateRoot: false, + // if block is trusted don't verify proposer or op signature + verifyProposer: !useBlsBatchVerify && !validSignatures && !validProposerSignature, + verifySignatures: !useBlsBatchVerify && !validSignatures, + }, + chain.metrics + ); + + // Verify 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(). + // NOTE: If in the future multiple blocks signatures are verified at once, all blocks must be in the same epoch + // so the attester and proposer shufflings are correct. + if (useBlsBatchVerify && !validSignatures) { + const signatureSets = validProposerSignature + ? allForks.getAllBlockSignatureSetsExceptProposer(postState, block) + : allForks.getAllBlockSignatureSets(postState as CachedBeaconState, block); + + if (signatureSets.length > 0 && !(await chain.bls.verifySignatureSets(signatureSets))) { + throw new BlockError(block, {code: BlockErrorCode.INVALID_SIGNATURE, state: postState}); + } } - } - if ( - merge.isMergeStateType(postState) && - merge.isMergeBlockBodyType(block.message.body) && - merge.isExecutionEnabled(postState, block.message.body) - ) { - // TODO: Handle better executePayload() returning error is syncing - const isValid = await chain.executionEngine.executePayload(block.message.body.executionPayload); - if (!isValid) { - throw new BlockError(block, {code: BlockErrorCode.EXECUTION_PAYLOAD_NOT_VALID}); + if (executionPayloadEnabled) { + // TODO: Handle better executePayload() returning error is syncing + const isValid = await chain.executionEngine.executePayload(executionPayloadEnabled); + if (!isValid) { + throw new BlockError(block, {code: BlockErrorCode.EXECUTION_PAYLOAD_NOT_VALID}); + } } - } - // Check state root matches - if (!ssz.Root.equals(block.message.stateRoot, postState.tree.root)) { - throw new BlockError(block, {code: BlockErrorCode.INVALID_STATE_ROOT, preState, postState}); - } + // Check state root matches + if (!ssz.Root.equals(block.message.stateRoot, postState.tree.root)) { + throw new BlockError(block, {code: BlockErrorCode.INVALID_STATE_ROOT, preState, postState}); + } - return { - block, - postState, - skipImportingAttestations: partiallyVerifiedBlock.skipImportingAttestations, - }; + // Block is fully valid for consensus, import block to execution client + if (executionPayloadEnabled) + chain.executionEngine.notifyConsensusValidated(executionPayloadEnabled.blockHash, true).catch((e) => { + chain.logger.error("Error on notifyConsensusValidated", {valid: true}, e); + }); + + return { + block, + postState, + skipImportingAttestations: partiallyVerifiedBlock.skipImportingAttestations, + }; + } catch (e) { + // Notify of consensus invalid block to execution client + if (executionPayloadEnabled) + chain.executionEngine.notifyConsensusValidated(executionPayloadEnabled.blockHash, true).catch((e) => { + chain.logger.error("Error on notifyConsensusValidated", {valid: false}, e); + }); + + throw e; + } } From 89895ae25af2f3fd16795ff42ff7bdec3f74786f Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 28 Sep 2021 00:34:10 +0200 Subject: [PATCH 6/7] Use ExecutionEngineHttp in BeaconNode --- .../src/eth1/provider/jsonRpcHttpClient.ts | 3 ++- packages/lodestar/src/executionEngine/http.ts | 25 ++++++++++++++++--- packages/lodestar/src/node/nodejs.ts | 8 +++--- packages/lodestar/src/node/options.ts | 3 +++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts b/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts index ec1b68f6e2d0..17afe77bc463 100644 --- a/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts +++ b/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts @@ -30,6 +30,7 @@ export class JsonRpcHttpClient { private readonly urls: string[], private readonly opts: { signal: AbortSignal; + timeout?: number; /** If returns true, do not fallback to other urls and throw early */ shouldNotFallback?: (error: Error) => boolean; } @@ -105,7 +106,7 @@ export class JsonRpcHttpClient { const controller = new AbortController(); const timeout = setTimeout(() => { controller.abort(); - }, opts?.timeout ?? REQUEST_TIMEOUT); + }, opts?.timeout ?? this.opts.timeout ?? REQUEST_TIMEOUT); const onParentSignalAbort = (): void => controller.abort(); diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index 2856269bbe1c..ad616b4f3840 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -1,10 +1,20 @@ +import {AbortSignal} from "@chainsafe/abort-controller"; import {Bytes32, merge, Root, ExecutionAddress, PayloadId, RootHex} from "@chainsafe/lodestar-types"; import {fromHexString, toHexString} from "@chainsafe/ssz"; import {JsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; import {hexToNumber, numberToHex} from "../eth1/provider/utils"; import {IExecutionEngine} from "./interface"; -/** +export type ExecutionEngineHttpOpts = { + urls: string[]; + timeout?: number; +}; + +export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = { + urls: ["http://localhost:8550"], +}; + +export /** * based on Ethereum JSON-RPC API and inherits the following properties of this standard: * - Supported communication protocols (HTTP and WebSocket) * - Message format and encoding notation @@ -13,8 +23,17 @@ import {IExecutionEngine} from "./interface"; * Client software MUST expose Engine API at a port independent from JSON-RPC API. The default port for the Engine API is 8550 for HTTP and 8551 for WebSocket. * https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.1/src/engine/interop/specification.md */ -export class ExecutionEngineHttp implements IExecutionEngine { - constructor(private readonly rpc: JsonRpcHttpClient) {} +class ExecutionEngineHttp implements IExecutionEngine { + private readonly rpc: JsonRpcHttpClient; + + constructor(opts: ExecutionEngineHttpOpts, signal: AbortSignal, rpc?: JsonRpcHttpClient) { + this.rpc = + rpc ?? + new JsonRpcHttpClient(opts.urls, { + signal, + timeout: opts.timeout, + }); + } /** * `engine_executePayload` diff --git a/packages/lodestar/src/node/nodejs.ts b/packages/lodestar/src/node/nodejs.ts index e27215255b32..bc20f37e027e 100644 --- a/packages/lodestar/src/node/nodejs.ts +++ b/packages/lodestar/src/node/nodejs.ts @@ -4,6 +4,7 @@ import {AbortController} from "@chainsafe/abort-controller"; import LibP2p from "libp2p"; +import {Registry} from "prom-client"; import {TreeBacked} from "@chainsafe/ssz"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; @@ -17,11 +18,10 @@ import {BeaconSync, IBeaconSync} from "../sync"; import {BeaconChain, IBeaconChain, initBeaconMetrics} from "../chain"; import {createMetrics, IMetrics, HttpMetricsServer} from "../metrics"; import {getApi, RestApi} from "../api"; -import {IBeaconNodeOptions} from "./options"; +import {ExecutionEngineHttp} from "../executionEngine"; import {initializeEth1ForBlockProduction} from "../eth1"; +import {IBeaconNodeOptions} from "./options"; import {runNodeNotifier} from "./notifier"; -import {Registry} from "prom-client"; -import {ExecutionEngineDisabled} from "../executionEngine"; export * from "./options"; @@ -137,7 +137,7 @@ export class BeaconNode { {config, db, logger: logger.child(opts.logger.eth1), signal}, anchorState ), - executionEngine: new ExecutionEngineDisabled(), + executionEngine: new ExecutionEngineHttp(opts.executionEngine, signal), }); // Load persisted data from disk to in-memory caches diff --git a/packages/lodestar/src/node/options.ts b/packages/lodestar/src/node/options.ts index 90e523a6c8cd..8eb9fe60b71f 100644 --- a/packages/lodestar/src/node/options.ts +++ b/packages/lodestar/src/node/options.ts @@ -10,6 +10,7 @@ import {defaultLoggerOptions, IBeaconLoggerOptions} from "./loggerOptions"; import {defaultMetricsOptions, IMetricsOptions} from "../metrics/options"; import {defaultNetworkOptions, INetworkOptions} from "../network/options"; import {defaultSyncOptions, SyncOptions} from "../sync/options"; +import {defaultExecutionEngineHttpOpts, ExecutionEngineHttpOpts} from "../executionEngine/http"; // Re-export so the CLI doesn't need to depend on lodestar-api export {allNamespaces} from "../api/rest/index"; @@ -18,6 +19,7 @@ export interface IBeaconNodeOptions { chain: IChainOptions; db: IDatabaseOptions; eth1: Eth1Options; + executionEngine: ExecutionEngineHttpOpts; logger: IBeaconLoggerOptions; metrics: IMetricsOptions; network: INetworkOptions; @@ -29,6 +31,7 @@ export const defaultOptions: IBeaconNodeOptions = { chain: defaultChainOptions, db: defaultDbOptions, eth1: defaultEth1Options, + executionEngine: defaultExecutionEngineHttpOpts, logger: defaultLoggerOptions, metrics: defaultMetricsOptions, network: defaultNetworkOptions, From e29ce68cc74c8403b1b9d323383b118571198d87 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 28 Sep 2021 00:47:54 +0200 Subject: [PATCH 7/7] Add execution cli options --- .../options/beaconNodeOptions/execution.ts | 30 +++++++++++++++++++ .../src/options/beaconNodeOptions/index.ts | 4 +++ .../unit/options/beaconNodeOptions.test.ts | 7 +++++ packages/lodestar/src/executionEngine/http.ts | 1 + 4 files changed, 42 insertions(+) create mode 100644 packages/cli/src/options/beaconNodeOptions/execution.ts diff --git a/packages/cli/src/options/beaconNodeOptions/execution.ts b/packages/cli/src/options/beaconNodeOptions/execution.ts new file mode 100644 index 000000000000..06741722bbe5 --- /dev/null +++ b/packages/cli/src/options/beaconNodeOptions/execution.ts @@ -0,0 +1,30 @@ +import {defaultOptions, IBeaconNodeOptions} from "@chainsafe/lodestar"; +import {ICliCommandOptions} from "../../util"; + +export type ExecutionEngineArgs = { + "execution.urls": string[]; + "execution.timeout": number; +}; + +export function parseArgs(args: ExecutionEngineArgs): IBeaconNodeOptions["executionEngine"] { + return { + urls: args["execution.urls"], + timeout: args["execution.timeout"], + }; +} + +export const options: ICliCommandOptions = { + "execution.urls": { + description: "Urls to execution client engine API", + type: "array", + defaultDescription: defaultOptions.executionEngine.urls.join(" "), + group: "execution", + }, + + "execution.timeout": { + description: "Timeout in miliseconds for execution engine API HTTP client", + type: "number", + defaultDescription: String(defaultOptions.executionEngine.timeout), + group: "execution", + }, +}; diff --git a/packages/cli/src/options/beaconNodeOptions/index.ts b/packages/cli/src/options/beaconNodeOptions/index.ts index 168b82facb2b..d9716d64d0de 100644 --- a/packages/cli/src/options/beaconNodeOptions/index.ts +++ b/packages/cli/src/options/beaconNodeOptions/index.ts @@ -4,6 +4,7 @@ import {removeUndefinedRecursive} from "../../util"; import * as api from "./api"; import * as chain from "./chain"; import * as eth1 from "./eth1"; +import * as execution from "./execution"; import * as logger from "./logger"; import * as metrics from "./metrics"; import * as network from "./network"; @@ -12,6 +13,7 @@ import * as sync from "./sync"; export type IBeaconNodeArgs = api.IApiArgs & chain.IChainArgs & eth1.IEth1Args & + execution.ExecutionEngineArgs & logger.ILoggerArgs & metrics.IMetricsArgs & network.INetworkArgs & @@ -24,6 +26,7 @@ export function parseBeaconNodeArgs(args: IBeaconNodeArgs): RecursivePartial { "eth1.providerUrls": ["http://my.node:8545"], "eth1.depositContractDeployBlock": 1625314, + "execution.urls": ["http://localhost:8550"], + "execution.timeout": 12000, + "logger.eth1.level": "debug", "logger.unknown.level": "debug", @@ -66,6 +69,10 @@ describe("options / beaconNodeOptions", () => { providerUrls: ["http://my.node:8545"], depositContractDeployBlock: 1625314, }, + executionEngine: { + urls: ["http://localhost:8550"], + timeout: 12000, + }, logger: { eth1: { level: LogLevel.debug, diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index ad616b4f3840..4b1e93a00d58 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -12,6 +12,7 @@ export type ExecutionEngineHttpOpts = { export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = { urls: ["http://localhost:8550"], + timeout: 12000, }; export /**