Skip to content

Commit

Permalink
Add capella hardfork and types (#4568)
Browse files Browse the repository at this point in the history
* Add capella hardfork and types

 * handle capella state upgrade case

 * fix more spec tests

 * lint params

 * add the new domain for constants check

 * rebase fixes

* update state transition with multi fork execution types

* separate allforks execution types
  • Loading branch information
g11tech committed Oct 2, 2022
1 parent 1d64a2f commit c9f07a4
Show file tree
Hide file tree
Showing 57 changed files with 714 additions and 192 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type Api = {
): Promise<{version: ForkName; data: bellatrix.SignedBuilderBid}>;
submitBlindedBlock(
signedBlock: allForks.SignedBlindedBeaconBlock
): Promise<{version: ForkName; data: bellatrix.ExecutionPayload}>;
): Promise<{version: ForkName; data: allForks.ExecutionPayload}>;
};

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/beacon-node/src/api/impl/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DOMAIN_SYNC_COMMITTEE,
DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF,
DOMAIN_CONTRIBUTION_AND_PROOF,
DOMAIN_BLS_TO_EXECUTION_CHANGE,
DOMAIN_APPLICATION_BUILDER,
TIMELY_SOURCE_FLAG_INDEX,
TIMELY_TARGET_FLAG_INDEX,
Expand Down Expand Up @@ -90,4 +91,7 @@ export const specConstants = {
// altair/validator.md
TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE,
SYNC_COMMITTEE_SUBNET_COUNT,

// ## Capella domain types
DOMAIN_BLS_TO_EXECUTION_CHANGE,
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
CachedBeaconStateAllForks,
isBellatrixStateType,
isBellatrixBlockBodyType,
isExecutionStateType,
isExecutionBlockBodyType,
isMergeTransitionBlock as isMergeTransitionBlockFn,
isExecutionEnabled,
} from "@lodestar/state-transition";
Expand Down Expand Up @@ -177,8 +177,8 @@ export async function verifyBlocksExecutionPayload(
// will still evalute to true for the following blocks leading to errors (while syncing)
// as the preState0 still belongs to the pre state of the first block on segment
mergeBlockFound === null &&
isBellatrixStateType(preState0) &&
isBellatrixBlockBodyType(block.message.body) &&
isExecutionStateType(preState0) &&
isExecutionBlockBodyType(block.message.body) &&
isMergeTransitionBlockFn(preState0, block.message.body);

// If this is a merge transition block, check to ensure if it references
Expand Down Expand Up @@ -256,8 +256,8 @@ export async function verifyBlockExecutionPayload(
): Promise<VerifyBlockExecutionResponse> {
/** Not null if execution is enabled */
const executionPayloadEnabled =
isBellatrixStateType(preState0) &&
isBellatrixBlockBodyType(block.message.body) &&
isExecutionStateType(preState0) &&
isExecutionBlockBodyType(block.message.body) &&
// Safe to use with a state previous to block's preState. isMergeComplete can only transition from false to true.
// - If preState0 is after merge block: condition is true, and will always be true
// - If preState0 is before merge block: the block could lie but then state transition function will throw above
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import {
CachedBeaconStateAllForks,
getEffectiveBalanceIncrementsZeroInactive,
isBellatrixStateType,
isExecutionStateType,
isMergeTransitionComplete,
} from "@lodestar/state-transition";

Expand Down Expand Up @@ -78,7 +78,7 @@ export function initializeForkChoice(
unrealizedFinalizedEpoch: finalizedCheckpoint.epoch,
unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root),

...(isBellatrixStateType(state) && isMergeTransitionComplete(state)
...(isExecutionStateType(state) && isMergeTransitionComplete(state)
? {
executionPayloadBlockHash: toHexString(state.latestExecutionPayloadHeader.blockHash),
executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing,
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/chain/prepareNextSlot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {computeEpochAtSlot, isBellatrixStateType, computeTimeAtSlot} from "@lodestar/state-transition";
import {computeEpochAtSlot, isExecutionStateType, computeTimeAtSlot} from "@lodestar/state-transition";
import {IChainForkConfig} from "@lodestar/config";
import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
import {Slot} from "@lodestar/types";
Expand Down Expand Up @@ -116,7 +116,7 @@ export class PrepareNextSlotScheduler {
});
}

if (isBellatrixStateType(prepareState)) {
if (isExecutionStateType(prepareState)) {
const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot);
const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
if (feeRecipient) {
Expand Down
23 changes: 15 additions & 8 deletions packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
bellatrix,
Bytes32,
phase0,
allForks,
Expand All @@ -15,14 +14,17 @@ import {
import {
CachedBeaconStateAllForks,
CachedBeaconStateBellatrix,
CachedBeaconStateExecutions,
computeEpochAtSlot,
computeTimeAtSlot,
getRandaoMix,
getCurrentEpoch,
isMergeTransitionComplete,
} from "@lodestar/state-transition";
import {IChainForkConfig} from "@lodestar/config";
import {ForkName} from "@lodestar/params";
import {toHex, sleep} from "@lodestar/utils";

import type {BeaconChain} from "../chain.js";
import {PayloadId, IExecutionEngine, IExecutionBuilder} from "../../execution/index.js";
import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
Expand Down Expand Up @@ -109,7 +111,8 @@ export async function produceBlockBody<T extends BlockType>(
);
}

if (blockEpoch >= this.config.BELLATRIX_FORK_EPOCH) {
const forkName = currentState.config.getForkName(blockSlot);
if (forkName !== ForkName.phase0 && forkName !== ForkName.altair) {
const safeBlockHash = this.forkChoice.getJustifiedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const feeRecipient = this.beaconProposerCache.getOrDefault(proposerIndex);
Expand Down Expand Up @@ -151,7 +154,9 @@ export async function produceBlockBody<T extends BlockType>(
feeRecipient
);
if (prepareRes.isPremerge) {
(blockBody as bellatrix.BeaconBlockBody).executionPayload = ssz.bellatrix.ExecutionPayload.defaultValue();
(blockBody as allForks.ExecutionBlockBody).executionPayload = ssz.allForksExecution[
forkName
].ExecutionPayload.defaultValue();
} else {
const {prepType, payloadId} = prepareRes;
if (prepType !== PayloadPreparationType.Cached) {
Expand All @@ -163,7 +168,7 @@ export async function produceBlockBody<T extends BlockType>(
await sleep(PAYLOAD_GENERATION_TIME_MS);
}
const payload = await this.executionEngine.getPayload(payloadId);
(blockBody as bellatrix.BeaconBlockBody).executionPayload = payload;
(blockBody as allForks.ExecutionBlockBody).executionPayload = payload;

const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime);
this.metrics?.blockPayload.payloadFetchedTime.observe({prepType}, fetchedTime);
Expand All @@ -182,7 +187,9 @@ export async function produceBlockBody<T extends BlockType>(
{},
e as Error
);
(blockBody as bellatrix.BeaconBlockBody).executionPayload = ssz.bellatrix.ExecutionPayload.defaultValue();
(blockBody as allForks.ExecutionBlockBody).executionPayload = ssz.allForksExecution[
forkName
].ExecutionPayload.defaultValue();
} else {
// since merge transition is complete, we need a valid payload even if with an
// empty (transactions) one. defaultValue isn't gonna cut it!
Expand Down Expand Up @@ -210,7 +217,7 @@ export async function prepareExecutionPayload(
},
safeBlockHash: RootHex,
finalizedBlockHash: RootHex,
state: CachedBeaconStateBellatrix,
state: CachedBeaconStateExecutions,
suggestedFeeRecipient: string
): Promise<{isPremerge: true} | {isPremerge: false; prepType: PayloadPreparationType; payloadId: PayloadId}> {
const parentHashRes = await getExecutionPayloadParentHash(chain, state);
Expand Down Expand Up @@ -281,7 +288,7 @@ async function prepareExecutionPayloadHeader(
},
state: CachedBeaconStateBellatrix,
proposerPubKey: BLSPubkey
): Promise<bellatrix.ExecutionPayloadHeader> {
): Promise<allForks.ExecutionPayloadHeader> {
if (!chain.executionBuilder) {
throw Error("executionBuilder required");
}
Expand All @@ -302,7 +309,7 @@ async function getExecutionPayloadParentHash(
eth1: IEth1ForBlockProduction;
config: IChainForkConfig;
},
state: CachedBeaconStateBellatrix
state: CachedBeaconStateExecutions
): Promise<{isPremerge: true} | {isPremerge: false; parentHash: Root}> {
// Use different POW block hash parent for block production based on merge status.
// Returned value of null == using an empty ExecutionPayload value
Expand Down
8 changes: 4 additions & 4 deletions packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {allForks} from "@lodestar/types";
import {
computeStartSlotAtEpoch,
computeTimeAtSlot,
isBellatrixBlockBodyType,
isBellatrixStateType,
isExecutionBlockBodyType,
isExecutionStateType,
isExecutionEnabled,
getProposerSignatureSet,
} from "@lodestar/state-transition";
Expand Down Expand Up @@ -126,9 +126,9 @@ export async function validateGossipBlock(
// [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.bellatrix) {
if (!isBellatrixBlockBodyType(block.body)) throw Error("Not merge block type");
if (!isExecutionBlockBodyType(block.body)) throw Error("Not merge block type");
const executionPayload = block.body.executionPayload;
if (isBellatrixStateType(blockState) && isExecutionEnabled(blockState, block)) {
if (isExecutionStateType(blockState) && isExecutionEnabled(blockState, block)) {
const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime);
if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) {
throw new BlockGossipError(GossipAction.REJECT, {
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/execution/builder/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
return this.api.registerValidator(registrations);
}

async getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<bellatrix.ExecutionPayloadHeader> {
async getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<allForks.ExecutionPayloadHeader> {
const {data: signedBid} = await this.api.getHeader(slot, parentHash, proposerPubKey);
const executionPayloadHeader = signedBid.message.header;
return executionPayloadHeader;
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/execution/builder/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export interface IExecutionBuilder {
updateStatus(shouldEnable: boolean): void;
checkStatus(): Promise<void>;
registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<bellatrix.ExecutionPayloadHeader>;
getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<allForks.ExecutionPayloadHeader>;
submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise<allForks.SignedBeaconBlock>;
}
17 changes: 12 additions & 5 deletions packages/beacon-node/src/execution/engine/http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {bellatrix, RootHex} from "@lodestar/types";
import {RootHex, allForks, capella} from "@lodestar/types";
import {BYTES_PER_LOGS_BLOOM} from "@lodestar/params";
import {fromHex} from "@lodestar/utils";

Expand Down Expand Up @@ -110,7 +110,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
*
* If any of the above fails due to errors unrelated to the normal processing flow of the method, client software MUST respond with an error object.
*/
async notifyNewPayload(executionPayload: bellatrix.ExecutionPayload): Promise<ExecutePayloadResponse> {
async notifyNewPayload(executionPayload: allForks.ExecutionPayload): Promise<ExecutePayloadResponse> {
const method = "engine_newPayloadV1";
const serializedExecutionPayload = serializeExecutionPayload(executionPayload);
const {status, latestValidHash, validationError} = await this.rpc
Expand Down Expand Up @@ -264,7 +264,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
* 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<bellatrix.ExecutionPayload> {
async getPayload(payloadId: PayloadId): Promise<allForks.ExecutionPayload> {
const method = "engine_getPayloadV1";
const executionPayloadRpc = await this.rpc.fetchWithRetries<
EngineApiRpcReturnTypes[typeof method],
Expand Down Expand Up @@ -371,9 +371,13 @@ type ExecutionPayloadRpc = {
baseFeePerGas: QUANTITY;
blockHash: DATA; // 32 bytes
transactions: DATA[];
withdrawals?: DATA[]; // Capella hardfork
};

export function serializeExecutionPayload(data: bellatrix.ExecutionPayload): ExecutionPayloadRpc {
export function serializeExecutionPayload(data: allForks.ExecutionPayload): ExecutionPayloadRpc {
if ((data as capella.ExecutionPayload).withdrawals !== undefined) {
throw Error("Capella Not implemented");
}
return {
parentHash: bytesToData(data.parentHash),
feeRecipient: bytesToData(data.feeRecipient),
Expand All @@ -392,7 +396,10 @@ export function serializeExecutionPayload(data: bellatrix.ExecutionPayload): Exe
};
}

export function parseExecutionPayload(data: ExecutionPayloadRpc): bellatrix.ExecutionPayload {
export function parseExecutionPayload(data: ExecutionPayloadRpc): allForks.ExecutionPayload {
if (data.withdrawals !== undefined) {
throw Error("Capella Not implemented");
}
return {
parentHash: dataToBytes(data.parentHash, 32),
feeRecipient: dataToBytes(data.feeRecipient, 20),
Expand Down
6 changes: 3 additions & 3 deletions packages/beacon-node/src/execution/engine/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {bellatrix, RootHex} from "@lodestar/types";
import {RootHex, allForks} from "@lodestar/types";
import {DATA, QUANTITY} from "../../eth1/provider/utils.js";
import {PayloadIdCache, PayloadId, ApiPayloadAttributes} from "./payloadIdCache.js";

Expand Down Expand Up @@ -71,7 +71,7 @@ export interface IExecutionEngine {
*
* Should be called in advance before, after or in parallel to block processing
*/
notifyNewPayload(executionPayload: bellatrix.ExecutionPayload): Promise<ExecutePayloadResponse>;
notifyNewPayload(executionPayload: allForks.ExecutionPayload): Promise<ExecutePayloadResponse>;

/**
* Signal fork choice updates
Expand Down Expand Up @@ -99,7 +99,7 @@ export interface IExecutionEngine {
* Required for block producing
* https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/validator.md#get_payload
*/
getPayload(payloadId: PayloadId): Promise<bellatrix.ExecutionPayload>;
getPayload(payloadId: PayloadId): Promise<allForks.ExecutionPayload>;

exchangeTransitionConfigurationV1(
transitionConfiguration: TransitionConfigurationV1
Expand Down
5 changes: 4 additions & 1 deletion packages/beacon-node/src/network/gossip/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
// )[:20]
// ```
// https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/p2p-interface.md#topics-and-messages
//
// TODO: check if the capella handling is same as the other forks
case ForkName.altair:
case ForkName.bellatrix: {
case ForkName.bellatrix:
case ForkName.capella: {
vec = [MESSAGE_DOMAIN_VALID_SNAPPY, intToBytes(msg.topic.length, 8), Buffer.from(msg.topic), msg.data];
break;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/node/notifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {ProtoBlock} from "@lodestar/fork-choice";
import {ErrorAborted, ILogger, sleep, prettyBytes} from "@lodestar/utils";
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params";
import {computeEpochAtSlot, isBellatrixCachedStateType, isMergeTransitionComplete} from "@lodestar/state-transition";
import {computeEpochAtSlot, isExecutionCachedStateType, isMergeTransitionComplete} from "@lodestar/state-transition";
import {IBeaconChain} from "../chain/index.js";
import {INetwork} from "../network/index.js";
import {IBeaconSync, SyncState} from "../sync/index.js";
Expand Down Expand Up @@ -158,7 +158,7 @@ function getExecutionInfo(
const executionStatusStr = headInfo.executionStatus.toLowerCase();

// Add execution status to notifier only if head is on/post bellatrix
if (isBellatrixCachedStateType(headState)) {
if (isExecutionCachedStateType(headState)) {
if (isMergeTransitionComplete(headState)) {
return [`execution: ${executionStatusStr}(${prettyBytes(headInfo.executionPayloadBlockHash ?? "empty")})`];
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/test/sim/merge-interop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import net from "node:net";
import {spawn} from "node:child_process";
import {Context} from "mocha";
import {fromHexString} from "@chainsafe/ssz";
import {isBellatrixStateType, isMergeTransitionComplete} from "@lodestar/state-transition";
import {isExecutionStateType, isMergeTransitionComplete} from "@lodestar/state-transition";
import {LogLevel, sleep, TimestampFormatCode} from "@lodestar/utils";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {IChainConfig} from "@lodestar/config";
Expand Down Expand Up @@ -418,7 +418,7 @@ describe("executionEngine / ExecutionEngineHttp", function () {
// By this slot, ttd should be reached and merge complete
case Number(ttd) + 3: {
const headState = bn.chain.getHeadState();
if (!(isBellatrixStateType(headState) && isMergeTransitionComplete(headState))) {
if (!(isExecutionStateType(headState) && isMergeTransitionComplete(headState))) {
reject("Merge not completed");
}

Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/test/spec/presets/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const fork: TestRunnerFn<ForkStateCase, BeaconStateAllForks> = (forkNext)
return slotFns.upgradeStateToAltair(preState as CachedBeaconStatePhase0);
case ForkName.bellatrix:
return slotFns.upgradeStateToBellatrix(preState as CachedBeaconStateAltair);
case ForkName.capella:
throw Error("capella upgrade not implemented yet");
}
},
options: {
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/test/spec/presets/fork_choice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect} from "chai";
import {BeaconStateAllForks, isBellatrixStateType} from "@lodestar/state-transition";
import {BeaconStateAllForks, isExecutionStateType} from "@lodestar/state-transition";
import {InputType} from "@lodestar/spec-test-util";
import {toHexString} from "@chainsafe/ssz";
import {CheckpointWithHex, ForkChoice} from "@lodestar/fork-choice";
Expand Down Expand Up @@ -46,7 +46,7 @@ export const forkChoiceTest: TestRunnerFn<ForkChoiceTestCase, void> = (fork) =>
const clock = new ClockStopped(currentSlot);
const eth1 = new Eth1ForBlockProductionMock();
const executionEngine = new ExecutionEngineMock({
genesisBlockHash: isBellatrixStateType(anchorState)
genesisBlockHash: isExecutionStateType(anchorState)
? toHexString(anchorState.latestExecutionPayloadHeader.blockHash)
: ZERO_HASH_HEX,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/test/spec/presets/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ function getTransitionConfig(fork: ForkName, forkEpoch: number): Partial<IChainC
return {ALTAIR_FORK_EPOCH: forkEpoch};
case ForkName.bellatrix:
return {ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: forkEpoch};
case ForkName.capella:
return {ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0, CAPELLA_FORK_EPOCH: forkEpoch};
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/beacon-node/test/spec/utils/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,11 @@ export function getConfig(fork: ForkName, forkEpoch = 0): IChainForkConfig {
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: forkEpoch,
});
case ForkName.capella:
return createIChainForkConfig({
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: forkEpoch,
});
}
}

0 comments on commit c9f07a4

Please sign in to comment.