Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/beacon-node/src/api/impl/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
BASE_REWARDS_PER_EPOCH,
BLOB_TX_TYPE,
BLS_WITHDRAWAL_PREFIX,
BUILDER_DEPOSIT_REQUEST_TYPE,
BUILDER_EXIT_REQUEST_TYPE,
BUILDER_INDEX_FLAG,
BUILDER_INDEX_SELF_BUILD,
BUILDER_PAYMENT_THRESHOLD_DENOMINATOR,
Expand All @@ -21,6 +23,7 @@ import {
DOMAIN_BEACON_BUILDER,
DOMAIN_BEACON_PROPOSER,
DOMAIN_BLS_TO_EXECUTION_CHANGE,
DOMAIN_BUILDER_DEPOSIT,
DOMAIN_CONTRIBUTION_AND_PROOF,
DOMAIN_DEPOSIT,
DOMAIN_PROPOSER_PREFERENCES,
Expand All @@ -38,6 +41,7 @@ import {
JUSTIFICATION_BITS_LENGTH,
MAX_CONCURRENT_REQUESTS,
NODE_ID_BITS,
PAYLOAD_BUILDER_VERSION,
PROPOSER_WEIGHT,
SYNC_COMMITTEE_SUBNET_COUNT,
SYNC_REWARD_WEIGHT,
Expand Down Expand Up @@ -140,6 +144,10 @@ export const specConstants = {
BUILDER_INDEX_SELF_BUILD,
BUILDER_PAYMENT_THRESHOLD_NUMERATOR,
BUILDER_PAYMENT_THRESHOLD_DENOMINATOR,
PAYLOAD_BUILDER_VERSION,
DOMAIN_BUILDER_DEPOSIT,
BUILDER_DEPOSIT_REQUEST_TYPE: toHexByte(BUILDER_DEPOSIT_REQUEST_TYPE),
BUILDER_EXIT_REQUEST_TYPE: toHexByte(BUILDER_EXIT_REQUEST_TYPE),
};

/** Convert single-byte numbers to hex strings for API spec compliance */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function verifyExecutionPayloadEnvelope(
// Verify execution_requests_root matches bid commitment.
// Can be skipped if already verified during gossip validation.
if (verifyExecutionRequestsRoot) {
const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
if (!byteArrayEquals(requestsRoot, bid.executionRequestsRoot)) {
throw new Error(
`Execution requests root mismatch envelope=${toRootHex(requestsRoot)} bid=${toRootHex(bid.executionRequestsRoot)}`
Expand Down
5 changes: 2 additions & 3 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
ValidatorIndex,
Wei,
deneb,
electra,
gloas,
isBlindedBeaconBlock,
phase0,
Expand Down Expand Up @@ -925,10 +924,10 @@ export class BeaconChain implements IBeaconChain {
async getParentExecutionRequests(
parentBlockSlot: Slot,
parentBlockRootHex: RootHex
): Promise<electra.ExecutionRequests> {
): Promise<gloas.ExecutionRequests> {
// at the fork boundary, parent is pre-gloas
if (!isForkPostGloas(this.config.getForkName(parentBlockSlot))) {
return ssz.electra.ExecutionRequests.defaultValue();
return ssz.gloas.ExecutionRequests.defaultValue();
}
const envelope = await this.getExecutionPayloadEnvelope(parentBlockSlot, parentBlockRootHex);
if (envelope === null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {GossipActionError} from "./gossipValidation.js";

export enum ExecutionPayloadBidErrorCode {
BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
INVALID_BUILDER_VERSION = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_BUILDER_VERSION",
NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
Expand All @@ -21,6 +22,12 @@ export enum ExecutionPayloadBidErrorCode {

export type ExecutionPayloadBidErrorType =
| {code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE; builderIndex: BuilderIndex}
| {
code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION;
builderIndex: BuilderIndex;
version: number;
expectedVersion: number;
}
| {
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT;
builderIndex: BuilderIndex;
Expand Down
3 changes: 1 addition & 2 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
altair,
capella,
deneb,
electra,
gloas,
phase0,
rewards,
Expand Down Expand Up @@ -236,7 +235,7 @@ export interface IBeaconChain {
blockSlot: Slot,
blockRootHex: string
): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<gloas.ExecutionRequests>;

produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export type ProduceFullGloas = {
type: BlockType.Full;
fork: ForkPostGloas;
executionPayload: ExecutionPayload<ForkPostGloas>;
executionRequests: electra.ExecutionRequests;
executionRequests: gloas.ExecutionRequests;
blobsBundle: BlobsBundle<ForkPostGloas>;
cells: fulu.Cell[][];
parentBlockRoot: Root;
Expand Down Expand Up @@ -228,7 +228,7 @@ export async function produceBlockBody<T extends BlockType>(
);
const parentExecutionRequests = isExtendingPayload
? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
: ssz.electra.ExecutionRequests.defaultValue();
: ssz.gloas.ExecutionRequests.defaultValue();
executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;

const commonBlockBody = await commonBlockBodyPromise;
Expand Down Expand Up @@ -272,7 +272,7 @@ export async function produceBlockBody<T extends BlockType>(

// Get execution payload from EL
let parentBlockHash: Bytes32;
let parentExecutionRequests: electra.ExecutionRequests;
let parentExecutionRequests: gloas.ExecutionRequests;
// Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
// Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
Expand All @@ -285,7 +285,7 @@ export async function produceBlockBody<T extends BlockType>(
stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
} else {
parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
parentExecutionRequests = ssz.gloas.ExecutionRequests.defaultValue();
}
const prepareRes = await prepareExecutionPayload(
this,
Expand Down Expand Up @@ -350,7 +350,7 @@ export async function produceBlockBody<T extends BlockType>(
value: 0,
executionPayment: 0,
blobKzgCommitments: blobsBundle.commitments,
executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests),
};
const signedBid: gloas.SignedExecutionPayloadBid = {
message: bid,
Expand All @@ -375,7 +375,9 @@ export async function produceBlockBody<T extends BlockType>(
// Store execution payload data required to construct execution payload envelope later
const gloasResult = produceResult as ProduceFullGloas;
gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
gloasResult.executionRequests = executionRequests;
// EL response carries the gloas-extended ExecutionRequests (with builderDeposits/builderExits)
// when the fork is post-gloas; deserializer always produces the gloas shape.
gloasResult.executionRequests = executionRequests as gloas.ExecutionRequests;
gloasResult.blobsBundle = blobsBundle;
gloasResult.cells = cells;
gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {PublicKey} from "@chainsafe/blst";
import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
import {
computeEpochAtSlot,
createSingleSignatureSetFromComponents,
Expand Down Expand Up @@ -139,6 +140,17 @@ async function validateExecutionPayloadBid(
});
}

// [REJECT] The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e.
// `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`.
if (builder.version !== PAYLOAD_BUILDER_VERSION) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION,
builderIndex: bid.builderIndex,
version: builder.version,
expectedVersion: PAYLOAD_BUILDER_VERSION,
});
}

// [REJECT] `bid.execution_payment` is zero.
if (bid.executionPayment !== 0) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async function validateExecutionPayloadEnvelope(
}

// [REJECT] `hash_tree_root(envelope.execution_requests) == bid.execution_requests_root`
const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
if (!byteArrayEquals(requestsRoot, payloadInput.getBid().executionRequestsRoot)) {
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH,
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/execution/engine/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
if (executionRequests === undefined) {
throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
}
const serializedExecutionRequests = serializeExecutionRequests(executionRequests);
const serializedExecutionRequests = serializeExecutionRequests(fork, executionRequests);
engineRequest = {
method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
params: [
Expand Down
14 changes: 12 additions & 2 deletions packages/beacon-node/src/execution/engine/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {routes} from "@lodestar/api";
import {
BUILDER_DEPOSIT_REQUEST_TYPE,
BUILDER_EXIT_REQUEST_TYPE,
CONSOLIDATION_REQUEST_TYPE,
DEPOSIT_REQUEST_TYPE,
ForkName,
Expand Down Expand Up @@ -51,10 +53,18 @@ export enum ExecutionEngineState {
export type ExecutionRequestType =
| typeof DEPOSIT_REQUEST_TYPE
| typeof WITHDRAWAL_REQUEST_TYPE
| typeof CONSOLIDATION_REQUEST_TYPE;
| typeof CONSOLIDATION_REQUEST_TYPE
| typeof BUILDER_DEPOSIT_REQUEST_TYPE
| typeof BUILDER_EXIT_REQUEST_TYPE;

export function isExecutionRequestType(type: number): type is ExecutionRequestType {
return type === DEPOSIT_REQUEST_TYPE || type === WITHDRAWAL_REQUEST_TYPE || type === CONSOLIDATION_REQUEST_TYPE;
return (
type === DEPOSIT_REQUEST_TYPE ||
type === WITHDRAWAL_REQUEST_TYPE ||
type === CONSOLIDATION_REQUEST_TYPE ||
type === BUILDER_DEPOSIT_REQUEST_TYPE ||
type === BUILDER_EXIT_REQUEST_TYPE
);
}

export type ExecutePayloadResponse =
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/execution/engine/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
blobs,
proofs,
}),
executionRequests: serializeExecutionRequests({
executionRequests: serializeExecutionRequests(fork, {
Comment thread
ensi321 marked this conversation as resolved.
Outdated
deposits: ssz.electra.DepositRequests.defaultValue(),
withdrawals: ssz.electra.WithdrawalRequests.defaultValue(),
consolidations: ssz.electra.ConsolidationRequests.defaultValue(),
Expand Down
96 changes: 86 additions & 10 deletions packages/beacon-node/src/execution/engine/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
BUILDER_DEPOSIT_REQUEST_TYPE,
BUILDER_EXIT_REQUEST_TYPE,
BYTES_PER_FIELD_ELEMENT,
BYTES_PER_LOGS_BLOOM,
CELLS_PER_EXT_BLOB,
Comment thread
nflaig marked this conversation as resolved.
Expand Down Expand Up @@ -205,16 +207,27 @@ export type WithdrawalRpc = {
};

/**
* ExecutionRequestsRpc only holds at most 3 elements and no repeated type:
* - ssz'ed DepositRequests
* - ssz'ed WithdrawalRequests
* - ssz'ed ConsolidationRequests
* ExecutionRequestsRpc holds at most 5 elements and no repeated type, ordered by
* the single-byte request type prefix:
* - ssz'ed DepositRequests (0x00)
* - ssz'ed WithdrawalRequests (0x01)
* - ssz'ed ConsolidationRequests (0x02)
* - ssz'ed BuilderDepositRequests (0x03, GLOAS:EIP-8282)
* - ssz'ed BuilderExitRequests (0x04, GLOAS:EIP-8282)
*/
export type ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[];
export type ExecutionRequestsRpc = (
| DepositRequestsRpc
| WithdrawalRequestsRpc
| ConsolidationRequestsRpc
| BuilderDepositRequestsRpc
| BuilderExitRequestsRpc
)[];

export type DepositRequestsRpc = DATA;
export type WithdrawalRequestsRpc = DATA;
export type ConsolidationRequestsRpc = DATA;
export type BuilderDepositRequestsRpc = DATA;
export type BuilderExitRequestsRpc = DATA;

export type BlobAndProofRpc = {
blob: DATA;
Expand Down Expand Up @@ -339,7 +352,7 @@ export function parseExecutionPayload(
data = response.executionPayload;
blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined;
executionRequests = response.executionRequests
? deserializeExecutionRequests(response.executionRequests)
? deserializeExecutionRequests(fork, response.executionRequests)
: undefined;
shouldOverrideBuilder = response.shouldOverrideBuilder ?? false;
} else {
Expand Down Expand Up @@ -526,13 +539,36 @@ function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc):
return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null));
}

function serializeBuilderDepositRequests(
builderDepositRequests: gloas.BuilderDepositRequests
): BuilderDepositRequestsRpc {
const requestsBytes = ssz.gloas.BuilderDepositRequests.serialize(builderDepositRequests);
return bytesToData(prefixRequests(requestsBytes, BUILDER_DEPOSIT_REQUEST_TYPE));
}

function deserializeBuilderDepositRequests(serialized: BuilderDepositRequestsRpc): gloas.BuilderDepositRequests {
return ssz.gloas.BuilderDepositRequests.deserialize(dataToBytes(serialized, null));
}

function serializeBuilderExitRequests(builderExitRequests: gloas.BuilderExitRequests): BuilderExitRequestsRpc {
const requestsBytes = ssz.gloas.BuilderExitRequests.serialize(builderExitRequests);
return bytesToData(prefixRequests(requestsBytes, BUILDER_EXIT_REQUEST_TYPE));
}

function deserializeBuilderExitRequests(serialized: BuilderExitRequestsRpc): gloas.BuilderExitRequests {
return ssz.gloas.BuilderExitRequests.deserialize(dataToBytes(serialized, null));
}

/**
* This is identical to get_execution_requests_list in
* https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list
*
* Gloas extends the list with builder deposits (0x03) and builder exits (0x04) per
* https://github.com/ethereum/consensus-specs/pull/5359 (EIP-8282).
*/
export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc {
export function serializeExecutionRequests(fork: ForkName, executionRequests: ExecutionRequests): ExecutionRequestsRpc {
const {deposits, withdrawals, consolidations} = executionRequests;
const result = [];
const result: ExecutionRequestsRpc = [];

if (deposits.length !== 0) {
result.push(serializeDepositRequests(deposits));
Expand All @@ -546,14 +582,40 @@ export function serializeExecutionRequests(executionRequests: ExecutionRequests)
result.push(serializeConsolidationRequests(consolidations));
}

const gloasRequests = executionRequests as Partial<gloas.ExecutionRequests>;

// Builder requests (0x03/0x04) only exist post-gloas. Never emit them into a pre-gloas
// (newPayloadV4) request, symmetric with deserializeExecutionRequests rejecting them.
if (ForkSeq[fork] < ForkSeq.gloas) {
if (
(gloasRequests.builderDeposits !== undefined && gloasRequests.builderDeposits.length !== 0) ||
(gloasRequests.builderExits !== undefined && gloasRequests.builderExits.length !== 0)
) {
throw Error(`Builder requests are not supported pre-gloas fork=${fork}`);
}
return result;
}

if (gloasRequests.builderDeposits !== undefined && gloasRequests.builderDeposits.length !== 0) {
result.push(serializeBuilderDepositRequests(gloasRequests.builderDeposits));
}

if (gloasRequests.builderExits !== undefined && gloasRequests.builderExits.length !== 0) {
result.push(serializeBuilderExitRequests(gloasRequests.builderExits));
}

return result;
}

export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests {
const result: ExecutionRequests = {
export function deserializeExecutionRequests(fork: ForkName, serialized: ExecutionRequestsRpc): ExecutionRequests {
// Gloas-shaped result is a structural superset of electra/fulu — extra fields are dropped on
// assignment to fork-narrower types at the call site.
const result: gloas.ExecutionRequests = {
deposits: [],
withdrawals: [],
consolidations: [],
builderDeposits: [],
builderExits: [],
};

if (serialized.length === 0) {
Expand Down Expand Up @@ -601,6 +663,20 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc):
result.consolidations = deserializeConsolidationRequests(requests);
break;
}
case BUILDER_DEPOSIT_REQUEST_TYPE: {
if (ForkSeq[fork] < ForkSeq.gloas) {
throw Error(`Builder deposit request is not supported pre-gloas fork=${fork}`);
}
result.builderDeposits = deserializeBuilderDepositRequests(requests);
break;
}
case BUILDER_EXIT_REQUEST_TYPE: {
if (ForkSeq[fork] < ForkSeq.gloas) {
throw Error(`Builder exit request is not supported pre-gloas fork=${fork}`);
}
result.builderExits = deserializeBuilderExitRequests(requests);
break;
}
}
prevRequestType = currentRequestType;
}
Expand Down
Loading
Loading