Skip to content

Commit

Permalink
Issue advance fcU for builing the EL block
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed May 2, 2022
1 parent c9c7558 commit b73f5bf
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 35 deletions.
74 changes: 50 additions & 24 deletions packages/lodestar/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {ILogger} from "@chainsafe/lodestar-utils";
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {IMetrics} from "../../metrics";
import {IEth1ForBlockProduction} from "../../eth1";
import {IExecutionEngine} from "../../executionEngine";
import {IExecutionEngine, PayloadId} from "../../executionEngine/interface";
import {IBeaconDb} from "../../db";
import {ZERO_HASH_HEX} from "../../constants";
import {CheckpointStateCache, StateContextCache, toCheckpointHex} from "../stateCache";
Expand All @@ -31,6 +31,7 @@ import {LightClientServer} from "../lightClient";
import {getCheckpointFromState} from "./utils/checkpoint";
import {PendingEvents} from "./utils/pendingEvents";
import {FullyVerifiedBlock} from "./types";
import {prepareExecutionPayload} from "../factory/block/body";
// import {ForkChoiceError, ForkChoiceErrorCode} from "@chainsafe/lodestar-fork-choice/lib/forkChoice/errors";

/**
Expand Down Expand Up @@ -199,6 +200,8 @@ export async function importBlock(chain: ImportBlockModules, fullyVerifiedBlock:
const oldHead = chain.forkChoice.getHead();
chain.forkChoice.updateHead();
const newHead = chain.forkChoice.getHead();
const currFinalizedEpoch = chain.forkChoice.getFinalizedCheckpoint().epoch;

if (newHead.blockRoot !== oldHead.blockRoot) {
// new head
pendingEvents.push(ChainEvent.forkChoiceHead, newHead);
Expand All @@ -212,30 +215,31 @@ export async function importBlock(chain: ImportBlockModules, fullyVerifiedBlock:
}
}

// NOTE: forkChoice.fsStore.finalizedCheckpoint MUST only change is response to an onBlock event
// Notify execution layer of head and finalized updates
const currFinalizedEpoch = chain.forkChoice.getFinalizedCheckpoint().epoch;
if (newHead.blockRoot !== oldHead.blockRoot || currFinalizedEpoch !== prevFinalizedEpoch) {
/**
* On post BELLATRIX_EPOCH but pre TTD, blocks include empty execution payload with a zero block hash.
* The consensus clients must not send notifyForkchoiceUpdate before TTD since the execution client will error.
* So we must check that:
* - `headBlockHash !== null` -> Pre BELLATRIX_EPOCH
* - `headBlockHash !== ZERO_HASH` -> Pre TTD
*/
const headBlockHash = chain.forkChoice.getHead().executionPayloadBlockHash;
/**
* After BELLATRIX_EPOCH and TTD it's okay to send a zero hash block hash for the finalized block. This will happen if
* the current finalized block does not contain any execution payload at all (pre MERGE_EPOCH) or if it contains a
* zero block hash (pre TTD)
*/
const finalizedBlockHash = chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash;
if (headBlockHash !== null && headBlockHash !== ZERO_HASH_HEX) {
chain.executionEngine.notifyForkchoiceUpdate(headBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX).catch((e) => {
chain.logger.error("Error pushing notifyForkchoiceUpdate()", {headBlockHash, finalizedBlockHash}, e);
});
void issueNextProposerEngineFcU(chain, postState).then((payloadId) => {
// NOTE: forkChoice.fsStore.finalizedCheckpoint MUST only change is response to an onBlock event
// Notify execution layer of head and finalized updates
if (payloadId === null || newHead.blockRoot !== oldHead.blockRoot || currFinalizedEpoch !== prevFinalizedEpoch) {
/**
* On post BELLATRIX_EPOCH but pre TTD, blocks include empty execution payload with a zero block hash.
* The consensus clients must not send notifyForkchoiceUpdate before TTD since the execution client will error.
* So we must check that:
* - `headBlockHash !== null` -> Pre BELLATRIX_EPOCH
* - `headBlockHash !== ZERO_HASH` -> Pre TTD
*/
const headBlockHash = chain.forkChoice.getHead().executionPayloadBlockHash ?? ZERO_HASH_HEX;
/**
* After BELLATRIX_EPOCH and TTD it's okay to send a zero hash block hash for the finalized block. This will happen if
* the current finalized block does not contain any execution payload at all (pre MERGE_EPOCH) or if it contains a
* zero block hash (pre TTD)
*/
const finalizedBlockHash = chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
if (headBlockHash !== ZERO_HASH_HEX) {
chain.executionEngine.notifyForkchoiceUpdate(headBlockHash, finalizedBlockHash).catch((e) => {
chain.logger.error("Error pushing notifyForkchoiceUpdate()", {headBlockHash, finalizedBlockHash}, e);
});
}
}
}
});

// Emit ChainEvent.block event
//
Expand Down Expand Up @@ -269,6 +273,28 @@ export async function importBlock(chain: ImportBlockModules, fullyVerifiedBlock:
pendingEvents.emit();
}

export async function issueNextProposerEngineFcU(
chain: ImportBlockModules,
state: CachedBeaconStateAllForks
): Promise<PayloadId | null> {
const prepareSlot = state.slot + 1;
// TODO: if prepareSlot is on boundary do the epoch processing
if (bellatrix.isBellatrixStateType(state) && prepareSlot% SLOTS_PER_EPOCH !== 0) {
try {
const proposerIndex = state.epochCtx.getBeaconProposer(prepareSlot);
const feeRecipient = chain.executionEngine.proposers.get(proposerIndex)?.feeRecipient;
if (feeRecipient) {
const finalizedBlockHash = chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const payloadId = await prepareExecutionPayload(chain, finalizedBlockHash, state, feeRecipient);
return payloadId;
}
} catch (e) {
chain.logger.error("Error on issuing next proposer engine fcU", {}, e as Error);
}
}
return null;
}

/**
* Returns the closest state to postState.currentJustifiedCheckpoint in the same fork as postState
*
Expand Down
28 changes: 20 additions & 8 deletions packages/lodestar/src/chain/factory/block/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ import {
getCurrentEpoch,
bellatrix,
} from "@chainsafe/lodestar-beacon-state-transition";
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {toHex} from "@chainsafe/lodestar-utils";

import {IBeaconChain} from "../../interface";
import {PayloadId} from "../../../executionEngine/interface";
import {PayloadId, IExecutionEngine} from "../../../executionEngine/interface";
import {ZERO_HASH, ZERO_HASH_HEX} from "../../../constants";
import {IEth1ForBlockProduction} from "../../../eth1";

export async function assembleBody(
chain: IBeaconChain,
Expand Down Expand Up @@ -130,8 +133,12 @@ export async function assembleBody(
*
* @returns PayloadId = pow block found, null = pow NOT found
*/
async function prepareExecutionPayload(
chain: IBeaconChain,
export async function prepareExecutionPayload(
chain: {
eth1: IEth1ForBlockProduction;
executionEngine: IExecutionEngine;
config: IChainForkConfig;
},
finalizedBlockHash: RootHex,
state: CachedBeaconStateBellatrix,
suggestedFeeRecipient: ExecutionAddress
Expand Down Expand Up @@ -164,11 +171,16 @@ async function prepareExecutionPayload(

const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
const prevRandao = getRandaoMix(state, state.epochCtx.epoch);
const payloadId = await chain.executionEngine.notifyForkchoiceUpdate(parentHash, finalizedBlockHash, {
timestamp,
prevRandao,
suggestedFeeRecipient,
});

const payloadId =
chain.executionEngine.payloadIdCache.get(
`${toHex(parentHash)}-${finalizedBlockHash}-${toHex(prevRandao)}-${toHex(suggestedFeeRecipient)}`
) ??
(await chain.executionEngine.notifyForkchoiceUpdate(parentHash, finalizedBlockHash, {
timestamp,
prevRandao,
suggestedFeeRecipient,
}));
if (!payloadId) throw new Error("InvalidPayloadId: Null");
return payloadId;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/lodestar/src/executionEngine/disabled.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {ValidatorIndex, Epoch, ExecutionAddress} from "@chainsafe/lodestar-types";
import {MapDef} from "../util/map";
import {IExecutionEngine} from "./interface";
import {IExecutionEngine, PayloadId} from "./interface";

export class ExecutionEngineDisabled implements IExecutionEngine {
readonly proposers = new MapDef<ValidatorIndex, {epoch: Epoch; feeRecipient: ExecutionAddress}>(() => ({
epoch: 0,
feeRecipient: Buffer.alloc(20, 0),
}));
readonly payloadIdCache = new Map<string, PayloadId>();

async notifyNewPayload(): Promise<never> {
throw Error("Execution engine disabled");
Expand Down
11 changes: 9 additions & 2 deletions packages/lodestar/src/executionEngine/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = {
*/
export class ExecutionEngineHttp implements IExecutionEngine {
readonly proposers: MapDef<ValidatorIndex, {epoch: Epoch; feeRecipient: ExecutionAddress}>;
readonly payloadIdCache = new Map<string, PayloadId>();
private readonly rpc: IJsonRpcHttpClient;

constructor(opts: ExecutionEngineHttpOpts, signal: AbortSignal, rpc?: IJsonRpcHttpClient) {
Expand Down Expand Up @@ -231,8 +232,14 @@ export class ExecutionEngineHttp implements IExecutionEngine {
switch (status) {
case ExecutePayloadStatus.VALID:
// if payloadAttributes are provided, a valid payloadId is expected
if (payloadAttributes && (!payloadId || payloadId === "0x")) {
throw Error(`Received invalid payloadId=${payloadId}`);
if (apiPayloadAttributes) {
if (!payloadId || payloadId === "0x") {
throw Error(`Received invalid payloadId=${payloadId}`);
}
this.payloadIdCache.set(
`${headBlockHashData}-${finalizedBlockHash}-${apiPayloadAttributes.prevRandao}-${apiPayloadAttributes.suggestedFeeRecipient}`,
payloadId
);
}
return payloadId !== "0x" ? payloadId : null;

Expand Down
1 change: 1 addition & 0 deletions packages/lodestar/src/executionEngine/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export type ProposerPreparationData = {
*/
export interface IExecutionEngine {
proposers: MapDef<ValidatorIndex, {epoch: Epoch; feeRecipient: ExecutionAddress}>;
payloadIdCache: Map<string, PayloadId>;
/**
* A state transition function which applies changes to the self.execution_state.
* Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``.
Expand Down
1 change: 1 addition & 0 deletions packages/lodestar/src/executionEngine/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class ExecutionEngineMock implements IExecutionEngine {
headBlockRoot = ZERO_HASH_HEX;
finalizedBlockRoot = ZERO_HASH_HEX;
readonly proposers: MapDef<ValidatorIndex, {epoch: Epoch; feeRecipient: ExecutionAddress}>;
readonly payloadIdCache = new Map<string, PayloadId>();

private knownBlocks = new Map<RootHex, bellatrix.ExecutionPayload>();
private preparingPayloads = new Map<number, bellatrix.ExecutionPayload>();
Expand Down

0 comments on commit b73f5bf

Please sign in to comment.