From 5251797d5b6ab56ac871bafc1d3ffa3299fcc87d Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 5 Nov 2021 19:07:41 +0530 Subject: [PATCH 01/23] completing ethereum/consensus-specs#2617 --- packages/config/src/chainConfig/sszTypes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/config/src/chainConfig/sszTypes.ts b/packages/config/src/chainConfig/sszTypes.ts index c15533f9d19e..58525b46cccc 100644 --- a/packages/config/src/chainConfig/sszTypes.ts +++ b/packages/config/src/chainConfig/sszTypes.ts @@ -13,6 +13,7 @@ export const ChainConfig = new ContainerType({ // Transition TERMINAL_TOTAL_DIFFICULTY: ssz.Uint256, + TERMINAL_BLOCK_HASH: ssz.Root, // Genesis MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: ssz.Number64, From d49940bbc2665277dfd2ebde13eab350954b300b Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 5 Nov 2021 23:16:45 +0530 Subject: [PATCH 02/23] add TBH_ACTIVATION_EPOCH ethereum/consensus-specs#2682 --- packages/config/src/chainConfig/presets/mainnet.ts | 1 + packages/config/src/chainConfig/presets/minimal.ts | 1 + packages/config/src/chainConfig/sszTypes.ts | 1 + packages/config/src/chainConfig/types.ts | 1 + packages/fork-choice/src/forkChoice/forkChoice.ts | 10 ++++++++-- packages/lodestar/src/chain/factory/block/body.ts | 7 +++++++ 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/config/src/chainConfig/presets/mainnet.ts b/packages/config/src/chainConfig/presets/mainnet.ts index f5aa90857e01..d7832f3734b9 100644 --- a/packages/config/src/chainConfig/presets/mainnet.ts +++ b/packages/config/src/chainConfig/presets/mainnet.ts @@ -10,6 +10,7 @@ export const chainConfig: IChainConfig = { // TBD, 2**256-1 is a placeholder TERMINAL_TOTAL_DIFFICULTY: BigInt(115792089237316195423570985008687907853269984665640564039457584007913129639935), TERMINAL_BLOCK_HASH: b("0x0000000000000000000000000000000000000000000000000000000000000000"), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: Infinity, // Genesis // --------------------------------------------------------------- diff --git a/packages/config/src/chainConfig/presets/minimal.ts b/packages/config/src/chainConfig/presets/minimal.ts index d5ebe2d39e63..16cfb0f41d74 100644 --- a/packages/config/src/chainConfig/presets/minimal.ts +++ b/packages/config/src/chainConfig/presets/minimal.ts @@ -11,6 +11,7 @@ export const chainConfig: IChainConfig = { // TBD, 2**256-1 is a placeholder TERMINAL_TOTAL_DIFFICULTY: BigInt(115792089237316195423570985008687907853269984665640564039457584007913129639935), TERMINAL_BLOCK_HASH: b("0x0000000000000000000000000000000000000000000000000000000000000000"), + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: Infinity, // Genesis // --------------------------------------------------------------- diff --git a/packages/config/src/chainConfig/sszTypes.ts b/packages/config/src/chainConfig/sszTypes.ts index 58525b46cccc..61ee8b1f9b34 100644 --- a/packages/config/src/chainConfig/sszTypes.ts +++ b/packages/config/src/chainConfig/sszTypes.ts @@ -14,6 +14,7 @@ export const ChainConfig = new ContainerType({ // Transition TERMINAL_TOTAL_DIFFICULTY: ssz.Uint256, TERMINAL_BLOCK_HASH: ssz.Root, + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: ssz.Number64, // Genesis MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: ssz.Number64, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index fa3e4aa34e28..2241c41a75c8 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -10,6 +10,7 @@ export interface IChainConfig { // Transition TERMINAL_TOTAL_DIFFICULTY: bigint; TERMINAL_BLOCK_HASH: Uint8Array; + TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: number; // Genesis MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: number; diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 6cf8eff54af4..224238481f7a 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -306,6 +306,12 @@ export class ForkChoice implements IForkChoice { if (!isValidTerminalPowBlock(this.config, powBlock, powBlockParent)) { throw Error("Not valid terminal POW block"); } + if ( + this.config.TERMINAL_BLOCK_HASH !== ZERO_HASH && + computeEpochAtSlot(block.slot) < this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + ) { + throw Error("Terminal activation epoch not reached"); + } } let shouldUpdateJustified = false; @@ -911,8 +917,8 @@ export class ForkChoice implements IForkChoice { } function isValidTerminalPowBlock(config: IChainConfig, block: PowBlockHex, parent: PowBlockHex): boolean { - if (block.blockhash === toHexString(config.TERMINAL_BLOCK_HASH)) { - return true; + if (config.TERMINAL_BLOCK_HASH !== ZERO_HASH) { + return block.blockhash === toHexString(config.TERMINAL_BLOCK_HASH); } const isTotalDifficultyReached = block.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; const isParentTotalDifficultyValid = parent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index 97ebfc5bbb1c..d16b9c485b38 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -10,9 +10,11 @@ import { computeTimeAtSlot, getRandaoMix, merge, + getCurrentEpoch, } from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconChain} from "../../interface"; import {PayloadId} from "../../../executionEngine/interface"; +import {ZERO_HASH} from "../../../constants"; export async function assembleBody( chain: IBeaconChain, @@ -107,6 +109,11 @@ async function prepareExecutionPayload( // Returned value of null == using an empty ExecutionPayload value let parentHash: Root; if (!merge.isMergeComplete(state)) { + if ( + chain.config.TERMINAL_BLOCK_HASH !== ZERO_HASH && + getCurrentEpoch(state) < chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + ) + return null; const terminalPowBlockHash = chain.eth1.getTerminalPowBlock(); if (terminalPowBlockHash === null) { // Pre-merge, no prepare payload call is needed From 7f929d1063d7a28282d4bae9cfc307eb2ab6d5c2 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 13:10:25 +0530 Subject: [PATCH 03/23] remove prepare payload specs 2682, add payload id as return to notify_forkchoice... 2711 --- .../lodestar/src/chain/factory/block/body.ts | 33 +++++++++-- .../lodestar/src/executionEngine/disabled.ts | 4 -- packages/lodestar/src/executionEngine/http.ts | 58 +++---------------- .../lodestar/src/executionEngine/interface.ts | 33 +++++------ packages/lodestar/src/executionEngine/mock.ts | 43 +++++--------- .../lodestar/test/sim/merge-interop.test.ts | 24 ++------ .../test/unit/executionEngine/http.test.ts | 39 ------------- packages/types/src/merge/types.ts | 6 ++ 8 files changed, 76 insertions(+), 164 deletions(-) diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index d16b9c485b38..293959049160 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -3,18 +3,30 @@ */ import {List} from "@chainsafe/ssz"; -import {Bytes96, Bytes32, phase0, allForks, altair, Root, Slot, ssz, ExecutionAddress} from "@chainsafe/lodestar-types"; +import { + Bytes96, + Bytes32, + phase0, + allForks, + altair, + Root, + RootHex, + Slot, + ssz, + ExecutionAddress, +} from "@chainsafe/lodestar-types"; import { CachedBeaconState, computeEpochAtSlot, computeTimeAtSlot, getRandaoMix, - merge, getCurrentEpoch, + merge, } from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconChain} from "../../interface"; import {PayloadId} from "../../../executionEngine/interface"; -import {ZERO_HASH} from "../../../constants"; +import {ZERO_HASH, ZERO_HASH_HEX} from "../../../constants"; +import {bytesToData, numToQuantity} from "../../../eth1/provider/utils"; export async function assembleBody( chain: IBeaconChain, @@ -79,8 +91,11 @@ export async function assembleBody( // - Call prepareExecutionPayload as early as possible // - Call prepareExecutionPayload again if parameters change + const finalizedBlockHash = chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash; + const payloadId = await prepareExecutionPayload( chain, + finalizedBlockHash ?? ZERO_HASH_HEX, currentState as CachedBeaconState, feeRecipient ); @@ -102,6 +117,7 @@ export async function assembleBody( */ async function prepareExecutionPayload( chain: IBeaconChain, + finalizedBlockHash: RootHex, state: CachedBeaconState, feeRecipient: ExecutionAddress ): Promise { @@ -127,9 +143,14 @@ async function prepareExecutionPayload( parentHash = state.latestExecutionPayloadHeader.blockHash; } - const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime); - const random = getRandaoMix(state, state.currentShuffling.epoch); - return chain.executionEngine.preparePayload(parentHash, timestamp, random, feeRecipient); + const timestamp = numToQuantity(computeTimeAtSlot(chain.config, state.slot, state.genesisTime)); + const random = bytesToData(getRandaoMix(state, state.currentShuffling.epoch)); + const payloadAttributes = {timestamp, random, feeRecipient: bytesToData(feeRecipient)}; + return await chain.executionEngine.notifyForkchoiceUpdate( + bytesToData(parentHash), + finalizedBlockHash, + payloadAttributes + ); } /** process_sync_committee_contributions is implemented in syncCommitteeContribution.getSyncAggregate */ diff --git a/packages/lodestar/src/executionEngine/disabled.ts b/packages/lodestar/src/executionEngine/disabled.ts index 14c18eee731e..16da77957e42 100644 --- a/packages/lodestar/src/executionEngine/disabled.ts +++ b/packages/lodestar/src/executionEngine/disabled.ts @@ -9,10 +9,6 @@ export class ExecutionEngineDisabled implements IExecutionEngine { throw Error("Execution engine disabled"); } - async preparePayload(): Promise { - throw Error("Execution engine disabled"); - } - async getPayload(): Promise { throw Error("Execution engine disabled"); } diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index 697bfe53ef42..fde8fe6ae437 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -1,5 +1,5 @@ import {AbortSignal} from "@chainsafe/abort-controller"; -import {Bytes32, merge, Root, ExecutionAddress, RootHex} from "@chainsafe/lodestar-types"; +import {merge, RootHex} from "@chainsafe/lodestar-types"; import {JsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; import { bytesToData, @@ -11,7 +11,7 @@ import { quantityToBigint, } from "../eth1/provider/utils"; import {IJsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; -import {ExecutePayloadStatus, IExecutionEngine, PayloadId} from "./interface"; +import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes} from "./interface"; import {BYTES_PER_LOGS_BLOOM} from "@chainsafe/lodestar-params"; export type ExecutionEngineHttpOpts = { @@ -81,7 +81,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: RootHex, finalizedBlockHash: RootHex): Promise { + notifyForkchoiceUpdate( + headBlockHash: RootHex, + finalizedBlockHash: RootHex, + _payloadAttributes?: PayloadAttributes + ): Promise { const method = "engine_forkchoiceUpdated"; return this.rpc.fetch({ method, @@ -89,41 +93,6 @@ export class ExecutionEngineHttp implements IExecutionEngine { }); } - /** - * `engine_preparePayload` - * - * 1. Given provided field value set client software SHOULD build the initial version of the payload which has an empty transaction set. - * 2. Client software SHOULD start the process of updating the payload. The strategy of this process is implementation dependent. The default strategy would be to keep the transaction set up-to-date with the state of local mempool. - * 3. Client software SHOULD stop the updating process either by finishing to serve the engine_getPayload call with the same payloadId value or when SECONDS_PER_SLOT (currently set to 12 in the Mainnet configuration) seconds have passed since the point in time identified by the timestamp parameter. - * 4. Client software MUST set the payload field values according to the set of parameters passed in the call to this method with exception for the feeRecipient. The coinbase field value MAY deviate from what is specified by the feeRecipient parameter. - * 5. Client software SHOULD respond with 2: Action not allowed error if the sync process is in progress. - * 6. Client software SHOULD respond with 4: Unknown block error if the parent block is unknown. - */ - async preparePayload( - parentHash: Root, - timestamp: number, - random: Bytes32, - feeRecipient: ExecutionAddress - ): Promise { - const method = "engine_preparePayload"; - const {payloadId} = await this.rpc.fetch< - EngineApiRpcReturnTypes[typeof method], - EngineApiRpcParamTypes[typeof method] - >({ - method, - params: [ - { - parentHash: bytesToData(parentHash), - timestamp: numToQuantity(timestamp), - random: bytesToData(random), - feeRecipient: bytesToData(feeRecipient), - }, - ], - }); - - return payloadId; - } - /** * `engine_getPayload` * @@ -181,7 +150,7 @@ type EngineApiRpcReturnTypes = { */ engine_executePayload: {status: ExecutePayloadStatus}; engine_consensusValidated: void; - engine_forkchoiceUpdated: void; + engine_forkchoiceUpdated: QUANTITY; /** * payloadId | Error: QUANTITY, 64 Bits - Identifier of the payload building process */ @@ -189,17 +158,6 @@ type EngineApiRpcReturnTypes = { engine_getPayload: ExecutionPayloadRpc; }; -type PayloadAttributes = { - /** DATA, 32 Bytes - hash of the parent block */ - parentHash: DATA; - /** QUANTITY, 64 Bits - value for the timestamp field of the new payload */ - timestamp: QUANTITY; - /** DATA, 32 Bytes - value for the random field of the new payload */ - random: DATA; - /** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */ - feeRecipient: DATA; -}; - type ExecutionPayloadRpc = { parentHash: DATA; // 32 bytes coinbase: DATA; // 20 bytes diff --git a/packages/lodestar/src/executionEngine/interface.ts b/packages/lodestar/src/executionEngine/interface.ts index 67025e5a8885..6ae00b079929 100644 --- a/packages/lodestar/src/executionEngine/interface.ts +++ b/packages/lodestar/src/executionEngine/interface.ts @@ -1,5 +1,5 @@ -import {Bytes32, merge, Root, ExecutionAddress, RootHex} from "@chainsafe/lodestar-types"; - +import {merge, RootHex} from "@chainsafe/lodestar-types"; +import {DATA, QUANTITY} from "../eth1/provider/utils"; // An execution engine can produce a payload id anywhere the the uint64 range // Since we do no processing with this id, we have no need to deserialize it export type PayloadId = string; @@ -13,6 +13,14 @@ export enum ExecutePayloadStatus { SYNCING = "SYNCING", } +export type PayloadAttributes = { + /** QUANTITY, 64 Bits - value for the timestamp field of the new payload */ + timestamp: QUANTITY; + /** DATA, 32 Bytes - value for the random field of the new payload */ + random: DATA; + /** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */ + feeRecipient: DATA; +}; /** * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: * - JSON RPC over network @@ -43,23 +51,10 @@ export interface IExecutionEngine { * * Should be called in response to fork-choice head and finalized events */ - notifyForkchoiceUpdate(headBlockHash: RootHex, finalizedBlockHash: RootHex): 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 + notifyForkchoiceUpdate( + headBlockHash: RootHex, + finalizedBlockHash: RootHex, + payloadAttributes?: PayloadAttributes ): Promise; /** diff --git a/packages/lodestar/src/executionEngine/mock.ts b/packages/lodestar/src/executionEngine/mock.ts index 170a37327ec2..57304f2461bd 100644 --- a/packages/lodestar/src/executionEngine/mock.ts +++ b/packages/lodestar/src/executionEngine/mock.ts @@ -1,10 +1,10 @@ import crypto from "crypto"; -import {Bytes32, merge, Root, ExecutionAddress, RootHex} from "@chainsafe/lodestar-types"; -import {toHexString} from "@chainsafe/ssz"; +import {merge, RootHex} from "@chainsafe/lodestar-types"; +import {toHexString, fromHexString} from "@chainsafe/ssz"; import {ZERO_HASH, ZERO_HASH_HEX} from "../constants"; -import {ExecutePayloadStatus, IExecutionEngine, PayloadId} from "./interface"; +import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes} from "./interface"; import {BYTES_PER_LOGS_BLOOM} from "@chainsafe/lodestar-params"; - +import {quantityToNum} from "../eth1/provider/utils"; const INTEROP_GAS_LIMIT = 30e6; export type ExecutionEngineMockOpts = { @@ -69,7 +69,11 @@ export class ExecutionEngineMock 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. */ - async notifyForkchoiceUpdate(headBlockHash: RootHex, finalizedBlockHash: RootHex): Promise { + async notifyForkchoiceUpdate( + headBlockHash: RootHex, + finalizedBlockHash: RootHex, + payloadAttributes?: PayloadAttributes + ): Promise { if (!this.knownBlocks.has(headBlockHash)) { throw Error(`Unknown headBlockHash ${headBlockHash}`); } @@ -79,42 +83,27 @@ export class ExecutionEngineMock implements IExecutionEngine { this.headBlockRoot = headBlockHash; this.finalizedBlockRoot = finalizedBlockHash; - } - /** - * `engine_preparePayload` - * - * 1. Given provided field value set client software SHOULD build the initial version of the payload which has an empty transaction set. - * 2. Client software SHOULD start the process of updating the payload. The strategy of this process is implementation dependent. The default strategy would be to keep the transaction set up-to-date with the state of local mempool. - * 3. Client software SHOULD stop the updating process either by finishing to serve the engine_getPayload call with the same payloadId value or when SECONDS_PER_SLOT (currently set to 12 in the Mainnet configuration) seconds have passed since the point in time identified by the timestamp parameter. - * 4. Client software MUST set the payload field values according to the set of parameters passed in the call to this method with exception for the feeRecipient. The coinbase field value MAY deviate from what is specified by the feeRecipient parameter. - * 5. Client software SHOULD respond with 2: Action not allowed error if the sync process is in progress. - * 6. Client software SHOULD respond with 4: Unknown block error if the parent block is unknown. - */ - async preparePayload( - parentHash: Root, - timestamp: number, - random: Bytes32, - feeRecipient: ExecutionAddress - ): Promise { - const parentHashHex = toHexString(parentHash); + const parentHashHex = headBlockHash; const parentPayload = this.knownBlocks.get(parentHashHex); if (!parentPayload) { throw Error(`Unknown parentHash ${parentHashHex}`); } + if (!payloadAttributes) throw Error("InvalidPayloadAttributes"); + const payloadId = this.payloadId++; const payload: merge.ExecutionPayload = { - parentHash: parentHash, - coinbase: feeRecipient, + parentHash: fromHexString(headBlockHash), + coinbase: fromHexString(payloadAttributes.feeRecipient), stateRoot: crypto.randomBytes(32), receiptRoot: crypto.randomBytes(32), logsBloom: crypto.randomBytes(BYTES_PER_LOGS_BLOOM), - random: random, + random: fromHexString(payloadAttributes.random), blockNumber: parentPayload.blockNumber + 1, gasLimit: INTEROP_GAS_LIMIT, gasUsed: Math.floor(0.5 * INTEROP_GAS_LIMIT), - timestamp: timestamp, + timestamp: quantityToNum(payloadAttributes.timestamp), extraData: ZERO_HASH, baseFeePerGas: BigInt(0), blockHash: crypto.randomBytes(32), diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index ef9615259962..0ad69bc492db 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -4,12 +4,11 @@ import net from "net"; import {spawn} from "child_process"; import {Context} from "mocha"; import {AbortController, AbortSignal} from "@chainsafe/abort-controller"; -import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {fromHexString} from "@chainsafe/ssz"; import {LogLevel, sleep, TimestampFormatCode} from "@chainsafe/lodestar-utils"; import {SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; import {IChainConfig} from "@chainsafe/lodestar-config"; import {Epoch} from "@chainsafe/lodestar-types"; -import {dataToBytes, quantityToNum} from "../../src/eth1/provider/utils"; import {ExecutionEngineHttp} from "../../src/executionEngine/http"; import {shell} from "./shell"; import {ChainEvent} from "../../src/chain"; @@ -206,17 +205,15 @@ describe("executionEngine / ExecutionEngineHttp", function () { const preparePayloadParams = { // Note: this is created with a pre-defined genesis.json - parentHash: genesisBlockHash, timestamp: "0x5", random: "0x0000000000000000000000000000000000000000000000000000000000000000", feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", }; - const payloadId = await executionEngine.preparePayload( - dataToBytes(preparePayloadParams.parentHash), - quantityToNum(preparePayloadParams.timestamp), - dataToBytes(preparePayloadParams.random), - dataToBytes(preparePayloadParams.feeRecipient) + const payloadId = await executionEngine.notifyForkchoiceUpdate( + genesisBlockHash, + genesisBlockHash, + preparePayloadParams ); // 2. Get the payload @@ -230,17 +227,6 @@ describe("executionEngine / ExecutionEngineHttp", function () { throw Error("getPayload returned payload that executePayload deems invalid"); } - // 4. Update the fork choice - - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdated","params":[{ - * "headBlockHash":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", - * "finalizedBlockHash":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174" - * }],"id":67}' http://localhost:8545 - */ - - await executionEngine.notifyForkchoiceUpdate(toHexString(payload.blockHash), toHexString(payload.blockHash)); - // Error cases // 1. unknown payload diff --git a/packages/lodestar/test/unit/executionEngine/http.test.ts b/packages/lodestar/test/unit/executionEngine/http.test.ts index f7655c7e2a50..69eeb176ba64 100644 --- a/packages/lodestar/test/unit/executionEngine/http.test.ts +++ b/packages/lodestar/test/unit/executionEngine/http.test.ts @@ -3,7 +3,6 @@ import chaiAsPromised from "chai-as-promised"; import {fastify} from "fastify"; import {AbortController} from "@chainsafe/abort-controller"; import {ExecutionEngineHttp, parseExecutionPayload, serializeExecutionPayload} from "../../../src/executionEngine/http"; -import {dataToBytes, quantityToNum} from "../../../src/eth1/provider/utils"; chai.use(chaiAsPromised); @@ -40,44 +39,6 @@ describe("ExecutionEngine / http", () => { executionEngine = new ExecutionEngineHttp({urls: [baseUrl]}, controller.signal); }); - it("preparePayload", async () => { - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_preparePayload","params":[{ - * "parentHash":"0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131", - * "timestamp":"0x5", - * "random":"0x0000000000000000000000000000000000000000000000000000000000000000", - * "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" - * }],"id":67}' http://localhost:8545 - * - * {"jsonrpc":"2.0","id":67,"result":{"payloadId":"0x0"}} - */ - - const request = { - jsonrpc: "2.0", - method: "engine_preparePayload", - params: [ - { - parentHash: "0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131", - timestamp: "0x5", - random: "0x0000000000000000000000000000000000000000000000000000000000000000", - feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - }, - ], - }; - returnValue = {jsonrpc: "2.0", id: 67, result: {payloadId: "0x0"}}; - - const param0 = request.params[0]; - const payloadId = await executionEngine.preparePayload( - dataToBytes(param0.parentHash), - quantityToNum(param0.timestamp), - dataToBytes(param0.random), - dataToBytes(param0.feeRecipient) - ); - - expect(payloadId).to.equal("0x0", "Wrong returned payloadId"); - expect(reqJsonRpcPayload).to.deep.equal(request, "Wrong request JSON RPC payload"); - }); - it("getPayload", async () => { /** * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayload","params":["0x0"],"id":67}' http://localhost:8545 diff --git a/packages/types/src/merge/types.ts b/packages/types/src/merge/types.ts index a702517635e5..9159ef16e8e4 100644 --- a/packages/types/src/merge/types.ts +++ b/packages/types/src/merge/types.ts @@ -50,6 +50,12 @@ export interface BeaconState extends altair.BeaconState { latestExecutionPayloadHeader: ExecutionPayloadHeader; } +export interface PayloadAttributes{ + timestamp: Number64; + random: Bytes32; + feeRecipient: ExecutionAddress; +} + export type PowBlock = { blockHash: Root; parentHash: Root; From 9340c3612a443d23b8037eaa65f524ea321f056e Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 14:06:29 +0530 Subject: [PATCH 04/23] remove uniion from transaction (2683), fix gossip and tx size (2686), remove gas validation(2699) --- .../merge/block/processExecutionPayload.ts | 35 ------------------- packages/params/src/index.ts | 13 +++---- packages/params/src/preset/index.ts | 1 + packages/params/src/preset/interface.ts | 3 +- packages/params/src/preset/merge/index.ts | 2 ++ packages/params/src/preset/merge/interface.ts | 8 +++++ packages/params/src/preset/merge/ssz.ts | 18 ++++++++++ packages/params/src/preset/ssz.ts | 2 ++ packages/params/src/presets/mainnet/index.ts | 2 ++ packages/params/src/presets/mainnet/merge.ts | 9 +++++ packages/params/src/presets/minimal/index.ts | 2 ++ packages/params/src/presets/minimal/merge.ts | 9 +++++ packages/types/src/merge/sszTypes.ts | 20 +++-------- packages/types/src/merge/types.ts | 2 +- 14 files changed, 65 insertions(+), 61 deletions(-) create mode 100644 packages/params/src/preset/merge/index.ts create mode 100644 packages/params/src/preset/merge/interface.ts create mode 100644 packages/params/src/preset/merge/ssz.ts create mode 100644 packages/params/src/presets/mainnet/merge.ts create mode 100644 packages/params/src/presets/minimal/merge.ts diff --git a/packages/beacon-state-transition/src/merge/block/processExecutionPayload.ts b/packages/beacon-state-transition/src/merge/block/processExecutionPayload.ts index 5650a3a88a73..cfbbe156d96b 100644 --- a/packages/beacon-state-transition/src/merge/block/processExecutionPayload.ts +++ b/packages/beacon-state-transition/src/merge/block/processExecutionPayload.ts @@ -1,4 +1,3 @@ -import {GAS_LIMIT_DENOMINATOR, MIN_GAS_LIMIT} from "@chainsafe/lodestar-params"; import {merge, ssz} from "@chainsafe/lodestar-types"; import {byteArrayEquals, List, toHexString} from "@chainsafe/ssz"; import {CachedBeaconState} from "../../allForks"; @@ -6,30 +5,6 @@ import {getRandaoMix} from "../../util"; import {ExecutionEngine} from "../executionEngine"; import {isMergeComplete} from "../utils"; -function isValidGasLimit(payload: merge.ExecutionPayload, parent: merge.ExecutionPayloadHeader): boolean { - const parentGasLimit = parent.gasLimit; - - // Check if the payload used too much gas - if (payload.gasUsed > payload.gasLimit) { - return false; - } - - // Check if the payload changed the gas limit too much - if (payload.gasLimit >= parentGasLimit + Math.floor(parentGasLimit / GAS_LIMIT_DENOMINATOR)) { - return false; - } - if (payload.gasLimit <= parentGasLimit - Math.floor(parentGasLimit / GAS_LIMIT_DENOMINATOR)) { - return false; - } - - // Check if the gas limit is at least the minimum gas limit - if (payload.gasLimit < MIN_GAS_LIMIT) { - return false; - } - - return true; -} - export function processExecutionPayload( state: CachedBeaconState, payload: merge.ExecutionPayload, @@ -46,16 +21,6 @@ export function processExecutionPayload( )}` ); } - if (payload.blockNumber !== latestExecutionPayloadHeader.blockNumber + 1) { - throw Error( - `Invalid execution payload blockNumber ${payload.blockNumber} parent=${latestExecutionPayloadHeader.blockNumber}` - ); - } - if (!isValidGasLimit(payload, latestExecutionPayloadHeader)) { - throw Error( - `Invalid gasLimit gasUsed=${payload.gasUsed} gasLimit=${payload.gasLimit} parentGasLimit=${latestExecutionPayloadHeader.gasLimit}` - ); - } } // Verify random diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 9656ea243636..aff4f972235f 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -67,6 +67,11 @@ export const { INACTIVITY_PENALTY_QUOTIENT_ALTAIR, MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR, PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, + + MAX_BYTES_PER_TRANSACTION, + MAX_TRANSACTIONS_PER_PAYLOAD, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, } = presets[ACTIVE_PRESET]; //////////// @@ -136,14 +141,6 @@ export const SYNC_COMMITTEE_SUBNET_COUNT = 4; export const MAX_REQUEST_BLOCKS = 2 ** 10; // 1024 -// Merge constants - Spec v1.0.1 -export const MAX_BYTES_PER_OPAQUE_TRANSACTION = 1048576; // 2**20; -export const MAX_TRANSACTIONS_PER_PAYLOAD = 16384; // 2**14; -export const BYTES_PER_LOGS_BLOOM = 256; // 2**8; -export const GAS_LIMIT_DENOMINATOR = 1024; // 2**10; -export const MIN_GAS_LIMIT = 5000; -export const MAX_EXTRA_DATA_BYTES = 32; // 2**5 - // Merge constants - Spec v1.0.1 // Genesis testing settings // Note: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. diff --git a/packages/params/src/preset/index.ts b/packages/params/src/preset/index.ts index 496948262cdb..90cca2856417 100644 --- a/packages/params/src/preset/index.ts +++ b/packages/params/src/preset/index.ts @@ -4,3 +4,4 @@ export * from "./ssz"; export * from "./phase0"; export * from "./altair"; +export * from "./merge"; diff --git a/packages/params/src/preset/interface.ts b/packages/params/src/preset/interface.ts index 77a593082686..7d3e9cf2fedd 100644 --- a/packages/params/src/preset/interface.ts +++ b/packages/params/src/preset/interface.ts @@ -5,5 +5,6 @@ import {IPhase0Preset} from "./phase0"; import {IAltairPreset} from "./altair"; +import {IMergePreset} from "./merge"; -export type IBeaconPreset = IPhase0Preset & IAltairPreset; +export type IBeaconPreset = IPhase0Preset & IAltairPreset & IMergePreset; diff --git a/packages/params/src/preset/merge/index.ts b/packages/params/src/preset/merge/index.ts new file mode 100644 index 000000000000..20616ee7a7ff --- /dev/null +++ b/packages/params/src/preset/merge/index.ts @@ -0,0 +1,2 @@ +export * from "./interface"; +export * from "./ssz"; diff --git a/packages/params/src/preset/merge/interface.ts b/packages/params/src/preset/merge/interface.ts new file mode 100644 index 000000000000..2aec19b6f5d1 --- /dev/null +++ b/packages/params/src/preset/merge/interface.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +export interface IMergePreset { + MAX_BYTES_PER_TRANSACTION: number; + MAX_TRANSACTIONS_PER_PAYLOAD: number; + BYTES_PER_LOGS_BLOOM: number; + MAX_EXTRA_DATA_BYTES: number; +} diff --git a/packages/params/src/preset/merge/ssz.ts b/packages/params/src/preset/merge/ssz.ts new file mode 100644 index 000000000000..7eb82ac683c6 --- /dev/null +++ b/packages/params/src/preset/merge/ssz.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import {ContainerType, NumberUintType} from "@chainsafe/ssz"; + +import {IMergePreset} from "./interface"; + +const Number64 = new NumberUintType({byteLength: 8}); + +export const MergePreset = new ContainerType({ + fields: { + MAX_BYTES_PER_TRANSACTION: Number64, + MAX_TRANSACTIONS_PER_PAYLOAD: Number64, + BYTES_PER_LOGS_BLOOM: Number64, + MAX_EXTRA_DATA_BYTES: Number64, + }, + // Expected and container fields are the same here + expectedCase: "notransform", +}); diff --git a/packages/params/src/preset/ssz.ts b/packages/params/src/preset/ssz.ts index f55ea2285e09..e142aab99271 100644 --- a/packages/params/src/preset/ssz.ts +++ b/packages/params/src/preset/ssz.ts @@ -5,11 +5,13 @@ import {ContainerType} from "@chainsafe/ssz"; import {IBeaconPreset} from "./interface"; import {Phase0Preset} from "./phase0"; import {AltairPreset} from "./altair"; +import {MergePreset} from "./merge"; export const BeaconPreset = new ContainerType({ fields: { ...Phase0Preset.fields, ...AltairPreset.fields, + ...MergePreset.fields, }, expectedCase: "notransform", }); diff --git a/packages/params/src/presets/mainnet/index.ts b/packages/params/src/presets/mainnet/index.ts index 3d872fce5c83..f6f3b7414591 100644 --- a/packages/params/src/presets/mainnet/index.ts +++ b/packages/params/src/presets/mainnet/index.ts @@ -2,10 +2,12 @@ import {IBeaconPreset} from "../../preset"; import {phase0} from "./phase0"; import {altair} from "./altair"; +import {merge} from "./merge"; export const commit = "v1.1.0-alpha.7"; export const preset: IBeaconPreset = { ...phase0, ...altair, + ...merge, }; diff --git a/packages/params/src/presets/mainnet/merge.ts b/packages/params/src/presets/mainnet/merge.ts new file mode 100644 index 000000000000..f4d5a6f9b806 --- /dev/null +++ b/packages/params/src/presets/mainnet/merge.ts @@ -0,0 +1,9 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {IMergePreset} from "../../preset"; + +export const merge: IMergePreset = { + MAX_BYTES_PER_TRANSACTION: 1073741824, + MAX_TRANSACTIONS_PER_PAYLOAD: 1048576, + BYTES_PER_LOGS_BLOOM: 256, + MAX_EXTRA_DATA_BYTES: 32, +}; diff --git a/packages/params/src/presets/minimal/index.ts b/packages/params/src/presets/minimal/index.ts index 3d872fce5c83..f6f3b7414591 100644 --- a/packages/params/src/presets/minimal/index.ts +++ b/packages/params/src/presets/minimal/index.ts @@ -2,10 +2,12 @@ import {IBeaconPreset} from "../../preset"; import {phase0} from "./phase0"; import {altair} from "./altair"; +import {merge} from "./merge"; export const commit = "v1.1.0-alpha.7"; export const preset: IBeaconPreset = { ...phase0, ...altair, + ...merge, }; diff --git a/packages/params/src/presets/minimal/merge.ts b/packages/params/src/presets/minimal/merge.ts new file mode 100644 index 000000000000..f4d5a6f9b806 --- /dev/null +++ b/packages/params/src/presets/minimal/merge.ts @@ -0,0 +1,9 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {IMergePreset} from "../../preset"; + +export const merge: IMergePreset = { + MAX_BYTES_PER_TRANSACTION: 1073741824, + MAX_TRANSACTIONS_PER_PAYLOAD: 1048576, + BYTES_PER_LOGS_BLOOM: 256, + MAX_EXTRA_DATA_BYTES: 32, +}; diff --git a/packages/types/src/merge/sszTypes.ts b/packages/types/src/merge/sszTypes.ts index d4b41348337d..5895cb3be8ac 100644 --- a/packages/types/src/merge/sszTypes.ts +++ b/packages/types/src/merge/sszTypes.ts @@ -1,20 +1,9 @@ -import { - byteType, - ByteVectorType, - ContainerType, - List, - ListType, - RootType, - Union, - UnionType, - Vector, - VectorType, -} from "@chainsafe/ssz"; +import {byteType, ByteVectorType, ContainerType, List, ListType, RootType, Vector, VectorType} from "@chainsafe/ssz"; import { BYTES_PER_LOGS_BLOOM, HISTORICAL_ROOTS_LIMIT, MAX_TRANSACTIONS_PER_PAYLOAD, - MAX_BYTES_PER_OPAQUE_TRANSACTION, + MAX_BYTES_PER_TRANSACTION, MAX_EXTRA_DATA_BYTES, SLOTS_PER_HISTORICAL_ROOT, } from "@chainsafe/lodestar-params"; @@ -34,18 +23,17 @@ const typesRef = new LazyVariable<{ }>(); /** - * ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION] + * ByteList[MAX_BYTES_PER_TRANSACTION] * * Spec v1.0.1 */ -export const OpaqueTransaction = new ListType({elementType: byteType, limit: MAX_BYTES_PER_OPAQUE_TRANSACTION}); +export const Transaction = new ListType({elementType: byteType, limit: MAX_BYTES_PER_TRANSACTION}); /** * Union[OpaqueTransaction] * * Spec v1.0.1 */ -export const Transaction = new UnionType>({types: [OpaqueTransaction]}); export const Transactions = new ListType>({ elementType: Transaction, limit: MAX_TRANSACTIONS_PER_PAYLOAD, diff --git a/packages/types/src/merge/types.ts b/packages/types/src/merge/types.ts index 9159ef16e8e4..d2a4acb4bb7b 100644 --- a/packages/types/src/merge/types.ts +++ b/packages/types/src/merge/types.ts @@ -50,7 +50,7 @@ export interface BeaconState extends altair.BeaconState { latestExecutionPayloadHeader: ExecutionPayloadHeader; } -export interface PayloadAttributes{ +export interface PayloadAttributes { timestamp: Number64; random: Bytes32; feeRecipient: ExecutionAddress; From 484c8b834def66a99b0feba84c554a8f554634f6 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 14:21:00 +0530 Subject: [PATCH 05/23] remove extraneous p2p condition (2687) --- packages/lodestar/src/chain/validation/block.ts | 10 ---------- packages/types/src/merge/types.ts | 6 ------ 2 files changed, 16 deletions(-) diff --git a/packages/lodestar/src/chain/validation/block.ts b/packages/lodestar/src/chain/validation/block.ts index 37c92f5c40e9..d81c5f9abc34 100644 --- a/packages/lodestar/src/chain/validation/block.ts +++ b/packages/lodestar/src/chain/validation/block.ts @@ -8,7 +8,6 @@ import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants"; import {IBeaconChain} from "../interface"; import {BlockGossipError, BlockErrorCode, GossipAction} from "../errors"; import {RegenCaller} from "../regen"; -import {byteArrayEquals} from "../../util/bytes"; // TODO - TEMP: Placeholder, to be agreed with other clients const MAX_TRANSACTIONS_SIZE = 50e6; // 50MB @@ -118,15 +117,6 @@ export async function validateGossipBlock( }); } - // [REJECT] The execution payload block hash is not equal to the parent hash - // -- i.e. execution_payload.block_hash != execution_payload.parent_hash. - if (byteArrayEquals(executionPayload.blockHash, executionPayload.parentHash)) { - throw new BlockGossipError(GossipAction.REJECT, { - code: BlockErrorCode.SAME_PARENT_HASH, - blockHash: toHexString(executionPayload.blockHash), - }); - } - // [REJECT] The execution payload transaction list data is within expected size limits, the data MUST NOT be larger // than the SSZ list-limit, and a client MAY be more strict. const totalTransactionSize = getTotalTransactionsSize(executionPayload.transactions); diff --git a/packages/types/src/merge/types.ts b/packages/types/src/merge/types.ts index d2a4acb4bb7b..a702517635e5 100644 --- a/packages/types/src/merge/types.ts +++ b/packages/types/src/merge/types.ts @@ -50,12 +50,6 @@ export interface BeaconState extends altair.BeaconState { latestExecutionPayloadHeader: ExecutionPayloadHeader; } -export interface PayloadAttributes { - timestamp: Number64; - random: Bytes32; - feeRecipient: ExecutionAddress; -} - export type PowBlock = { blockHash: Root; parentHash: Root; From 6725f18a6abe624ce14f4173d1dc9d1fdf0edc49 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 14:42:48 +0530 Subject: [PATCH 06/23] params e2e test fix --- packages/params/src/presets/mainnet/index.ts | 2 +- packages/params/src/presets/minimal/index.ts | 2 +- packages/params/test/e2e/ensure-config-is-synced.test.ts | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/params/src/presets/mainnet/index.ts b/packages/params/src/presets/mainnet/index.ts index f6f3b7414591..723cda4d8ac0 100644 --- a/packages/params/src/presets/mainnet/index.ts +++ b/packages/params/src/presets/mainnet/index.ts @@ -4,7 +4,7 @@ import {phase0} from "./phase0"; import {altair} from "./altair"; import {merge} from "./merge"; -export const commit = "v1.1.0-alpha.7"; +export const commit = "v1.1.4"; export const preset: IBeaconPreset = { ...phase0, diff --git a/packages/params/src/presets/minimal/index.ts b/packages/params/src/presets/minimal/index.ts index f6f3b7414591..723cda4d8ac0 100644 --- a/packages/params/src/presets/minimal/index.ts +++ b/packages/params/src/presets/minimal/index.ts @@ -4,7 +4,7 @@ import {phase0} from "./phase0"; import {altair} from "./altair"; import {merge} from "./merge"; -export const commit = "v1.1.0-alpha.7"; +export const commit = "v1.1.4"; export const preset: IBeaconPreset = { ...phase0, diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index a639b43d4177..c4863e03bd99 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -10,11 +10,14 @@ import {loadConfigYaml} from "../yaml"; async function downloadRemoteConfig(preset: "mainnet" | "minimal", commit: string): Promise> { const phase0Url = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/phase0.yaml`; const altairUrl = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/altair.yaml`; + const mergeUrl = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/merge.yaml`; const phase0Res = await axios({url: phase0Url, timeout: 30 * 1000}); const altairRes = await axios({url: altairUrl, timeout: 30 * 1000}); + const mergeRes = await axios({url: mergeUrl, timeout: 30 * 1000}); return createIBeaconPreset({ ...loadConfigYaml(phase0Res.data), ...loadConfigYaml(altairRes.data), + ...loadConfigYaml(mergeRes.data), }); } From d4eb94e494c76368dea3b77d2d616cda854b575d Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 16:57:53 +0530 Subject: [PATCH 07/23] update penalty params for Merge (2698) --- .../src/allForks/block/slashValidator.ts | 7 ++++++- .../src/allForks/epoch/processSlashings.ts | 7 ++++++- .../beacon-state-transition/src/altair/epoch/balance.ts | 9 ++++++++- packages/params/src/index.ts | 3 +++ packages/params/src/preset/merge/interface.ts | 3 +++ packages/params/src/preset/merge/ssz.ts | 3 +++ packages/params/src/presets/mainnet/merge.ts | 4 ++++ packages/params/src/presets/minimal/merge.ts | 4 ++++ 8 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/beacon-state-transition/src/allForks/block/slashValidator.ts b/packages/beacon-state-transition/src/allForks/block/slashValidator.ts index b698226614aa..95e479bc3df3 100644 --- a/packages/beacon-state-transition/src/allForks/block/slashValidator.ts +++ b/packages/beacon-state-transition/src/allForks/block/slashValidator.ts @@ -4,6 +4,7 @@ import { ForkName, MIN_SLASHING_PENALTY_QUOTIENT, MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR, + MIN_SLASHING_PENALTY_QUOTIENT_MERGE, PROPOSER_REWARD_QUOTIENT, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR, @@ -35,7 +36,11 @@ export function slashValidatorAllForks( state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += BigInt(effectiveBalance); const minSlashingPenaltyQuotient = - fork === ForkName.phase0 ? MIN_SLASHING_PENALTY_QUOTIENT : MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR; + fork === ForkName.phase0 + ? MIN_SLASHING_PENALTY_QUOTIENT + : fork === ForkName.altair + ? MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR + : MIN_SLASHING_PENALTY_QUOTIENT_MERGE; decreaseBalance(state, slashedIndex, Math.floor(effectiveBalance / minSlashingPenaltyQuotient)); // apply proposer and whistleblower rewards diff --git a/packages/beacon-state-transition/src/allForks/epoch/processSlashings.ts b/packages/beacon-state-transition/src/allForks/epoch/processSlashings.ts index 6ea62a57d45e..df170cca5583 100644 --- a/packages/beacon-state-transition/src/allForks/epoch/processSlashings.ts +++ b/packages/beacon-state-transition/src/allForks/epoch/processSlashings.ts @@ -6,6 +6,7 @@ import { ForkName, PROPORTIONAL_SLASHING_MULTIPLIER, PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, } from "@chainsafe/lodestar-params"; import {decreaseBalance} from "../../util"; @@ -35,7 +36,11 @@ export function processSlashingsAllForks( const totalSlashings = Array.from(readonlyValues(state.slashings)).reduce((a, b) => a + b, BigInt(0)); const proportionalSlashingMultiplier = - fork === ForkName.phase0 ? PROPORTIONAL_SLASHING_MULTIPLIER : PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR; + fork === ForkName.phase0 + ? PROPORTIONAL_SLASHING_MULTIPLIER + : fork === ForkName.altair + ? PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR + : PROPORTIONAL_SLASHING_MULTIPLIER_MERGE; const adjustedTotalSlashingBalance = bigIntMin(totalSlashings * BigInt(proportionalSlashingMultiplier), totalBalance); const increment = EFFECTIVE_BALANCE_INCREMENT; diff --git a/packages/beacon-state-transition/src/altair/epoch/balance.ts b/packages/beacon-state-transition/src/altair/epoch/balance.ts index e360acf23e6e..67027c14c165 100644 --- a/packages/beacon-state-transition/src/altair/epoch/balance.ts +++ b/packages/beacon-state-transition/src/altair/epoch/balance.ts @@ -2,6 +2,7 @@ import {allForks, altair} from "@chainsafe/lodestar-types"; import { EFFECTIVE_BALANCE_INCREMENT, INACTIVITY_PENALTY_QUOTIENT_ALTAIR, + INACTIVITY_PENALTY_QUOTIENT_MERGE, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, @@ -27,6 +28,7 @@ interface IRewardPenaltyItem { timelyTargetPenalty: number; timelyHeadReward: number; } +import {ForkName} from "@chainsafe/lodestar-params"; /** * An aggregate of getFlagIndexDeltas and getInactivityPenaltyDeltas that loop through process.statuses 1 time instead of 4. @@ -56,7 +58,12 @@ export function getRewardsPenaltiesDeltas( // so there are limited values of them like 32000000000, 31000000000, 30000000000 const rewardPenaltyItemCache = new Map(); const {config, epochCtx} = state; - const penaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR; + const fork = config.getForkName(state.slot); + + const inactivityPenalityMultiplier = + fork === ForkName.altair ? INACTIVITY_PENALTY_QUOTIENT_ALTAIR : INACTIVITY_PENALTY_QUOTIENT_MERGE; + const penaltyDenominator = config.INACTIVITY_SCORE_BIAS * inactivityPenalityMultiplier; + const {statuses} = process; for (let i = 0; i < statuses.length; i++) { const status = statuses[i]; diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index aff4f972235f..d1f68f0583ea 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -68,6 +68,9 @@ export const { MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR, PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, + INACTIVITY_PENALTY_QUOTIENT_MERGE, + MIN_SLASHING_PENALTY_QUOTIENT_MERGE, + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, MAX_BYTES_PER_TRANSACTION, MAX_TRANSACTIONS_PER_PAYLOAD, BYTES_PER_LOGS_BLOOM, diff --git a/packages/params/src/preset/merge/interface.ts b/packages/params/src/preset/merge/interface.ts index 2aec19b6f5d1..d14f72c44f44 100644 --- a/packages/params/src/preset/merge/interface.ts +++ b/packages/params/src/preset/merge/interface.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ export interface IMergePreset { + INACTIVITY_PENALTY_QUOTIENT_MERGE: number; + MIN_SLASHING_PENALTY_QUOTIENT_MERGE: number; + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: number; MAX_BYTES_PER_TRANSACTION: number; MAX_TRANSACTIONS_PER_PAYLOAD: number; BYTES_PER_LOGS_BLOOM: number; diff --git a/packages/params/src/preset/merge/ssz.ts b/packages/params/src/preset/merge/ssz.ts index 7eb82ac683c6..aedd298409ba 100644 --- a/packages/params/src/preset/merge/ssz.ts +++ b/packages/params/src/preset/merge/ssz.ts @@ -8,6 +8,9 @@ const Number64 = new NumberUintType({byteLength: 8}); export const MergePreset = new ContainerType({ fields: { + INACTIVITY_PENALTY_QUOTIENT_MERGE: Number64, + MIN_SLASHING_PENALTY_QUOTIENT_MERGE: Number64, + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: Number64, MAX_BYTES_PER_TRANSACTION: Number64, MAX_TRANSACTIONS_PER_PAYLOAD: Number64, BYTES_PER_LOGS_BLOOM: Number64, diff --git a/packages/params/src/presets/mainnet/merge.ts b/packages/params/src/presets/mainnet/merge.ts index f4d5a6f9b806..0499f441898b 100644 --- a/packages/params/src/presets/mainnet/merge.ts +++ b/packages/params/src/presets/mainnet/merge.ts @@ -2,6 +2,10 @@ import {IMergePreset} from "../../preset"; export const merge: IMergePreset = { + INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216, + MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32, + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3, + MAX_BYTES_PER_TRANSACTION: 1073741824, MAX_TRANSACTIONS_PER_PAYLOAD: 1048576, BYTES_PER_LOGS_BLOOM: 256, diff --git a/packages/params/src/presets/minimal/merge.ts b/packages/params/src/presets/minimal/merge.ts index f4d5a6f9b806..0499f441898b 100644 --- a/packages/params/src/presets/minimal/merge.ts +++ b/packages/params/src/presets/minimal/merge.ts @@ -2,6 +2,10 @@ import {IMergePreset} from "../../preset"; export const merge: IMergePreset = { + INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216, + MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32, + PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3, + MAX_BYTES_PER_TRANSACTION: 1073741824, MAX_TRANSACTIONS_PER_PAYLOAD: 1048576, BYTES_PER_LOGS_BLOOM: 256, From 5c74efd2a9e481276a84aeaeb900af5daba761d4 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 6 Nov 2021 17:01:54 +0530 Subject: [PATCH 08/23] updating spec version --- packages/spec-test-runner/test/specTestVersioning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spec-test-runner/test/specTestVersioning.ts b/packages/spec-test-runner/test/specTestVersioning.ts index cf738e8be2d5..f9bde6acee25 100644 --- a/packages/spec-test-runner/test/specTestVersioning.ts +++ b/packages/spec-test-runner/test/specTestVersioning.ts @@ -8,5 +8,5 @@ import path from "path"; // The contents of this file MUST include the URL, version and target path, and nothing else. export const SPEC_TEST_REPO_URL = "https://github.com/ethereum/consensus-spec-tests"; -export const SPEC_TEST_VERSION = "v1.1.3"; +export const SPEC_TEST_VERSION = "v1.1.4"; export const SPEC_TEST_LOCATION = path.join(__dirname, "../spec-tests"); From 9266bd3bedeb90f43e860e6cc9f4437995f4af22 Mon Sep 17 00:00:00 2001 From: harkamal Date: Mon, 8 Nov 2021 20:49:17 +0530 Subject: [PATCH 09/23] spec runner merge sanity and operations fixes --- .../src/merge/block/index.ts | 8 +++- .../merge/block/processAttesterSlashing.ts | 17 +++++++ .../src/merge/block/processOperations.ts | 44 +++++++++++++++++++ .../merge/block/processProposerSlashing.ts | 17 +++++++ .../src/merge/epoch/index.ts | 1 + .../src/merge/epoch/processSlashings.ts | 8 ++++ .../src/merge/index.ts | 5 ++- .../test/spec/merge/epoch_processing.test.ts | 4 +- .../test/spec/merge/operations.test.ts | 20 ++++----- 9 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 packages/beacon-state-transition/src/merge/block/processAttesterSlashing.ts create mode 100644 packages/beacon-state-transition/src/merge/block/processOperations.ts create mode 100644 packages/beacon-state-transition/src/merge/block/processProposerSlashing.ts create mode 100644 packages/beacon-state-transition/src/merge/epoch/index.ts create mode 100644 packages/beacon-state-transition/src/merge/epoch/processSlashings.ts diff --git a/packages/beacon-state-transition/src/merge/block/index.ts b/packages/beacon-state-transition/src/merge/block/index.ts index ac8d3a53f915..ec25375e87fd 100644 --- a/packages/beacon-state-transition/src/merge/block/index.ts +++ b/packages/beacon-state-transition/src/merge/block/index.ts @@ -2,11 +2,15 @@ import {allForks, altair, merge} from "@chainsafe/lodestar-types"; import {CachedBeaconState} from "../../allForks/util"; import {processBlockHeader, processEth1Data, processRandao} from "../../allForks/block"; -import {processOperations} from "../../altair/block/processOperations"; +import {processOperations} from "./processOperations"; import {processSyncAggregate} from "../../altair/block/processSyncCommittee"; import {processExecutionPayload} from "./processExecutionPayload"; import {ExecutionEngine} from "../executionEngine"; import {isExecutionEnabled} from "../utils"; +import {processAttesterSlashing} from "./processAttesterSlashing"; +import {processProposerSlashing} from "./processProposerSlashing"; + +export {processOperations, processAttesterSlashing, processProposerSlashing}; export function processBlock( state: CachedBeaconState, @@ -23,6 +27,6 @@ export function processBlock( processRandao(state as CachedBeaconState, block, verifySignatures); processEth1Data(state as CachedBeaconState, block.body); - processOperations((state as unknown) as CachedBeaconState, block.body, verifySignatures); + processOperations(state, block.body, verifySignatures); processSyncAggregate((state as unknown) as CachedBeaconState, block, verifySignatures); } diff --git a/packages/beacon-state-transition/src/merge/block/processAttesterSlashing.ts b/packages/beacon-state-transition/src/merge/block/processAttesterSlashing.ts new file mode 100644 index 000000000000..10135559479c --- /dev/null +++ b/packages/beacon-state-transition/src/merge/block/processAttesterSlashing.ts @@ -0,0 +1,17 @@ +import {allForks, merge, phase0} from "@chainsafe/lodestar-types"; +import {ForkName} from "@chainsafe/lodestar-params"; +import {CachedBeaconState} from "../../allForks/util"; +import {processAttesterSlashing as processAttesterSlashingAllForks} from "../../allForks/block"; + +export function processAttesterSlashing( + state: CachedBeaconState, + attesterSlashing: phase0.AttesterSlashing, + verifySignatures = true +): void { + processAttesterSlashingAllForks( + ForkName.merge, + state as CachedBeaconState, + attesterSlashing, + verifySignatures + ); +} diff --git a/packages/beacon-state-transition/src/merge/block/processOperations.ts b/packages/beacon-state-transition/src/merge/block/processOperations.ts new file mode 100644 index 000000000000..7d625ee19f46 --- /dev/null +++ b/packages/beacon-state-transition/src/merge/block/processOperations.ts @@ -0,0 +1,44 @@ +import {readonlyValues} from "@chainsafe/ssz"; +import {altair, merge} from "@chainsafe/lodestar-types"; + +import {CachedBeaconState} from "../../allForks/util"; +import {processProposerSlashing} from "./processProposerSlashing"; +import {processAttesterSlashing} from "./processAttesterSlashing"; +import {processAttestations} from "../../altair/block/processAttestation"; +import {processDeposit} from "../../altair/block/processDeposit"; +import {processVoluntaryExit} from "../../altair/block/processVoluntaryExit"; +import {MAX_DEPOSITS} from "@chainsafe/lodestar-params"; + +export function processOperations( + state: CachedBeaconState, + body: merge.BeaconBlockBody, + verifySignatures = true +): void { + // verify that outstanding deposits are processed up to the maximum number of deposits + const maxDeposits = Math.min(MAX_DEPOSITS, state.eth1Data.depositCount - state.eth1DepositIndex); + if (body.deposits.length !== maxDeposits) { + throw new Error( + `Block contains incorrect number of deposits: depositCount=${body.deposits.length} expected=${maxDeposits}` + ); + } + + for (const proposerSlashing of readonlyValues(body.proposerSlashings)) { + processProposerSlashing(state, proposerSlashing, verifySignatures); + } + for (const attesterSlashing of readonlyValues(body.attesterSlashings)) { + processAttesterSlashing(state, attesterSlashing, verifySignatures); + } + + processAttestations( + (state as unknown) as CachedBeaconState, + Array.from(readonlyValues(body.attestations)), + verifySignatures + ); + + for (const deposit of readonlyValues(body.deposits)) { + processDeposit((state as unknown) as CachedBeaconState, deposit); + } + for (const voluntaryExit of readonlyValues(body.voluntaryExits)) { + processVoluntaryExit((state as unknown) as CachedBeaconState, voluntaryExit, verifySignatures); + } +} diff --git a/packages/beacon-state-transition/src/merge/block/processProposerSlashing.ts b/packages/beacon-state-transition/src/merge/block/processProposerSlashing.ts new file mode 100644 index 000000000000..4162fc13c3c3 --- /dev/null +++ b/packages/beacon-state-transition/src/merge/block/processProposerSlashing.ts @@ -0,0 +1,17 @@ +import {allForks, merge, phase0} from "@chainsafe/lodestar-types"; +import {ForkName} from "@chainsafe/lodestar-params"; +import {CachedBeaconState} from "../../allForks/util"; +import {processProposerSlashing as processProposerSlashingAllForks} from "../../allForks/block"; + +export function processProposerSlashing( + state: CachedBeaconState, + proposerSlashing: phase0.ProposerSlashing, + verifySignatures = true +): void { + processProposerSlashingAllForks( + ForkName.merge, + state as CachedBeaconState, + proposerSlashing, + verifySignatures + ); +} diff --git a/packages/beacon-state-transition/src/merge/epoch/index.ts b/packages/beacon-state-transition/src/merge/epoch/index.ts new file mode 100644 index 000000000000..5cf1ff67e89d --- /dev/null +++ b/packages/beacon-state-transition/src/merge/epoch/index.ts @@ -0,0 +1 @@ +export {processSlashings} from "./processSlashings"; diff --git a/packages/beacon-state-transition/src/merge/epoch/processSlashings.ts b/packages/beacon-state-transition/src/merge/epoch/processSlashings.ts new file mode 100644 index 000000000000..579915b2691a --- /dev/null +++ b/packages/beacon-state-transition/src/merge/epoch/processSlashings.ts @@ -0,0 +1,8 @@ +import {allForks, merge} from "@chainsafe/lodestar-types"; +import {ForkName} from "@chainsafe/lodestar-params"; +import {CachedBeaconState, IEpochProcess} from "../../allForks/util"; +import {processSlashingsAllForks} from "../../allForks/epoch/processSlashings"; + +export function processSlashings(state: CachedBeaconState, epochProcess: IEpochProcess): void { + processSlashingsAllForks(ForkName.merge, state as CachedBeaconState, epochProcess); +} diff --git a/packages/beacon-state-transition/src/merge/index.ts b/packages/beacon-state-transition/src/merge/index.ts index e94ddb31653a..273834de561d 100644 --- a/packages/beacon-state-transition/src/merge/index.ts +++ b/packages/beacon-state-transition/src/merge/index.ts @@ -1,5 +1,6 @@ -export {processBlock} from "./block"; -export {upgradeState} from "./upgradeState"; +export * from "./block"; +export * from "./epoch"; +export * from "./upgradeState"; export * from "./utils"; // re-export merge lodestar types for ergonomic usage downstream diff --git a/packages/spec-test-runner/test/spec/merge/epoch_processing.test.ts b/packages/spec-test-runner/test/spec/merge/epoch_processing.test.ts index c5ae9039f0d6..52b6c21df8a6 100644 --- a/packages/spec-test-runner/test/spec/merge/epoch_processing.test.ts +++ b/packages/spec-test-runner/test/spec/merge/epoch_processing.test.ts @@ -1,4 +1,4 @@ -import {allForks, altair} from "@chainsafe/lodestar-beacon-state-transition"; +import {allForks, altair, merge} from "@chainsafe/lodestar-beacon-state-transition"; import {processParticipationRecordUpdates} from "@chainsafe/lodestar-beacon-state-transition/src/phase0/epoch/processParticipationRecordUpdates"; import {ForkName} from "@chainsafe/lodestar-params"; import {EpochProcessFn, epochProcessing} from "../allForks/epochProcessing"; @@ -18,7 +18,7 @@ epochProcessing(ForkName.merge, { randao_mixes_reset: allForks.processRandaoMixesReset, registry_updates: allForks.processRegistryUpdates, rewards_and_penalties: altair.processRewardsAndPenalties as EpochProcessFn, - slashings: altair.processSlashings as EpochProcessFn, + slashings: merge.processSlashings as EpochProcessFn, slashings_reset: allForks.processSlashingsReset, sync_committee_updates: altair.processSyncCommitteeUpdates as EpochProcessFn, }); diff --git a/packages/spec-test-runner/test/spec/merge/operations.test.ts b/packages/spec-test-runner/test/spec/merge/operations.test.ts index b05f590b7e7a..2f96a7a070ac 100644 --- a/packages/spec-test-runner/test/spec/merge/operations.test.ts +++ b/packages/spec-test-runner/test/spec/merge/operations.test.ts @@ -1,5 +1,5 @@ -import {CachedBeaconState, allForks, altair} from "@chainsafe/lodestar-beacon-state-transition"; -import {phase0, merge, ssz} from "@chainsafe/lodestar-types"; +import {CachedBeaconState, allForks, altair, merge} from "@chainsafe/lodestar-beacon-state-transition"; +import {phase0, ssz} from "@chainsafe/lodestar-types"; import {ForkName} from "@chainsafe/lodestar-params"; import {IBaseSpecTest} from "../type"; import {operations, BlockProcessFn} from "../allForks/operations"; @@ -9,7 +9,7 @@ import {processExecutionPayload} from "@chainsafe/lodestar-beacon-state-transiti /* eslint-disable @typescript-eslint/naming-convention */ // Define above to re-use in sync_aggregate and sync_aggregate_random -const sync_aggregate: BlockProcessFn = ( +const sync_aggregate: BlockProcessFn = ( state, testCase: IBaseSpecTest & {sync_aggregate: altair.SyncAggregate} ) => { @@ -19,17 +19,17 @@ const sync_aggregate: BlockProcessFn = ( block.slot = state.slot; block.body.syncAggregate = testCase["sync_aggregate"]; - altair.processSyncAggregate(state, block); + altair.processSyncAggregate((state as unknown) as CachedBeaconState, block); }; -operations(ForkName.merge, { +operations(ForkName.merge, { attestation: (state, testCase: IBaseSpecTest & {attestation: phase0.Attestation}) => { - altair.processAttestations(state, [testCase.attestation]); + altair.processAttestations((state as unknown) as CachedBeaconState, [testCase.attestation]); }, attester_slashing: (state, testCase: IBaseSpecTest & {attester_slashing: phase0.AttesterSlashing}) => { const verify = !!testCase.meta && !!testCase.meta.blsSetting && testCase.meta.blsSetting === BigInt(1); - altair.processAttesterSlashing(state, testCase.attester_slashing, verify); + merge.processAttesterSlashing(state, testCase.attester_slashing, verify); }, block_header: (state, testCase: IBaseSpecTest & {block: altair.BeaconBlock}) => { @@ -37,18 +37,18 @@ operations(ForkName.merge, { }, deposit: (state, testCase: IBaseSpecTest & {deposit: phase0.Deposit}) => { - altair.processDeposit(state, testCase.deposit); + altair.processDeposit((state as unknown) as CachedBeaconState, testCase.deposit); }, proposer_slashing: (state, testCase: IBaseSpecTest & {proposer_slashing: phase0.ProposerSlashing}) => { - altair.processProposerSlashing(state, testCase.proposer_slashing); + merge.processProposerSlashing(state, testCase.proposer_slashing); }, sync_aggregate, sync_aggregate_random: sync_aggregate, voluntary_exit: (state, testCase: IBaseSpecTest & {voluntary_exit: phase0.SignedVoluntaryExit}) => { - altair.processVoluntaryExit(state, testCase.voluntary_exit); + altair.processVoluntaryExit((state as unknown) as CachedBeaconState, testCase.voluntary_exit); }, execution_payload: ( From 5fa8242081363034fd5dbdb755edc899537d0212 Mon Sep 17 00:00:00 2001 From: harkamal Date: Mon, 8 Nov 2021 21:58:31 +0530 Subject: [PATCH 10/23] removing the beacon block gossip validations as per 1.1.4 --- .../lodestar/src/chain/validation/block.ts | 67 ++++++------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/packages/lodestar/src/chain/validation/block.ts b/packages/lodestar/src/chain/validation/block.ts index d81c5f9abc34..e106973a1184 100644 --- a/packages/lodestar/src/chain/validation/block.ts +++ b/packages/lodestar/src/chain/validation/block.ts @@ -9,9 +9,6 @@ import {IBeaconChain} from "../interface"; import {BlockGossipError, BlockErrorCode, GossipAction} from "../errors"; import {RegenCaller} from "../regen"; -// TODO - TEMP: Placeholder, to be agreed with other clients -const MAX_TRANSACTIONS_SIZE = 50e6; // 50MB - export async function validateGossipBlock( config: IChainForkConfig, chain: IBeaconChain, @@ -92,43 +89,6 @@ export async function validateGossipBlock( }); } - // Extra conditions for merge fork blocks - if (fork === ForkName.merge) { - if (!merge.isMergeBlockBodyType(block.body)) throw Error("Not merge block type"); - const executionPayload = block.body.executionPayload; - - // [REJECT] The block's execution payload timestamp is correct with respect to the slot - // -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot). - const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime); - if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) { - throw new BlockGossipError(GossipAction.REJECT, { - code: BlockErrorCode.INCORRECT_TIMESTAMP, - timestamp: executionPayload.timestamp, - expectedTimestamp, - }); - } - - // [REJECT] Gas used is less than the gas limit -- i.e. execution_payload.gas_used <= execution_payload.gas_limit. - if (executionPayload.gasUsed > executionPayload.gasLimit) { - throw new BlockGossipError(GossipAction.REJECT, { - code: BlockErrorCode.TOO_MUCH_GAS_USED, - gasUsed: executionPayload.gasUsed, - gasLimit: executionPayload.gasLimit, - }); - } - - // [REJECT] The execution payload transaction list data is within expected size limits, the data MUST NOT be larger - // than the SSZ list-limit, and a client MAY be more strict. - const totalTransactionSize = getTotalTransactionsSize(executionPayload.transactions); - if (totalTransactionSize > MAX_TRANSACTIONS_SIZE) { - throw new BlockGossipError(GossipAction.REJECT, { - code: BlockErrorCode.TRANSACTIONS_TOO_BIG, - size: totalTransactionSize, - max: MAX_TRANSACTIONS_SIZE, - }); - } - } - // getBlockSlotState also checks for whether the current finalized checkpoint is an ancestor of the block. // As a result, we throw an IGNORE (whereas the spec says we should REJECT for this scenario). // this is something we should change this in the future to make the code airtight to the spec. @@ -140,6 +100,25 @@ export async function validateGossipBlock( throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); }); + // Extra conditions for merge fork blocks + if (fork === ForkName.merge) { + if (!merge.isMergeBlockBodyType(block.body)) throw Error("Not merge block type"); + const executionPayload = block.body.executionPayload; + + if (merge.isMergeStateType(blockState) && merge.isExecutionEnabled(blockState, block.body)) { + // [REJECT] The block's execution payload timestamp is correct with respect to the slot + // -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot). + const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime); + if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) { + throw new BlockGossipError(GossipAction.REJECT, { + code: BlockErrorCode.INCORRECT_TIMESTAMP, + timestamp: executionPayload.timestamp, + expectedTimestamp, + }); + } + } + } + // [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey. const signatureSet = allForks.getProposerSignatureSet(blockState, signedBlock); // Don't batch so verification is not delayed @@ -171,11 +150,3 @@ export async function validateGossipBlock( chain.seenBlockProposers.add(blockSlot, proposerIndex); } - -function getTotalTransactionsSize(transactions: merge.Transaction[]): number { - let totalSize = 0; - for (const transaction of transactions) { - totalSize += transaction.value.length; - } - return totalSize; -} From 3832c5d053d76630521f2e974d21e5df6c75eb1f Mon Sep 17 00:00:00 2001 From: harkamal Date: Tue, 9 Nov 2021 23:19:04 +0530 Subject: [PATCH 11/23] feedback cleanup --- .../src/altair/epoch/balance.ts | 2 +- .../fork-choice/src/forkChoice/forkChoice.ts | 20 +++++++------ .../lodestar/src/chain/validation/block.ts | 5 ++-- .../test/e2e/ensure-config-is-synced.test.ts | 29 ++++++++++++------- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/beacon-state-transition/src/altair/epoch/balance.ts b/packages/beacon-state-transition/src/altair/epoch/balance.ts index 67027c14c165..e234896ec215 100644 --- a/packages/beacon-state-transition/src/altair/epoch/balance.ts +++ b/packages/beacon-state-transition/src/altair/epoch/balance.ts @@ -8,6 +8,7 @@ import { TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR, + ForkName, } from "@chainsafe/lodestar-params"; import { CachedBeaconState, @@ -28,7 +29,6 @@ interface IRewardPenaltyItem { timelyTargetPenalty: number; timelyHeadReward: number; } -import {ForkName} from "@chainsafe/lodestar-params"; /** * An aggregate of getFlagIndexDeltas and getInactivityPenaltyDeltas that loop through process.statuses 1 time instead of 4. diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 224238481f7a..7854dcbabb03 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -303,14 +303,12 @@ export class ForkChoice implements IForkChoice { const {powBlock, powBlockParent} = preCachedData || {}; if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); - if (!isValidTerminalPowBlock(this.config, powBlock, powBlockParent)) { - throw Error("Not valid terminal POW block"); - } + assertValidTerminalPowBlock(this.config, powBlock, powBlockParent); if ( this.config.TERMINAL_BLOCK_HASH !== ZERO_HASH && computeEpochAtSlot(block.slot) < this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH ) { - throw Error("Terminal activation epoch not reached"); + throw Error(`Terminal block activation epoch ${this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached`); } } @@ -916,11 +914,15 @@ export class ForkChoice implements IForkChoice { } } -function isValidTerminalPowBlock(config: IChainConfig, block: PowBlockHex, parent: PowBlockHex): boolean { - if (config.TERMINAL_BLOCK_HASH !== ZERO_HASH) { - return block.blockhash === toHexString(config.TERMINAL_BLOCK_HASH); - } +function assertValidTerminalPowBlock(config: IChainConfig, block: PowBlockHex, parent: PowBlockHex): void { + if (config.TERMINAL_BLOCK_HASH !== ZERO_HASH && block.blockhash !== toHexString(config.TERMINAL_BLOCK_HASH)) + throw Error( + `Invalid terminal POW block: blockHash ${block.blockhash} expected ${toHexString(config.TERMINAL_BLOCK_HASH)}` + ); const isTotalDifficultyReached = block.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; const isParentTotalDifficultyValid = parent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; - return isTotalDifficultyReached && isParentTotalDifficultyValid; + if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) + throw Error( + `Invalid terminal POW block: total difficulty not reached ${parent.totalDifficulty} < ${block.totalDifficulty}` + ); } diff --git a/packages/lodestar/src/chain/validation/block.ts b/packages/lodestar/src/chain/validation/block.ts index e106973a1184..b59fdd850c00 100644 --- a/packages/lodestar/src/chain/validation/block.ts +++ b/packages/lodestar/src/chain/validation/block.ts @@ -101,13 +101,12 @@ export async function validateGossipBlock( }); // Extra conditions for merge fork blocks + // [REJECT] The block's execution payload timestamp is correct with respect to the slot + // -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot). if (fork === ForkName.merge) { if (!merge.isMergeBlockBodyType(block.body)) throw Error("Not merge block type"); const executionPayload = block.body.executionPayload; - if (merge.isMergeStateType(blockState) && merge.isExecutionEnabled(blockState, block.body)) { - // [REJECT] The block's execution payload timestamp is correct with respect to the slot - // -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot). const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime); if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) { throw new BlockGossipError(GossipAction.REJECT, { diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index c4863e03bd99..e7baf032ae95 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -3,22 +3,29 @@ import axios from "axios"; import {createIBeaconPreset} from "../../src/utils"; import * as mainnet from "../../src/presets/mainnet"; import * as minimal from "../../src/presets/minimal"; +import {ForkName} from "../../src"; import {loadConfigYaml} from "../yaml"; // Not e2e, but slow. Run with e2e tests async function downloadRemoteConfig(preset: "mainnet" | "minimal", commit: string): Promise> { - const phase0Url = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/phase0.yaml`; - const altairUrl = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/altair.yaml`; - const mergeUrl = `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/merge.yaml`; - const phase0Res = await axios({url: phase0Url, timeout: 30 * 1000}); - const altairRes = await axios({url: altairUrl, timeout: 30 * 1000}); - const mergeRes = await axios({url: mergeUrl, timeout: 30 * 1000}); - return createIBeaconPreset({ - ...loadConfigYaml(phase0Res.data), - ...loadConfigYaml(altairRes.data), - ...loadConfigYaml(mergeRes.data), - }); + const urlByFork: Record = { + [ForkName.phase0]: `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/phase0.yaml`, + [ForkName.altair]: `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/altair.yaml`, + [ForkName.merge]: `https://raw.githubusercontent.com/ethereum/eth2.0-specs/${commit}/presets/${preset}/merge.yaml`, + }; + + const downloadedParams = await Promise.all( + Object.values(urlByFork).map((url) => + axios({url, timeout: 30 * 1000}).then((response) => loadConfigYaml(response.data)) + ) + ); + + // Merge all the fetched yamls for the different forks + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const configParams = Object.assign(...((downloadedParams as unknown) as [input: Record])); + + return createIBeaconPreset(configParams); } describe("Ensure config is synced", function () { From f633778b9948816fc688a068e2e25f48a8fb2f60 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 00:07:43 +0530 Subject: [PATCH 12/23] spec v1.1.5, fixed blockhash (2710), payloadid (2711) already changed, tbh activation check(2712) already correct --- .../fork-choice/src/forkChoice/forkChoice.ts | 21 +++++++++++-------- .../test/specTestVersioning.ts | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 7854dcbabb03..13bea7f6a3c3 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -304,11 +304,18 @@ export class ForkChoice implements IForkChoice { if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); assertValidTerminalPowBlock(this.config, powBlock, powBlockParent); - if ( - this.config.TERMINAL_BLOCK_HASH !== ZERO_HASH && - computeEpochAtSlot(block.slot) < this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - ) { - throw Error(`Terminal block activation epoch ${this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached`); + + if (this.config.TERMINAL_BLOCK_HASH !== ZERO_HASH) { + if (computeEpochAtSlot(block.slot) < this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH) + throw Error( + `Terminal block activation epoch ${this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached` + ); + if (block.body.executionPayload.parentHash !== this.config.TERMINAL_BLOCK_HASH) + throw new Error( + `Invalid terminal block hash, expected: ${toHexString( + this.config.TERMINAL_BLOCK_HASH + )}, actual: ${toHexString(block.body.executionPayload.parentHash)}` + ); } } @@ -915,10 +922,6 @@ export class ForkChoice implements IForkChoice { } function assertValidTerminalPowBlock(config: IChainConfig, block: PowBlockHex, parent: PowBlockHex): void { - if (config.TERMINAL_BLOCK_HASH !== ZERO_HASH && block.blockhash !== toHexString(config.TERMINAL_BLOCK_HASH)) - throw Error( - `Invalid terminal POW block: blockHash ${block.blockhash} expected ${toHexString(config.TERMINAL_BLOCK_HASH)}` - ); const isTotalDifficultyReached = block.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; const isParentTotalDifficultyValid = parent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) diff --git a/packages/spec-test-runner/test/specTestVersioning.ts b/packages/spec-test-runner/test/specTestVersioning.ts index f9bde6acee25..9133ff9edd15 100644 --- a/packages/spec-test-runner/test/specTestVersioning.ts +++ b/packages/spec-test-runner/test/specTestVersioning.ts @@ -8,5 +8,5 @@ import path from "path"; // The contents of this file MUST include the URL, version and target path, and nothing else. export const SPEC_TEST_REPO_URL = "https://github.com/ethereum/consensus-spec-tests"; -export const SPEC_TEST_VERSION = "v1.1.4"; +export const SPEC_TEST_VERSION = "v1.1.5"; export const SPEC_TEST_LOCATION = path.join(__dirname, "../spec-tests"); From b48c8974a45e6964b0b1a141f0f6f1bc1cdec311 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 10:56:38 +0530 Subject: [PATCH 13/23] kintsugi geth interop --- .github/workflows/test-sim-merge.yml | 2 +- kintsugi/genesis.json | 35 +++ kintsugi/geth/Dockerfile | 19 ++ .../lodestar/src/chain/factory/block/body.ts | 16 +- packages/lodestar/src/executionEngine/http.ts | 63 ++-- .../lodestar/src/executionEngine/interface.ts | 14 +- packages/lodestar/src/executionEngine/mock.ts | 24 +- .../lodestar/test/sim/merge-interop.test.ts | 61 ++-- .../lodestar/test/sim/merge-kintsugi-test.ts | 290 ++++++++++++++++++ packages/types/src/merge/sszTypes.ts | 5 - 10 files changed, 447 insertions(+), 82 deletions(-) create mode 100644 kintsugi/genesis.json create mode 100644 kintsugi/geth/Dockerfile create mode 100644 packages/lodestar/test/sim/merge-kintsugi-test.ts diff --git a/.github/workflows/test-sim-merge.yml b/.github/workflows/test-sim-merge.yml index 7584b4712286..8054fcd622eb 100644 --- a/.github/workflows/test-sim-merge.yml +++ b/.github/workflows/test-sim-merge.yml @@ -34,7 +34,7 @@ jobs: # Install Geth merge interop - uses: actions/setup-go@v2 - name: Clone Geth merge interop branch - run: git clone -b merge-interop-spec https://github.com/MariusVanDerWijden/go-ethereum.git + run: git clone -b kintsugi-spec https://github.com/MariusVanDerWijden/go-ethereum.git - name: Build Geth run: cd go-ethereum && make diff --git a/kintsugi/genesis.json b/kintsugi/genesis.json new file mode 100644 index 000000000000..b36b848de510 --- /dev/null +++ b/kintsugi/genesis.json @@ -0,0 +1,35 @@ +{ + "config": { + "chainId":1, + "homesteadBlock":0, + "eip150Block":0, + "eip155Block":0, + "eip158Block":0, + "byzantiumBlock":0, + "constantinopleBlock":0, + "petersburgBlock":0, + "istanbulBlock":0, + "muirGlacierBlock":0, + "berlinBlock":0, + "londonBlock":0, + "clique": { + "period": 5, + "epoch": 30000 + }, + "terminalTotalDifficulty":0 + }, + "nonce":"0x42", + "timestamp":"0x0", + "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit":"0x1C9C380", + "difficulty":"0x400000000", + "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase":"0x0000000000000000000000000000000000000000", + "alloc":{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b":{"balance":"0x6d6172697573766477000000"} + }, + "number":"0x0", + "gasUsed":"0x0", + "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas":"0x7" +} diff --git a/kintsugi/geth/Dockerfile b/kintsugi/geth/Dockerfile new file mode 100644 index 000000000000..124e13d2d273 --- /dev/null +++ b/kintsugi/geth/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.17-alpine as builder +RUN apk add --no-cache gcc musl-dev linux-headers git +WORKDIR /usr/app + +RUN git clone -b kintsugi-spec https://github.com/MariusVanDerWijden/go-ethereum.git +RUN cd /usr/app/go-ethereum && go run build/ci.go install ./cmd/geth + + +# Pull Geth into a second stage deploy alpine container +FROM alpine:latest +RUN apk add --no-cache bash curl + +RUN apk add --no-cache ca-certificates +COPY --from=builder usr/app/go-ethereum/build/bin/geth /usr/local/bin/ + +WORKDIR /usr/app + +EXPOSE 30303 30303/udp +ENTRYPOINT ["geth"] diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index 293959049160..f5d47821f09f 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -26,7 +26,6 @@ import { import {IBeaconChain} from "../../interface"; import {PayloadId} from "../../../executionEngine/interface"; import {ZERO_HASH, ZERO_HASH_HEX} from "../../../constants"; -import {bytesToData, numToQuantity} from "../../../eth1/provider/utils"; export async function assembleBody( chain: IBeaconChain, @@ -143,14 +142,13 @@ async function prepareExecutionPayload( parentHash = state.latestExecutionPayloadHeader.blockHash; } - const timestamp = numToQuantity(computeTimeAtSlot(chain.config, state.slot, state.genesisTime)); - const random = bytesToData(getRandaoMix(state, state.currentShuffling.epoch)); - const payloadAttributes = {timestamp, random, feeRecipient: bytesToData(feeRecipient)}; - return await chain.executionEngine.notifyForkchoiceUpdate( - bytesToData(parentHash), - finalizedBlockHash, - payloadAttributes - ); + const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime); + const random = getRandaoMix(state, state.currentShuffling.epoch); + return await chain.executionEngine.notifyForkchoiceUpdate(parentHash, finalizedBlockHash, { + timestamp, + random, + feeRecipient, + }); } /** process_sync_committee_contributions is implemented in syncCommitteeContribution.getSyncAggregate */ diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index fde8fe6ae437..fea3c7b250d7 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -1,5 +1,5 @@ import {AbortSignal} from "@chainsafe/abort-controller"; -import {merge, RootHex} from "@chainsafe/lodestar-types"; +import {merge, RootHex, Root} from "@chainsafe/lodestar-types"; import {JsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; import { bytesToData, @@ -46,7 +46,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { } /** - * `engine_executePayload` + * `engine_executePayloadV1` * * 1. Client software MUST validate the payload according to the execution environment rule set with modifications to this rule set defined in the Block Validity section of EIP-3675 and respond with the validation result. * 2. Client software MUST defer persisting a valid payload until the corresponding engine_consensusValidated message deems the payload valid with respect to the proof-of-stake consensus rules. @@ -57,7 +57,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { * If the parent block is a PoS block as per EIP-3675 definition, then the call MAY be responded with SYNCING status and sync process SHOULD be initiated accordingly. */ async executePayload(executionPayload: merge.ExecutionPayload): Promise { - const method = "engine_executePayload"; + const method = "engine_executePayloadV1"; const {status} = await this.rpc.fetch< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] @@ -82,26 +82,43 @@ export class ExecutionEngineHttp implements IExecutionEngine { * 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: RootHex, + headBlockHash: Root | RootHex, finalizedBlockHash: RootHex, - _payloadAttributes?: PayloadAttributes - ): Promise { - const method = "engine_forkchoiceUpdated"; - return this.rpc.fetch({ - method, - params: [{headBlockHash, finalizedBlockHash}], - }); + payloadAttributes?: PayloadAttributes + ): Promise { + const method = "engine_forkchoiceUpdatedV1"; + const headBlockHashData = typeof headBlockHash === "string" ? headBlockHash : bytesToData(headBlockHash); + const apiPayloadAttributes = payloadAttributes + ? { + timestamp: numToQuantity(payloadAttributes.timestamp), + random: bytesToData(payloadAttributes.random), + feeRecipient: bytesToData(payloadAttributes.feeRecipient), + } + : undefined; + return this.rpc + .fetch({ + method, + params: [ + {headBlockHash: headBlockHashData, safeBlockHash: headBlockHashData, finalizedBlockHash}, + apiPayloadAttributes, + ], + }) + .then(({payloadId}) => { + if ((payloadAttributes && payloadId === "0x") || (!payloadAttributes && payloadId !== "0x")) + throw Error("InvalidPayloadId"); + return payloadId !== "0x" ? payloadId : null; + }); } /** - * `engine_getPayload` + * `engine_getPayloadV1` * * 1. Given the payloadId client software MUST respond with the most recent version of the payload that is available in the corresponding building process at the time of receiving the call. * 2. The call MUST be responded with 5: Unavailable payload error if the building process identified by the payloadId doesn't exist. * 3. Client software MAY stop the corresponding building process after serving this call. */ async getPayload(payloadId: PayloadId): Promise { - const method = "engine_getPayload"; + const method = "engine_getPayloadV1"; const executionPayloadRpc = await this.rpc.fetch< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] @@ -120,7 +137,7 @@ type EngineApiRpcParamTypes = { /** * 1. Object - Instance of ExecutionPayload */ - engine_executePayload: [ExecutionPayloadRpc]; + engine_executePayloadV1: [ExecutionPayloadRpc]; /** * 1. Object - Payload validity status with respect to the consensus rules: * - blockHash: DATA, 32 Bytes - block hash value of the payload @@ -132,15 +149,14 @@ type EngineApiRpcParamTypes = { * - headBlockHash: DATA, 32 Bytes - block hash of the head of the canonical chain * - finalizedBlockHash: DATA, 32 Bytes - block hash of the most recent finalized block */ - engine_forkchoiceUpdated: [{headBlockHash: DATA; finalizedBlockHash: DATA}]; - /** - * 1. Object - The payload attributes: - */ - engine_preparePayload: [PayloadAttributes]; + engine_forkchoiceUpdatedV1: [ + {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA}, + {timestamp: QUANTITY; random: DATA; feeRecipient: DATA} | undefined + ]; /** * 1. payloadId: QUANTITY, 64 Bits - Identifier of the payload building process */ - engine_getPayload: [QUANTITY]; + engine_getPayloadV1: [QUANTITY]; }; type EngineApiRpcReturnTypes = { @@ -148,14 +164,13 @@ type EngineApiRpcReturnTypes = { * Object - Response object: * - status: String - the result of the payload execution: */ - engine_executePayload: {status: ExecutePayloadStatus}; + engine_executePayloadV1: {status: ExecutePayloadStatus}; engine_consensusValidated: void; - engine_forkchoiceUpdated: QUANTITY; + engine_forkchoiceUpdatedV1: {status: ExecutePayloadStatus; payloadId: QUANTITY}; /** * payloadId | Error: QUANTITY, 64 Bits - Identifier of the payload building process */ - engine_preparePayload: {payloadId: QUANTITY}; - engine_getPayload: ExecutionPayloadRpc; + engine_getPayloadV1: ExecutionPayloadRpc; }; type ExecutionPayloadRpc = { diff --git a/packages/lodestar/src/executionEngine/interface.ts b/packages/lodestar/src/executionEngine/interface.ts index 6ae00b079929..349ed75d0826 100644 --- a/packages/lodestar/src/executionEngine/interface.ts +++ b/packages/lodestar/src/executionEngine/interface.ts @@ -1,5 +1,5 @@ -import {merge, RootHex} from "@chainsafe/lodestar-types"; -import {DATA, QUANTITY} from "../eth1/provider/utils"; +import {merge, Root, RootHex} from "@chainsafe/lodestar-types"; +import {ByteVector} from "@chainsafe/ssz"; // An execution engine can produce a payload id anywhere the the uint64 range // Since we do no processing with this id, we have no need to deserialize it export type PayloadId = string; @@ -15,11 +15,11 @@ export enum ExecutePayloadStatus { export type PayloadAttributes = { /** QUANTITY, 64 Bits - value for the timestamp field of the new payload */ - timestamp: QUANTITY; + timestamp: number; /** DATA, 32 Bytes - value for the random field of the new payload */ - random: DATA; + random: Uint8Array | ByteVector; /** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */ - feeRecipient: DATA; + feeRecipient: Uint8Array | ByteVector; }; /** * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: @@ -52,10 +52,10 @@ export interface IExecutionEngine { * Should be called in response to fork-choice head and finalized events */ notifyForkchoiceUpdate( - headBlockHash: RootHex, + headBlockHash: Root | RootHex, finalizedBlockHash: RootHex, payloadAttributes?: PayloadAttributes - ): Promise; + ): Promise; /** * Given the payload_id, get_payload returns the most recent version of the execution payload that has been built diff --git a/packages/lodestar/src/executionEngine/mock.ts b/packages/lodestar/src/executionEngine/mock.ts index 57304f2461bd..ddc7d861dcfa 100644 --- a/packages/lodestar/src/executionEngine/mock.ts +++ b/packages/lodestar/src/executionEngine/mock.ts @@ -1,10 +1,9 @@ import crypto from "crypto"; -import {merge, RootHex} from "@chainsafe/lodestar-types"; -import {toHexString, fromHexString} from "@chainsafe/ssz"; +import {merge, RootHex, Root} from "@chainsafe/lodestar-types"; +import {toHexString} from "@chainsafe/ssz"; import {ZERO_HASH, ZERO_HASH_HEX} from "../constants"; import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes} from "./interface"; import {BYTES_PER_LOGS_BLOOM} from "@chainsafe/lodestar-params"; -import {quantityToNum} from "../eth1/provider/utils"; const INTEROP_GAS_LIMIT = 30e6; export type ExecutionEngineMockOpts = { @@ -70,21 +69,22 @@ export class ExecutionEngineMock implements IExecutionEngine { * 2. Client software MUST respond with 4: Unknown block error if the payload identified by either the headBlockHash or the finalizedBlockHash is unknown. */ async notifyForkchoiceUpdate( - headBlockHash: RootHex, + headBlockHash: Root, finalizedBlockHash: RootHex, payloadAttributes?: PayloadAttributes ): Promise { - if (!this.knownBlocks.has(headBlockHash)) { - throw Error(`Unknown headBlockHash ${headBlockHash}`); + const headBlockHashHex = toHexString(headBlockHash); + if (!this.knownBlocks.has(headBlockHashHex)) { + throw Error(`Unknown headBlockHash ${headBlockHashHex}`); } if (!this.knownBlocks.has(finalizedBlockHash)) { throw Error(`Unknown finalizedBlockHash ${finalizedBlockHash}`); } - this.headBlockRoot = headBlockHash; + this.headBlockRoot = headBlockHashHex; this.finalizedBlockRoot = finalizedBlockHash; - const parentHashHex = headBlockHash; + const parentHashHex = headBlockHashHex; const parentPayload = this.knownBlocks.get(parentHashHex); if (!parentPayload) { throw Error(`Unknown parentHash ${parentHashHex}`); @@ -94,16 +94,16 @@ export class ExecutionEngineMock implements IExecutionEngine { const payloadId = this.payloadId++; const payload: merge.ExecutionPayload = { - parentHash: fromHexString(headBlockHash), - coinbase: fromHexString(payloadAttributes.feeRecipient), + parentHash: headBlockHash, + coinbase: payloadAttributes.feeRecipient, stateRoot: crypto.randomBytes(32), receiptRoot: crypto.randomBytes(32), logsBloom: crypto.randomBytes(BYTES_PER_LOGS_BLOOM), - random: fromHexString(payloadAttributes.random), + random: payloadAttributes.random, blockNumber: parentPayload.blockNumber + 1, gasLimit: INTEROP_GAS_LIMIT, gasUsed: Math.floor(0.5 * INTEROP_GAS_LIMIT), - timestamp: quantityToNum(payloadAttributes.timestamp), + timestamp: payloadAttributes.timestamp, extraData: ZERO_HASH, baseFeePerGas: BigInt(0), blockHash: crypto.randomBytes(32), diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 0ad69bc492db..60fd0cea2688 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -20,6 +20,7 @@ import {simTestInfoTracker} from "../utils/node/simTest"; import {getAndInitDevValidators} from "../utils/node/validator"; import {Eth1Provider} from "../../src"; import {ZERO_HASH} from "../../src/constants"; +import {bytesToData, dataToBytes, quantityToNum} from "../../src/eth1/provider/utils"; // NOTE: Must specify GETH_BINARY_PATH ENV // Example: @@ -122,9 +123,10 @@ describe("executionEngine / ExecutionEngineHttp", function () { return {genesisBlockHash}; } + // runGethPreMerge has not yet been implemented/tested on kintsugi branch // Ref: https://notes.ethereum.org/_UH57VUPRrC-re3ubtmo2w // Build geth from source at branch https://github.com/ethereum/go-ethereum/pull/23607 - async function runGethPreMerge(): Promise<{genesisBlockHash: string}> { + async function _runGethPreMerge(): Promise<{genesisBlockHash: string}> { const privKey = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; const pubKey = "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"; const password = "12345678"; @@ -193,40 +195,51 @@ describe("executionEngine / ExecutionEngineHttp", function () { // 1. Prepare a payload /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_preparePayload","params":[{ - * "parentHash":gensisBlockHash, - * "timestamp":"0x5", - * "random":"0x0000000000000000000000000000000000000000000000000000000000000000", - * "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" - * }],"id":67}' http://localhost:8545 - * - * {"jsonrpc":"2.0","id":67,"result":{"payloadId":"0x0"}} - */ + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "safeBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "finalizedBlockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}, {"timestamp":"0x5", "random":"0x0000000000000000000000000000000000000000000000000000000000000000", "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}],"id":67}' http://localhost:8550 + **/ const preparePayloadParams = { // Note: this is created with a pre-defined genesis.json - timestamp: "0x5", - random: "0x0000000000000000000000000000000000000000000000000000000000000000", - feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + timestamp: quantityToNum("0x5"), + random: dataToBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), + feeRecipient: dataToBytes("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), }; + const finalizedBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; + const payloadId = await executionEngine.notifyForkchoiceUpdate( genesisBlockHash, - genesisBlockHash, + finalizedBlockHash, preparePayloadParams ); + if (!payloadId) throw Error("InvalidPayloadId"); + // 2. Get the payload + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0xa247243752eb10b4"],"id":67}' http://localhost:8550 + **/ const payload = await executionEngine.getPayload(payloadId); // 3. Execute the payload + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayloadV1","params":[{"parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","transactions":[]}],"id":67}' http://localhost:8550 + * **/ const payloadResult = await executionEngine.executePayload(payload); if (!payloadResult) { throw Error("getPayload returned payload that executePayload deems invalid"); } + // 4. Update the fork choice + + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "safeBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a"}, null],"id":67}' http://localhost:8550 + **/ + + await executionEngine.notifyForkchoiceUpdate(bytesToData(payload.blockHash), genesisBlockHash); + // Error cases // 1. unknown payload @@ -259,16 +272,16 @@ describe("executionEngine / ExecutionEngineHttp", function () { }); }); - it("Pre-merge, run for a few blocks", async function () { - console.log("\n\nPre-merge, run for a few blocks\n\n"); - const {genesisBlockHash} = await runGethPreMerge(); - await runNodeWithGeth.bind(this)({ - genesisBlockHash, - mergeEpoch: 1, - ttd: BigInt(terminalTotalDifficultyPreMerge), - testName: "pre-merge", - }); - }); + // it("Pre-merge, run for a few blocks", async function () { + // console.log("\n\nPre-merge, run for a few blocks\n\n"); + // const {genesisBlockHash} = await runGethPreMerge(); + // await runNodeWithGeth.bind(this)({ + // genesisBlockHash, + // mergeEpoch: 1, + // ttd: BigInt(terminalTotalDifficultyPreMerge), + // testName: "pre-merge", + // }); + // }); async function runNodeWithGeth( this: Context, diff --git a/packages/lodestar/test/sim/merge-kintsugi-test.ts b/packages/lodestar/test/sim/merge-kintsugi-test.ts new file mode 100644 index 000000000000..a91125fd1bf6 --- /dev/null +++ b/packages/lodestar/test/sim/merge-kintsugi-test.ts @@ -0,0 +1,290 @@ +import fs from "fs"; +import {Context} from "mocha"; +import {AbortController, AbortSignal} from "@chainsafe/abort-controller"; +import {fromHexString} from "@chainsafe/ssz"; +import {LogLevel, sleep, TimestampFormatCode} from "@chainsafe/lodestar-utils"; +import {SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; +import {IChainConfig} from "@chainsafe/lodestar-config"; +import {Epoch} from "@chainsafe/lodestar-types"; +import {ExecutionEngineHttp} from "../../src/executionEngine/http"; +import {shell} from "./shell"; +import {ChainEvent} from "../../src/chain"; +import {testLogger, TestLoggerOpts} from "../utils/logger"; +import {logFilesDir} from "./params"; +import {getDevBeaconNode} from "../utils/node/beacon"; +import {RestApiOptions} from "../../src/api"; +import {simTestInfoTracker} from "../utils/node/simTest"; +import {getAndInitDevValidators} from "../utils/node/validator"; +import {Eth1Provider} from "../../src"; +import {ZERO_HASH} from "../../src/constants"; +import {bytesToData, dataToBytes, quantityToNum} from "../../src/eth1/provider/utils"; + +// NOTE: This file has been tested on a running geth outside CI, might be reworked or scrapped once CI interop test file: merge-interop.test.ts works just fine. + +/* eslint-disable no-console, @typescript-eslint/naming-convention, quotes */ + +describe("executionEngine / ExecutionEngineHttp", function () { + this.timeout("10min"); + + const dataPath = fs.mkdtempSync("lodestar-test-merge-interop"); + const jsonRpcPort = 8545; + const enginePort = 8545; + const jsonRpcUrl = `http://localhost:${jsonRpcPort}`; + const engineApiUrl = `http://localhost:${enginePort}`; + + after(async () => { + await shell(`rm -rf ${dataPath}`); + }); + + const afterEachCallbacks: (() => Promise | void)[] = []; + afterEach(async () => { + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); + + // Ref: https://notes.ethereum.org/@9AeMAlpyQYaAAyuj47BzRw/rkwW3ceVY + // Build geth from source at branch https://github.com/ethereum/go-ethereum/pull/23607 + // $ ./go-ethereum/build/bin/geth --catalyst --datadir "~/ethereum/taunus" init genesis.json + // $ ./build/bin/geth --catalyst --http --ws -http.api "engine" --datadir "~/ethereum/taunus" console + async function runGethPostMerge(): Promise<{genesisBlockHash: string}> { + await shell(`rm -rf ${dataPath}`); + fs.mkdirSync(dataPath, {recursive: true}); + + const controller = new AbortController(); + + // Fetch genesis block hash + const genesisBlockHash = await getGenesisBlockHash(jsonRpcUrl, controller.signal); + return {genesisBlockHash}; + } + + it("Send stub payloads to Geth", async () => { + const {genesisBlockHash} = await runGethPostMerge(); + + const controller = new AbortController(); + const executionEngine = new ExecutionEngineHttp({urls: [engineApiUrl]}, controller.signal); + + // 1. Prepare a payload + + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "safeBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "finalizedBlockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}, {"timestamp":"0x5", "random":"0x0000000000000000000000000000000000000000000000000000000000000000", "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}],"id":67}' http://localhost:8550 + **/ + + const preparePayloadParams = { + // Note: this is created with a pre-defined genesis.json + timestamp: quantityToNum("0x5"), + random: dataToBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), + feeRecipient: dataToBytes("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + }; + + const finalizedBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; + + const payloadId = await executionEngine.notifyForkchoiceUpdate( + genesisBlockHash, + finalizedBlockHash, + preparePayloadParams + ); + + if (!payloadId) throw Error("InvalidPayloadId"); + + // 2. Get the payload + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0xa247243752eb10b4"],"id":67}' http://localhost:8550 + **/ + + const payload = await executionEngine.getPayload(payloadId); + + // 3. Execute the payload + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayloadV1","params":[{"parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","transactions":[]}],"id":67}' http://localhost:8550 + * **/ + + const payloadResult = await executionEngine.executePayload(payload); + if (!payloadResult) { + throw Error("getPayload returned payload that executePayload deems invalid"); + } + + // 4. Update the fork choice + + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "safeBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a"}, null],"id":67}' http://localhost:8550 + **/ + + await executionEngine.notifyForkchoiceUpdate(bytesToData(payload.blockHash), genesisBlockHash); + + // Error cases + // 1. unknown payload + + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayload", + * "params":["0x123"] + * ,"id":67}' http://localhost:8545 + */ + + // await executionEngine.getPayload(1234567); + + // 2. unknown header + + /** + * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_consensusValidated","params":[{ + * "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + * "status":"VALID" + * }],"id":67}' http://localhost:8545 + */ + }); + + it("Post-merge, run for a few blocks", async function () { + console.log("\n\nPost-merge, run for a few blocks\n\n"); + const {genesisBlockHash} = await runGethPostMerge(); + await runNodeWithGeth.bind(this)({ + genesisBlockHash, + mergeEpoch: 0, + ttd: BigInt(0), + testName: "post-merge", + }); + }); + + async function runNodeWithGeth( + this: Context, + { + genesisBlockHash, + mergeEpoch, + ttd, + testName, + }: {genesisBlockHash: string; mergeEpoch: Epoch; ttd: bigint; testName: string} + ): Promise { + const validatorClientCount = 1; + const validatorsPerClient = 32; + const event = ChainEvent.finalized; + + const testParams: Pick = { + SECONDS_PER_SLOT: 2, + }; + + // Should reach justification in 6 epochs max. + // Merge block happens at epoch 2 slot 4. Then 4 epochs to finalize + const expectedEpochsToFinish = 6; + // 1 epoch of margin of error + const epochsOfMargin = 1; + const timeoutSetupMargin = 5 * 1000; // Give extra 5 seconds of margin + + // delay a bit so regular sync sees it's up to date and sync is completed from the beginning + const genesisSlotsDelay = 3; + + const timeout = + ((epochsOfMargin + expectedEpochsToFinish) * SLOTS_PER_EPOCH + genesisSlotsDelay) * + testParams.SECONDS_PER_SLOT * + 1000; + + this.timeout(timeout + 2 * timeoutSetupMargin); + + const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; + + const testLoggerOpts: TestLoggerOpts = { + logLevel: LogLevel.info, + logFile: `${logFilesDir}/merge-interop-${testName}.log`, + timestampFormat: { + format: TimestampFormatCode.EpochSlot, + genesisTime, + slotsPerEpoch: SLOTS_PER_EPOCH, + secondsPerSlot: testParams.SECONDS_PER_SLOT, + }, + }; + const loggerNodeA = testLogger("Node-A", testLoggerOpts); + + const bn = await getDevBeaconNode({ + params: {...testParams, ALTAIR_FORK_EPOCH: 0, MERGE_FORK_EPOCH: mergeEpoch, TERMINAL_TOTAL_DIFFICULTY: ttd}, + options: { + api: {rest: {enabled: true} as RestApiOptions}, + sync: {isSingleNode: true}, + network: {discv5: null}, + eth1: {enabled: true, providerUrls: [jsonRpcUrl]}, + executionEngine: {urls: [engineApiUrl]}, + }, + validatorCount: validatorClientCount * validatorsPerClient, + logger: loggerNodeA, + genesisTime, + eth1BlockHash: fromHexString(genesisBlockHash), + }); + + afterEachCallbacks.push(async function () { + await bn.close(); + await sleep(1000); + }); + + const stopInfoTracker = simTestInfoTracker(bn, loggerNodeA); + + const validators = await getAndInitDevValidators({ + node: bn, + validatorsPerClient, + validatorClientCount, + startIndex: 0, + // At least one sim test must use the REST API for beacon <-> validator comms + useRestApi: true, + testLoggerOpts, + }); + + afterEachCallbacks.push(async function () { + await Promise.all(validators.map((v) => v.stop())); + }); + + await Promise.all(validators.map((v) => v.start())); + + await new Promise((resolve) => { + bn.chain.emitter.on(ChainEvent.finalized, (checkpoint) => { + // Resolve only if the finalized checkpoint includes execution payload + const finalizedBlock = bn.chain.forkChoice.getBlock(checkpoint.root); + if (finalizedBlock?.executionPayloadBlockHash !== null) { + console.log(`\nGot event ${event}, stopping validators and nodes\n`); + resolve(); + } + }); + }); + + // Stop chain and un-subscribe events so the execution engine won't update it's head + // Allow some time to broadcast finalized events and complete the importBlock routine + await Promise.all(validators.map((v) => v.stop())); + await bn.close(); + await sleep(500); + + // Assertions to make sure the end state is good + // 1. The proper head is set + const rpc = new Eth1Provider({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, {providerUrls: [jsonRpcUrl]}); + const consensusHead = bn.chain.forkChoice.getHead(); + const executionHeadBlockNumber = await rpc.getBlockNumber(); + const executionHeadBlock = await rpc.getBlockByNumber(executionHeadBlockNumber); + if (!executionHeadBlock) throw Error("Execution has not head block"); + if (consensusHead.executionPayloadBlockHash !== executionHeadBlock.hash) { + throw Error( + "Consensus head not equal to execution head: " + + JSON.stringify({ + executionHeadBlockNumber, + executionHeadBlockHash: executionHeadBlock.hash, + consensusHeadExecutionPayloadBlockHash: consensusHead.executionPayloadBlockHash, + consensusHeadSlot: consensusHead.slot, + }) + ); + } + + // wait for 1 slot to print current epoch stats + await sleep(1 * bn.config.SECONDS_PER_SLOT * 1000); + stopInfoTracker(); + console.log("\n\nDone\n\n"); + } +}); + +async function getGenesisBlockHash(url: string, signal: AbortSignal): Promise { + const eth1Provider = new Eth1Provider( + ({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH} as Partial) as IChainConfig, + {providerUrls: [url]}, + signal + ); + + const genesisBlock = await eth1Provider.getBlockByNumber(0); + if (!genesisBlock) { + throw Error("No genesis block available"); + } + + return genesisBlock.hash; +} diff --git a/packages/types/src/merge/sszTypes.ts b/packages/types/src/merge/sszTypes.ts index 5895cb3be8ac..b507680c0792 100644 --- a/packages/types/src/merge/sszTypes.ts +++ b/packages/types/src/merge/sszTypes.ts @@ -29,11 +29,6 @@ const typesRef = new LazyVariable<{ */ export const Transaction = new ListType({elementType: byteType, limit: MAX_BYTES_PER_TRANSACTION}); -/** - * Union[OpaqueTransaction] - * - * Spec v1.0.1 - */ export const Transactions = new ListType>({ elementType: Transaction, limit: MAX_TRANSACTIONS_PER_PAYLOAD, From e348906d74fc977af67898bc8b5dc48cff153537 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 12:05:42 +0530 Subject: [PATCH 14/23] ee test fixes --- packages/lodestar/src/executionEngine/http.ts | 24 ++++++++++--------- .../lodestar/src/executionEngine/interface.ts | 13 +++++++--- .../test/unit/executionEngine/http.test.ts | 9 +++---- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index fea3c7b250d7..6367a312b529 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -11,7 +11,7 @@ import { quantityToBigint, } from "../eth1/provider/utils"; import {IJsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; -import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes} from "./interface"; +import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes, ApiPayloadAttributes} from "./interface"; import {BYTES_PER_LOGS_BLOOM} from "@chainsafe/lodestar-params"; export type ExecutionEngineHttpOpts = { @@ -88,19 +88,21 @@ export class ExecutionEngineHttp implements IExecutionEngine { ): Promise { const method = "engine_forkchoiceUpdatedV1"; const headBlockHashData = typeof headBlockHash === "string" ? headBlockHash : bytesToData(headBlockHash); - const apiPayloadAttributes = payloadAttributes - ? { - timestamp: numToQuantity(payloadAttributes.timestamp), - random: bytesToData(payloadAttributes.random), - feeRecipient: bytesToData(payloadAttributes.feeRecipient), - } - : undefined; + const apiPayloadAttributes: [input?: ApiPayloadAttributes] = payloadAttributes + ? [ + { + timestamp: numToQuantity(payloadAttributes.timestamp), + random: bytesToData(payloadAttributes.random), + feeRecipient: bytesToData(payloadAttributes.feeRecipient), + }, + ] + : []; return this.rpc .fetch({ method, params: [ {headBlockHash: headBlockHashData, safeBlockHash: headBlockHashData, finalizedBlockHash}, - apiPayloadAttributes, + ...apiPayloadAttributes, ], }) .then(({payloadId}) => { @@ -150,8 +152,8 @@ type EngineApiRpcParamTypes = { * - finalizedBlockHash: DATA, 32 Bytes - block hash of the most recent finalized block */ engine_forkchoiceUpdatedV1: [ - {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA}, - {timestamp: QUANTITY; random: DATA; feeRecipient: DATA} | undefined + param1: {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA}, + payloadAttributes?: ApiPayloadAttributes ]; /** * 1. payloadId: QUANTITY, 64 Bits - Identifier of the payload building process diff --git a/packages/lodestar/src/executionEngine/interface.ts b/packages/lodestar/src/executionEngine/interface.ts index 349ed75d0826..e7ee3dc65e06 100644 --- a/packages/lodestar/src/executionEngine/interface.ts +++ b/packages/lodestar/src/executionEngine/interface.ts @@ -1,5 +1,6 @@ import {merge, Root, RootHex} from "@chainsafe/lodestar-types"; import {ByteVector} from "@chainsafe/ssz"; +import {DATA, QUANTITY} from "../eth1/provider/utils"; // An execution engine can produce a payload id anywhere the the uint64 range // Since we do no processing with this id, we have no need to deserialize it export type PayloadId = string; @@ -14,13 +15,19 @@ export enum ExecutePayloadStatus { } export type PayloadAttributes = { - /** QUANTITY, 64 Bits - value for the timestamp field of the new payload */ timestamp: number; - /** DATA, 32 Bytes - value for the random field of the new payload */ random: Uint8Array | ByteVector; - /** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */ feeRecipient: Uint8Array | ByteVector; }; + +export type ApiPayloadAttributes = { + /** QUANTITY, 64 Bits - value for the timestamp field of the new payload */ + timestamp: QUANTITY; + /** DATA, 32 Bytes - value for the random field of the new payload */ + random: DATA; + /** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */ + feeRecipient: DATA; +}; /** * Execution engine represents an abstract protocol to interact with execution clients. Potential transports include: * - JSON RPC over network diff --git a/packages/lodestar/test/unit/executionEngine/http.test.ts b/packages/lodestar/test/unit/executionEngine/http.test.ts index 69eeb176ba64..9c07b23c56a7 100644 --- a/packages/lodestar/test/unit/executionEngine/http.test.ts +++ b/packages/lodestar/test/unit/executionEngine/http.test.ts @@ -44,7 +44,7 @@ describe("ExecutionEngine / http", () => { * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayload","params":["0x0"],"id":67}' http://localhost:8545 */ - const request = {jsonrpc: "2.0", method: "engine_getPayload", params: ["0x0"]}; + const request = {jsonrpc: "2.0", method: "engine_getPayloadV1", params: ["0x0"]}; const response = { jsonrpc: "2.0", id: 67, @@ -81,7 +81,7 @@ describe("ExecutionEngine / http", () => { const request = { jsonrpc: "2.0", - method: "engine_executePayload", + method: "engine_executePayloadV1", params: [ { blockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", @@ -117,15 +117,16 @@ describe("ExecutionEngine / http", () => { const request = { jsonrpc: "2.0", - method: "engine_forkchoiceUpdated", + method: "engine_forkchoiceUpdatedV1", params: [ { headBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", + safeBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", finalizedBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", }, ], }; - returnValue = {jsonrpc: "2.0", id: 67, result: null}; + returnValue = {jsonrpc: "2.0", id: 67, result: {payloadId: "0x"}}; await executionEngine.notifyForkchoiceUpdate(request.params[0].headBlockHash, request.params[0].finalizedBlockHash); From a391ec6ef0c1580b9f3b22f9b67b56f3c510b538 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 19:15:09 +0530 Subject: [PATCH 15/23] assetterminalpowblock refac and root comparision fix --- kintsugi/{genesis.json => genesisPost.json} | 0 .../fork-choice/src/forkChoice/forkChoice.ts | 52 ++++++++++--------- .../lodestar/src/chain/factory/block/body.ts | 2 +- 3 files changed, 29 insertions(+), 25 deletions(-) rename kintsugi/{genesis.json => genesisPost.json} (100%) diff --git a/kintsugi/genesis.json b/kintsugi/genesisPost.json similarity index 100% rename from kintsugi/genesis.json rename to kintsugi/genesisPost.json diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 13bea7f6a3c3..0c5f38a416d2 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -16,7 +16,7 @@ import {ProtoArray} from "../protoArray/protoArray"; import {IForkChoiceMetrics} from "../metrics"; import {ForkChoiceError, ForkChoiceErrorCode, InvalidBlockCode, InvalidAttestationCode} from "./errors"; -import {IForkChoice, ILatestMessage, IQueuedAttestation, OnBlockPrecachedData, PowBlockHex} from "./interface"; +import {IForkChoice, ILatestMessage, IQueuedAttestation, OnBlockPrecachedData} from "./interface"; import {IForkChoiceStore, CheckpointWithHex, toCheckpointWithHex, equalCheckpointWithHex} from "./store"; /* eslint-disable max-len */ @@ -299,25 +299,8 @@ export class ForkChoice implements IForkChoice { merge.isMergeStateType(state) && merge.isMergeBlockBodyType(block.body) && merge.isMergeBlock(state, block.body) - ) { - const {powBlock, powBlockParent} = preCachedData || {}; - if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); - if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); - assertValidTerminalPowBlock(this.config, powBlock, powBlockParent); - - if (this.config.TERMINAL_BLOCK_HASH !== ZERO_HASH) { - if (computeEpochAtSlot(block.slot) < this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH) - throw Error( - `Terminal block activation epoch ${this.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached` - ); - if (block.body.executionPayload.parentHash !== this.config.TERMINAL_BLOCK_HASH) - throw new Error( - `Invalid terminal block hash, expected: ${toHexString( - this.config.TERMINAL_BLOCK_HASH - )}, actual: ${toHexString(block.body.executionPayload.parentHash)}` - ); - } - } + ) + assertValidTerminalPowBlock(this.config, (block as unknown) as merge.BeaconBlock, preCachedData); let shouldUpdateJustified = false; const {finalizedCheckpoint} = state; @@ -921,11 +904,32 @@ export class ForkChoice implements IForkChoice { } } -function assertValidTerminalPowBlock(config: IChainConfig, block: PowBlockHex, parent: PowBlockHex): void { - const isTotalDifficultyReached = block.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; - const isParentTotalDifficultyValid = parent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; +function assertValidTerminalPowBlock( + config: IChainConfig, + block: merge.BeaconBlock, + preCachedData?: OnBlockPrecachedData +): void { + const {powBlock, powBlockParent} = preCachedData || {}; + if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); + if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); + + if (!ssz.Root.equals(config.TERMINAL_BLOCK_HASH, ZERO_HASH)) { + if (computeEpochAtSlot(block.slot) < config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH) + throw Error(`Terminal block activation epoch ${config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached`); + + // powBock.blockhash is hex, so we just pick the corresponding root + if (!ssz.Root.equals(block.body.executionPayload.parentHash, config.TERMINAL_BLOCK_HASH)) + throw new Error( + `Invalid terminal block hash, expected: ${toHexString(config.TERMINAL_BLOCK_HASH)}, actual: ${toHexString( + block.body.executionPayload.parentHash + )}` + ); + } + + const isTotalDifficultyReached = powBlock.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; + const isParentTotalDifficultyValid = powBlockParent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) throw Error( - `Invalid terminal POW block: total difficulty not reached ${parent.totalDifficulty} < ${block.totalDifficulty}` + `Invalid terminal POW block: total difficulty not reached ${powBlockParent.totalDifficulty} < ${powBlock.totalDifficulty}` ); } diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index f5d47821f09f..11359e811e3a 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -125,7 +125,7 @@ async function prepareExecutionPayload( let parentHash: Root; if (!merge.isMergeComplete(state)) { if ( - chain.config.TERMINAL_BLOCK_HASH !== ZERO_HASH && + !ssz.Root.equals(chain.config.TERMINAL_BLOCK_HASH, ZERO_HASH) && getCurrentEpoch(state) < chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH ) return null; From b30954075f612b63d982070f68def596f9703240 Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 19:46:06 +0530 Subject: [PATCH 16/23] runGethPreMerge test case --- .../lodestar/test/sim/merge-interop.test.ts | 23 +- .../lodestar/test/sim/merge-kintsugi-test.ts | 290 ------------------ 2 files changed, 11 insertions(+), 302 deletions(-) delete mode 100644 packages/lodestar/test/sim/merge-kintsugi-test.ts diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 60fd0cea2688..4692a9ce215d 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -123,10 +123,9 @@ describe("executionEngine / ExecutionEngineHttp", function () { return {genesisBlockHash}; } - // runGethPreMerge has not yet been implemented/tested on kintsugi branch // Ref: https://notes.ethereum.org/_UH57VUPRrC-re3ubtmo2w // Build geth from source at branch https://github.com/ethereum/go-ethereum/pull/23607 - async function _runGethPreMerge(): Promise<{genesisBlockHash: string}> { + async function runGethPreMerge(): Promise<{genesisBlockHash: string}> { const privKey = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; const pubKey = "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"; const password = "12345678"; @@ -272,16 +271,16 @@ describe("executionEngine / ExecutionEngineHttp", function () { }); }); - // it("Pre-merge, run for a few blocks", async function () { - // console.log("\n\nPre-merge, run for a few blocks\n\n"); - // const {genesisBlockHash} = await runGethPreMerge(); - // await runNodeWithGeth.bind(this)({ - // genesisBlockHash, - // mergeEpoch: 1, - // ttd: BigInt(terminalTotalDifficultyPreMerge), - // testName: "pre-merge", - // }); - // }); + it("Pre-merge, run for a few blocks", async function () { + console.log("\n\nPre-merge, run for a few blocks\n\n"); + const {genesisBlockHash} = await runGethPreMerge(); + await runNodeWithGeth.bind(this)({ + genesisBlockHash, + mergeEpoch: 1, + ttd: BigInt(terminalTotalDifficultyPreMerge), + testName: "pre-merge", + }); + }); async function runNodeWithGeth( this: Context, diff --git a/packages/lodestar/test/sim/merge-kintsugi-test.ts b/packages/lodestar/test/sim/merge-kintsugi-test.ts deleted file mode 100644 index a91125fd1bf6..000000000000 --- a/packages/lodestar/test/sim/merge-kintsugi-test.ts +++ /dev/null @@ -1,290 +0,0 @@ -import fs from "fs"; -import {Context} from "mocha"; -import {AbortController, AbortSignal} from "@chainsafe/abort-controller"; -import {fromHexString} from "@chainsafe/ssz"; -import {LogLevel, sleep, TimestampFormatCode} from "@chainsafe/lodestar-utils"; -import {SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; -import {IChainConfig} from "@chainsafe/lodestar-config"; -import {Epoch} from "@chainsafe/lodestar-types"; -import {ExecutionEngineHttp} from "../../src/executionEngine/http"; -import {shell} from "./shell"; -import {ChainEvent} from "../../src/chain"; -import {testLogger, TestLoggerOpts} from "../utils/logger"; -import {logFilesDir} from "./params"; -import {getDevBeaconNode} from "../utils/node/beacon"; -import {RestApiOptions} from "../../src/api"; -import {simTestInfoTracker} from "../utils/node/simTest"; -import {getAndInitDevValidators} from "../utils/node/validator"; -import {Eth1Provider} from "../../src"; -import {ZERO_HASH} from "../../src/constants"; -import {bytesToData, dataToBytes, quantityToNum} from "../../src/eth1/provider/utils"; - -// NOTE: This file has been tested on a running geth outside CI, might be reworked or scrapped once CI interop test file: merge-interop.test.ts works just fine. - -/* eslint-disable no-console, @typescript-eslint/naming-convention, quotes */ - -describe("executionEngine / ExecutionEngineHttp", function () { - this.timeout("10min"); - - const dataPath = fs.mkdtempSync("lodestar-test-merge-interop"); - const jsonRpcPort = 8545; - const enginePort = 8545; - const jsonRpcUrl = `http://localhost:${jsonRpcPort}`; - const engineApiUrl = `http://localhost:${enginePort}`; - - after(async () => { - await shell(`rm -rf ${dataPath}`); - }); - - const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - while (afterEachCallbacks.length > 0) { - const callback = afterEachCallbacks.pop(); - if (callback) await callback(); - } - }); - - // Ref: https://notes.ethereum.org/@9AeMAlpyQYaAAyuj47BzRw/rkwW3ceVY - // Build geth from source at branch https://github.com/ethereum/go-ethereum/pull/23607 - // $ ./go-ethereum/build/bin/geth --catalyst --datadir "~/ethereum/taunus" init genesis.json - // $ ./build/bin/geth --catalyst --http --ws -http.api "engine" --datadir "~/ethereum/taunus" console - async function runGethPostMerge(): Promise<{genesisBlockHash: string}> { - await shell(`rm -rf ${dataPath}`); - fs.mkdirSync(dataPath, {recursive: true}); - - const controller = new AbortController(); - - // Fetch genesis block hash - const genesisBlockHash = await getGenesisBlockHash(jsonRpcUrl, controller.signal); - return {genesisBlockHash}; - } - - it("Send stub payloads to Geth", async () => { - const {genesisBlockHash} = await runGethPostMerge(); - - const controller = new AbortController(); - const executionEngine = new ExecutionEngineHttp({urls: [engineApiUrl]}, controller.signal); - - // 1. Prepare a payload - - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "safeBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "finalizedBlockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}, {"timestamp":"0x5", "random":"0x0000000000000000000000000000000000000000000000000000000000000000", "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}],"id":67}' http://localhost:8550 - **/ - - const preparePayloadParams = { - // Note: this is created with a pre-defined genesis.json - timestamp: quantityToNum("0x5"), - random: dataToBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), - feeRecipient: dataToBytes("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - }; - - const finalizedBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; - - const payloadId = await executionEngine.notifyForkchoiceUpdate( - genesisBlockHash, - finalizedBlockHash, - preparePayloadParams - ); - - if (!payloadId) throw Error("InvalidPayloadId"); - - // 2. Get the payload - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0xa247243752eb10b4"],"id":67}' http://localhost:8550 - **/ - - const payload = await executionEngine.getPayload(payloadId); - - // 3. Execute the payload - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayloadV1","params":[{"parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","transactions":[]}],"id":67}' http://localhost:8550 - * **/ - - const payloadResult = await executionEngine.executePayload(payload); - if (!payloadResult) { - throw Error("getPayload returned payload that executePayload deems invalid"); - } - - // 4. Update the fork choice - - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "safeBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a"}, null],"id":67}' http://localhost:8550 - **/ - - await executionEngine.notifyForkchoiceUpdate(bytesToData(payload.blockHash), genesisBlockHash); - - // Error cases - // 1. unknown payload - - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayload", - * "params":["0x123"] - * ,"id":67}' http://localhost:8545 - */ - - // await executionEngine.getPayload(1234567); - - // 2. unknown header - - /** - * curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_consensusValidated","params":[{ - * "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - * "status":"VALID" - * }],"id":67}' http://localhost:8545 - */ - }); - - it("Post-merge, run for a few blocks", async function () { - console.log("\n\nPost-merge, run for a few blocks\n\n"); - const {genesisBlockHash} = await runGethPostMerge(); - await runNodeWithGeth.bind(this)({ - genesisBlockHash, - mergeEpoch: 0, - ttd: BigInt(0), - testName: "post-merge", - }); - }); - - async function runNodeWithGeth( - this: Context, - { - genesisBlockHash, - mergeEpoch, - ttd, - testName, - }: {genesisBlockHash: string; mergeEpoch: Epoch; ttd: bigint; testName: string} - ): Promise { - const validatorClientCount = 1; - const validatorsPerClient = 32; - const event = ChainEvent.finalized; - - const testParams: Pick = { - SECONDS_PER_SLOT: 2, - }; - - // Should reach justification in 6 epochs max. - // Merge block happens at epoch 2 slot 4. Then 4 epochs to finalize - const expectedEpochsToFinish = 6; - // 1 epoch of margin of error - const epochsOfMargin = 1; - const timeoutSetupMargin = 5 * 1000; // Give extra 5 seconds of margin - - // delay a bit so regular sync sees it's up to date and sync is completed from the beginning - const genesisSlotsDelay = 3; - - const timeout = - ((epochsOfMargin + expectedEpochsToFinish) * SLOTS_PER_EPOCH + genesisSlotsDelay) * - testParams.SECONDS_PER_SLOT * - 1000; - - this.timeout(timeout + 2 * timeoutSetupMargin); - - const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; - - const testLoggerOpts: TestLoggerOpts = { - logLevel: LogLevel.info, - logFile: `${logFilesDir}/merge-interop-${testName}.log`, - timestampFormat: { - format: TimestampFormatCode.EpochSlot, - genesisTime, - slotsPerEpoch: SLOTS_PER_EPOCH, - secondsPerSlot: testParams.SECONDS_PER_SLOT, - }, - }; - const loggerNodeA = testLogger("Node-A", testLoggerOpts); - - const bn = await getDevBeaconNode({ - params: {...testParams, ALTAIR_FORK_EPOCH: 0, MERGE_FORK_EPOCH: mergeEpoch, TERMINAL_TOTAL_DIFFICULTY: ttd}, - options: { - api: {rest: {enabled: true} as RestApiOptions}, - sync: {isSingleNode: true}, - network: {discv5: null}, - eth1: {enabled: true, providerUrls: [jsonRpcUrl]}, - executionEngine: {urls: [engineApiUrl]}, - }, - validatorCount: validatorClientCount * validatorsPerClient, - logger: loggerNodeA, - genesisTime, - eth1BlockHash: fromHexString(genesisBlockHash), - }); - - afterEachCallbacks.push(async function () { - await bn.close(); - await sleep(1000); - }); - - const stopInfoTracker = simTestInfoTracker(bn, loggerNodeA); - - const validators = await getAndInitDevValidators({ - node: bn, - validatorsPerClient, - validatorClientCount, - startIndex: 0, - // At least one sim test must use the REST API for beacon <-> validator comms - useRestApi: true, - testLoggerOpts, - }); - - afterEachCallbacks.push(async function () { - await Promise.all(validators.map((v) => v.stop())); - }); - - await Promise.all(validators.map((v) => v.start())); - - await new Promise((resolve) => { - bn.chain.emitter.on(ChainEvent.finalized, (checkpoint) => { - // Resolve only if the finalized checkpoint includes execution payload - const finalizedBlock = bn.chain.forkChoice.getBlock(checkpoint.root); - if (finalizedBlock?.executionPayloadBlockHash !== null) { - console.log(`\nGot event ${event}, stopping validators and nodes\n`); - resolve(); - } - }); - }); - - // Stop chain and un-subscribe events so the execution engine won't update it's head - // Allow some time to broadcast finalized events and complete the importBlock routine - await Promise.all(validators.map((v) => v.stop())); - await bn.close(); - await sleep(500); - - // Assertions to make sure the end state is good - // 1. The proper head is set - const rpc = new Eth1Provider({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, {providerUrls: [jsonRpcUrl]}); - const consensusHead = bn.chain.forkChoice.getHead(); - const executionHeadBlockNumber = await rpc.getBlockNumber(); - const executionHeadBlock = await rpc.getBlockByNumber(executionHeadBlockNumber); - if (!executionHeadBlock) throw Error("Execution has not head block"); - if (consensusHead.executionPayloadBlockHash !== executionHeadBlock.hash) { - throw Error( - "Consensus head not equal to execution head: " + - JSON.stringify({ - executionHeadBlockNumber, - executionHeadBlockHash: executionHeadBlock.hash, - consensusHeadExecutionPayloadBlockHash: consensusHead.executionPayloadBlockHash, - consensusHeadSlot: consensusHead.slot, - }) - ); - } - - // wait for 1 slot to print current epoch stats - await sleep(1 * bn.config.SECONDS_PER_SLOT * 1000); - stopInfoTracker(); - console.log("\n\nDone\n\n"); - } -}); - -async function getGenesisBlockHash(url: string, signal: AbortSignal): Promise { - const eth1Provider = new Eth1Provider( - ({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH} as Partial) as IChainConfig, - {providerUrls: [url]}, - signal - ); - - const genesisBlock = await eth1Provider.getBlockByNumber(0); - if (!genesisBlock) { - throw Error("No genesis block available"); - } - - return genesisBlock.hash; -} From 9239af8a9d1f7dfeba00d550cffaea0c0612068b Mon Sep 17 00:00:00 2001 From: harkamal Date: Wed, 10 Nov 2021 20:12:31 +0530 Subject: [PATCH 17/23] runGethPreMerge scenario with ignoring geth side ttd not reached error --- packages/lodestar/test/sim/merge-interop.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 4692a9ce215d..319d6370b768 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -477,7 +477,21 @@ const genesisGethPreMerge = { period: 5, epoch: 30000, }, - terminalTotalDifficulty: terminalTotalDifficultyPreMerge, + + // TODO: Pre merge scenario issue with geth's genesis configuration of terminalTotalDifficulty set at 20 i.e. >0 (but works fine with geth's terminal difficulty set to 0), throws the following error on geth call. + + // Error: JSON RPC error: total difficulty not reached yet, engine_forkchoiceUpdatedV1 + // at parseRpcResponse (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:159:9) + // at JsonRpcHttpClient.fetch (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:60:12) + // at runMicrotasks () + // at processTicksAndRejections (internal/process/task_queues.js:95:5) + // at prepareExecutionPayload (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/body.ts:147:10) + // at assembleBody (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/body.ts:95:23) + // at assembleBlock (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/index.ts:43:11) + // at Object.produceBlock (/home/runner/work/lodestar/lodestar/packages/lodestar/src/api/impl/validator/index.ts:148:21) + // at Object.handler (/home/runner/work/lodestar/lodestar/packages/api/src/server/utils/server.ts:70:23) + + terminalTotalDifficulty: 0, }, nonce: "0x42", timestamp: "0x0", From 29063d9a361c233888329930a7410533224e2279 Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 11 Nov 2021 00:15:50 +0530 Subject: [PATCH 18/23] assertvalidterminalpow block fix --- packages/fork-choice/src/forkChoice/forkChoice.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 0c5f38a416d2..6f20b13a1b8d 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -909,10 +909,6 @@ function assertValidTerminalPowBlock( block: merge.BeaconBlock, preCachedData?: OnBlockPrecachedData ): void { - const {powBlock, powBlockParent} = preCachedData || {}; - if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); - if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); - if (!ssz.Root.equals(config.TERMINAL_BLOCK_HASH, ZERO_HASH)) { if (computeEpochAtSlot(block.slot) < config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH) throw Error(`Terminal block activation epoch ${config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached`); @@ -924,8 +920,15 @@ function assertValidTerminalPowBlock( block.body.executionPayload.parentHash )}` ); + + // TERMINAL_BLOCK_HASH override skips ttd checks + return; } + const {powBlock, powBlockParent} = preCachedData || {}; + if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); + if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); + const isTotalDifficultyReached = powBlock.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; const isParentTotalDifficultyValid = powBlockParent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) From 36ec4d86c99aa731d07f37e1afe6348248d096e5 Mon Sep 17 00:00:00 2001 From: harkamal Date: Thu, 11 Nov 2021 00:27:59 +0530 Subject: [PATCH 19/23] tracker in comments for geth preMerge to postMerge issue --- packages/lodestar/test/sim/merge-interop.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 319d6370b768..9ee222fca5a9 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -480,6 +480,8 @@ const genesisGethPreMerge = { // TODO: Pre merge scenario issue with geth's genesis configuration of terminalTotalDifficulty set at 20 i.e. >0 (but works fine with geth's terminal difficulty set to 0), throws the following error on geth call. + // Tracker: https://github.com/ChainSafe/lodestar/issues/3427 + // Error: JSON RPC error: total difficulty not reached yet, engine_forkchoiceUpdatedV1 // at parseRpcResponse (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:159:9) // at JsonRpcHttpClient.fetch (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:60:12) From 12923ed19051c205af3b8c285c0fcf8273c8c647 Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 12 Nov 2021 01:07:24 +0530 Subject: [PATCH 20/23] merge transition scenario with ttd > 0 check fix deployed on geth:kintsugi-spec --- .../lodestar/test/sim/merge-interop.test.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/packages/lodestar/test/sim/merge-interop.test.ts b/packages/lodestar/test/sim/merge-interop.test.ts index 9ee222fca5a9..4692a9ce215d 100644 --- a/packages/lodestar/test/sim/merge-interop.test.ts +++ b/packages/lodestar/test/sim/merge-interop.test.ts @@ -477,23 +477,7 @@ const genesisGethPreMerge = { period: 5, epoch: 30000, }, - - // TODO: Pre merge scenario issue with geth's genesis configuration of terminalTotalDifficulty set at 20 i.e. >0 (but works fine with geth's terminal difficulty set to 0), throws the following error on geth call. - - // Tracker: https://github.com/ChainSafe/lodestar/issues/3427 - - // Error: JSON RPC error: total difficulty not reached yet, engine_forkchoiceUpdatedV1 - // at parseRpcResponse (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:159:9) - // at JsonRpcHttpClient.fetch (/home/runner/work/lodestar/lodestar/packages/lodestar/src/eth1/provider/jsonRpcHttpClient.ts:60:12) - // at runMicrotasks () - // at processTicksAndRejections (internal/process/task_queues.js:95:5) - // at prepareExecutionPayload (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/body.ts:147:10) - // at assembleBody (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/body.ts:95:23) - // at assembleBlock (/home/runner/work/lodestar/lodestar/packages/lodestar/src/chain/factory/block/index.ts:43:11) - // at Object.produceBlock (/home/runner/work/lodestar/lodestar/packages/lodestar/src/api/impl/validator/index.ts:148:21) - // at Object.handler (/home/runner/work/lodestar/lodestar/packages/api/src/server/utils/server.ts:70:23) - - terminalTotalDifficulty: 0, + terminalTotalDifficulty: terminalTotalDifficultyPreMerge, }, nonce: "0x42", timestamp: "0x0", From 52f05d3b0b65887cda1fccccee3808acaef2a0ec Mon Sep 17 00:00:00 2001 From: harkamal Date: Fri, 12 Nov 2021 01:16:50 +0530 Subject: [PATCH 21/23] cleanup as merge-interop test file scenaros updated and working --- kintsugi/genesisPost.json | 35 ----------------------------------- kintsugi/geth/Dockerfile | 19 ------------------- 2 files changed, 54 deletions(-) delete mode 100644 kintsugi/genesisPost.json delete mode 100644 kintsugi/geth/Dockerfile diff --git a/kintsugi/genesisPost.json b/kintsugi/genesisPost.json deleted file mode 100644 index b36b848de510..000000000000 --- a/kintsugi/genesisPost.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "config": { - "chainId":1, - "homesteadBlock":0, - "eip150Block":0, - "eip155Block":0, - "eip158Block":0, - "byzantiumBlock":0, - "constantinopleBlock":0, - "petersburgBlock":0, - "istanbulBlock":0, - "muirGlacierBlock":0, - "berlinBlock":0, - "londonBlock":0, - "clique": { - "period": 5, - "epoch": 30000 - }, - "terminalTotalDifficulty":0 - }, - "nonce":"0x42", - "timestamp":"0x0", - "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit":"0x1C9C380", - "difficulty":"0x400000000", - "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase":"0x0000000000000000000000000000000000000000", - "alloc":{ - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b":{"balance":"0x6d6172697573766477000000"} - }, - "number":"0x0", - "gasUsed":"0x0", - "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "baseFeePerGas":"0x7" -} diff --git a/kintsugi/geth/Dockerfile b/kintsugi/geth/Dockerfile deleted file mode 100644 index 124e13d2d273..000000000000 --- a/kintsugi/geth/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM golang:1.17-alpine as builder -RUN apk add --no-cache gcc musl-dev linux-headers git -WORKDIR /usr/app - -RUN git clone -b kintsugi-spec https://github.com/MariusVanDerWijden/go-ethereum.git -RUN cd /usr/app/go-ethereum && go run build/ci.go install ./cmd/geth - - -# Pull Geth into a second stage deploy alpine container -FROM alpine:latest -RUN apk add --no-cache bash curl - -RUN apk add --no-cache ca-certificates -COPY --from=builder usr/app/go-ethereum/build/bin/geth /usr/local/bin/ - -WORKDIR /usr/app - -EXPOSE 30303 30303/udp -ENTRYPOINT ["geth"] From a1c663575e05b418254cd78f5cd784c48eb81861 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sat, 13 Nov 2021 19:37:36 +0530 Subject: [PATCH 22/23] handling prepare payload failure scenarios --- .../lodestar/src/api/impl/validator/index.ts | 2 +- .../lodestar/src/chain/factory/block/body.ts | 32 ++++++++++++------- .../lodestar/src/chain/factory/block/index.ts | 28 ++++++++++------ packages/lodestar/src/executionEngine/http.ts | 24 +++++++++++--- .../lodestar/src/executionEngine/interface.ts | 7 ++++ .../test/unit/executionEngine/http.test.ts | 2 +- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/packages/lodestar/src/api/impl/validator/index.ts b/packages/lodestar/src/api/impl/validator/index.ts index c5749b9e16ea..0d1befaf27c4 100644 --- a/packages/lodestar/src/api/impl/validator/index.ts +++ b/packages/lodestar/src/api/impl/validator/index.ts @@ -146,7 +146,7 @@ export function getValidatorApi({chain, config, logger, metrics, network, sync}: timer = metrics?.blockProductionTime.startTimer(); const block = await assembleBlock( - {chain, metrics}, + {chain, metrics, logger}, { slot, randaoReveal, diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index 11359e811e3a..6701571bed83 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -23,6 +23,8 @@ import { getCurrentEpoch, merge, } from "@chainsafe/lodestar-beacon-state-transition"; +import {ILogger} from "@chainsafe/lodestar-utils"; + import {IBeaconChain} from "../../interface"; import {PayloadId} from "../../../executionEngine/interface"; import {ZERO_HASH, ZERO_HASH_HEX} from "../../../constants"; @@ -44,7 +46,8 @@ export async function assembleBody( parentSlot: Slot; parentBlockRoot: Root; feeRecipient: ExecutionAddress; - } + }, + logger?: ILogger | null ): Promise { // TODO: // Iterate through the naive aggregation pool and ensure all the attestations from there @@ -92,18 +95,23 @@ export async function assembleBody( const finalizedBlockHash = chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash; - const payloadId = await prepareExecutionPayload( - chain, - finalizedBlockHash ?? ZERO_HASH_HEX, - currentState as CachedBeaconState, - feeRecipient - ); - - if (payloadId !== null) { - (blockBody as merge.BeaconBlockBody).executionPayload = await chain.executionEngine.getPayload(payloadId); - } else { - (blockBody as merge.BeaconBlockBody).executionPayload = ssz.merge.ExecutionPayload.defaultValue(); + let executionPayload: merge.ExecutionPayload | null = null; + try { + const payloadId = await prepareExecutionPayload( + chain, + finalizedBlockHash ?? ZERO_HASH_HEX, + currentState as CachedBeaconState, + feeRecipient + ); + if (payloadId) executionPayload = await chain.executionEngine.getPayload(payloadId); + } catch (e) { + logger?.warn("Failed to produce execution payload", {}, e as Error); } + + if (!executionPayload) logger?.verbose("Assembling block with empty executionPayload"); + + (blockBody as merge.BeaconBlockBody).executionPayload = + executionPayload ?? ssz.merge.ExecutionPayload.defaultValue(); } return blockBody; diff --git a/packages/lodestar/src/chain/factory/block/index.ts b/packages/lodestar/src/chain/factory/block/index.ts index c76dd30f758f..46bb43819b93 100644 --- a/packages/lodestar/src/chain/factory/block/index.ts +++ b/packages/lodestar/src/chain/factory/block/index.ts @@ -5,20 +5,23 @@ import {CachedBeaconState, allForks} from "@chainsafe/lodestar-beacon-state-transition"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {Bytes32, Bytes96, ExecutionAddress, Root, Slot} from "@chainsafe/lodestar-types"; +import {fromHexString} from "@chainsafe/ssz"; +import {ILogger} from "@chainsafe/lodestar-utils"; + import {ZERO_HASH} from "../../../constants"; import {IMetrics} from "../../../metrics"; import {IBeaconChain} from "../../interface"; import {assembleBody} from "./body"; import {RegenCaller} from "../../regen"; -import {fromHexString} from "@chainsafe/ssz"; type AssembleBlockModules = { chain: IBeaconChain; metrics: IMetrics | null; + logger?: ILogger | null; }; export async function assembleBlock( - {chain, metrics}: AssembleBlockModules, + {chain, metrics, logger}: AssembleBlockModules, { randaoReveal, graffiti, @@ -40,14 +43,19 @@ export async function assembleBlock( proposerIndex: state.getBeaconProposer(slot), parentRoot: parentBlockRoot, stateRoot: ZERO_HASH, - body: await assembleBody(chain, state, { - randaoReveal, - graffiti, - blockSlot: slot, - parentSlot: slot - 1, - parentBlockRoot, - feeRecipient, - }), + body: await assembleBody( + chain, + state, + { + randaoReveal, + graffiti, + blockSlot: slot, + parentSlot: slot - 1, + parentBlockRoot, + feeRecipient, + }, + logger + ), }; block.stateRoot = computeNewStateRoot({config: chain.config, metrics}, state, block); diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index 6367a312b529..48954f45be27 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -11,7 +11,14 @@ import { quantityToBigint, } from "../eth1/provider/utils"; import {IJsonRpcHttpClient} from "../eth1/provider/jsonRpcHttpClient"; -import {ExecutePayloadStatus, IExecutionEngine, PayloadId, PayloadAttributes, ApiPayloadAttributes} from "./interface"; +import { + ExecutePayloadStatus, + ForkChoiceUpdateStatus, + IExecutionEngine, + PayloadId, + PayloadAttributes, + ApiPayloadAttributes, +} from "./interface"; import {BYTES_PER_LOGS_BLOOM} from "@chainsafe/lodestar-params"; export type ExecutionEngineHttpOpts = { @@ -105,9 +112,16 @@ export class ExecutionEngineHttp implements IExecutionEngine { ...apiPayloadAttributes, ], }) - .then(({payloadId}) => { - if ((payloadAttributes && payloadId === "0x") || (!payloadAttributes && payloadId !== "0x")) - throw Error("InvalidPayloadId"); + .then(({status, payloadId}) => { + // Validate status is known + const statusEnum = ForkChoiceUpdateStatus[status]; + if (statusEnum === undefined) { + throw Error(`Unknown status ${status}`); + } + + // Throw error on syncing if requested to produce a block, else silently ignore + if (payloadAttributes && statusEnum === ForkChoiceUpdateStatus.SYNCING) throw Error("Execution Layer Syncing"); + return payloadId !== "0x" ? payloadId : null; }); } @@ -168,7 +182,7 @@ type EngineApiRpcReturnTypes = { */ engine_executePayloadV1: {status: ExecutePayloadStatus}; engine_consensusValidated: void; - engine_forkchoiceUpdatedV1: {status: ExecutePayloadStatus; payloadId: QUANTITY}; + engine_forkchoiceUpdatedV1: {status: ForkChoiceUpdateStatus; payloadId: QUANTITY}; /** * payloadId | Error: QUANTITY, 64 Bits - Identifier of the payload building process */ diff --git a/packages/lodestar/src/executionEngine/interface.ts b/packages/lodestar/src/executionEngine/interface.ts index e7ee3dc65e06..5c4085eec796 100644 --- a/packages/lodestar/src/executionEngine/interface.ts +++ b/packages/lodestar/src/executionEngine/interface.ts @@ -14,6 +14,13 @@ export enum ExecutePayloadStatus { SYNCING = "SYNCING", } +export enum ForkChoiceUpdateStatus { + /** given payload is valid */ + SUCCESS = "SUCCESS", + /** sync process is in progress */ + SYNCING = "SYNCING", +} + export type PayloadAttributes = { timestamp: number; random: Uint8Array | ByteVector; diff --git a/packages/lodestar/test/unit/executionEngine/http.test.ts b/packages/lodestar/test/unit/executionEngine/http.test.ts index 9c07b23c56a7..f11e27b7c3b3 100644 --- a/packages/lodestar/test/unit/executionEngine/http.test.ts +++ b/packages/lodestar/test/unit/executionEngine/http.test.ts @@ -126,7 +126,7 @@ describe("ExecutionEngine / http", () => { }, ], }; - returnValue = {jsonrpc: "2.0", id: 67, result: {payloadId: "0x"}}; + returnValue = {jsonrpc: "2.0", id: 67, result: {status: "SUCCESS", payloadId: "0x"}}; await executionEngine.notifyForkchoiceUpdate(request.params[0].headBlockHash, request.params[0].finalizedBlockHash); From c15bf0714e800eca40d489021e441cd6027ec379 Mon Sep 17 00:00:00 2001 From: harkamal Date: Mon, 15 Nov 2021 16:38:56 +0530 Subject: [PATCH 23/23] seperating optimistic sync, fixing and testing the transaction submission/execution --- .../fork-choice/src/forkChoice/forkChoice.ts | 26 +++++++++---------- packages/lodestar/src/executionEngine/http.ts | 4 +-- packages/lodestar/src/executionEngine/mock.ts | 2 +- packages/types/src/merge/types.ts | 7 +---- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 6f20b13a1b8d..109955808a36 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -920,19 +920,17 @@ function assertValidTerminalPowBlock( block.body.executionPayload.parentHash )}` ); - - // TERMINAL_BLOCK_HASH override skips ttd checks - return; + } else { + // If no TERMINAL_BLOCK_HASH override, check ttd + const {powBlock, powBlockParent} = preCachedData || {}; + if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); + if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); + + const isTotalDifficultyReached = powBlock.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; + const isParentTotalDifficultyValid = powBlockParent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; + if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) + throw Error( + `Invalid terminal POW block: total difficulty not reached ${powBlockParent.totalDifficulty} < ${powBlock.totalDifficulty}` + ); } - - const {powBlock, powBlockParent} = preCachedData || {}; - if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); - if (!powBlockParent) throw Error("onBlock preCachedData must include powBlock"); - - const isTotalDifficultyReached = powBlock.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; - const isParentTotalDifficultyValid = powBlockParent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; - if (!isTotalDifficultyReached || !isParentTotalDifficultyValid) - throw Error( - `Invalid terminal POW block: total difficulty not reached ${powBlockParent.totalDifficulty} < ${powBlock.totalDifficulty}` - ); } diff --git a/packages/lodestar/src/executionEngine/http.ts b/packages/lodestar/src/executionEngine/http.ts index 48954f45be27..0d3961cce264 100644 --- a/packages/lodestar/src/executionEngine/http.ts +++ b/packages/lodestar/src/executionEngine/http.ts @@ -221,7 +221,7 @@ export function serializeExecutionPayload(data: merge.ExecutionPayload): Executi extraData: bytesToData(data.extraData), baseFeePerGas: numToQuantity(data.baseFeePerGas), blockHash: bytesToData(data.blockHash), - transactions: data.transactions.map((tran) => bytesToData(tran.value)), + transactions: data.transactions.map((tran) => bytesToData(tran)), }; } @@ -240,6 +240,6 @@ export function parseExecutionPayload(data: ExecutionPayloadRpc): merge.Executio extraData: dataToBytes(data.extraData), baseFeePerGas: quantityToBigint(data.baseFeePerGas), blockHash: dataToBytes(data.blockHash, 32), - transactions: data.transactions.map((tran) => ({selector: 0, value: dataToBytes(tran)})), + transactions: data.transactions.map((tran) => dataToBytes(tran)), }; } diff --git a/packages/lodestar/src/executionEngine/mock.ts b/packages/lodestar/src/executionEngine/mock.ts index ddc7d861dcfa..964e5d65f89e 100644 --- a/packages/lodestar/src/executionEngine/mock.ts +++ b/packages/lodestar/src/executionEngine/mock.ts @@ -107,7 +107,7 @@ export class ExecutionEngineMock implements IExecutionEngine { extraData: ZERO_HASH, baseFeePerGas: BigInt(0), blockHash: crypto.randomBytes(32), - transactions: [{selector: 0, value: crypto.randomBytes(512)}], + transactions: [crypto.randomBytes(512)], }; this.preparingPayloads.set(payloadId, payload); diff --git a/packages/types/src/merge/types.ts b/packages/types/src/merge/types.ts index a702517635e5..3086f7fa4513 100644 --- a/packages/types/src/merge/types.ts +++ b/packages/types/src/merge/types.ts @@ -1,12 +1,7 @@ import * as altair from "../altair/types"; import {Root, Bytes32, Number64, ExecutionAddress, Uint256} from "../primitive/types"; -export type OpaqueTransaction = Uint8Array; - -export type Transaction = { - selector: number; - value: OpaqueTransaction; -}; +export type Transaction = Uint8Array; type ExecutionPayloadFields = { // Execution block header fields