Skip to content

Commit

Permalink
feat: upgrade 7002 exits to withdrawal request (#6736)
Browse files Browse the repository at this point in the history
* feat: upgrade 7002 exits to withdrawal request

* fix types

* fix types and references

* further fix the types references and get build passing

* update the process ops fn but needs to be extended by maxeb
  • Loading branch information
g11tech committed Jun 25, 2024
1 parent 62d401c commit a6e21b2
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 52 deletions.
3 changes: 2 additions & 1 deletion packages/beacon-node/src/execution/engine/payloadIdCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export type DepositReceiptV1 = {
index: QUANTITY;
};

export type ExecutionLayerExitV1 = {
export type ExecutionLayerWithdrawalRequestV1 = {
sourceAddress: DATA;
validatorPubkey: DATA;
amount: QUANTITY;
};

type FcuAttributes = {headBlockHash: DATA; finalizedBlockHash: DATA} & Omit<PayloadAttributesRpc, "withdrawals">;
Expand Down
50 changes: 31 additions & 19 deletions packages/beacon-node/src/execution/engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
quantityToBigint,
} from "../../eth1/provider/utils.js";
import {ExecutionPayloadStatus, BlobsBundle, PayloadAttributes, VersionedHashes} from "./interface.js";
import {WithdrawalV1, DepositReceiptV1, ExecutionLayerExitV1} from "./payloadIdCache.js";
import {WithdrawalV1, DepositReceiptV1, ExecutionLayerWithdrawalRequestV1} from "./payloadIdCache.js";

/* eslint-disable @typescript-eslint/naming-convention */

Expand Down Expand Up @@ -119,14 +119,14 @@ export type ExecutionPayloadBodyRpc = {
transactions: DATA[];
withdrawals: WithdrawalV1[] | null | undefined;
depositReceipts: DepositReceiptV1[] | null | undefined;
exits: ExecutionLayerExitV1[] | null | undefined;
withdrawalRequests: ExecutionLayerWithdrawalRequestV1[] | null | undefined;
};

export type ExecutionPayloadBody = {
transactions: bellatrix.Transaction[];
withdrawals: capella.Withdrawals | null;
depositReceipts: electra.DepositReceipts | null;
exits: electra.ExecutionLayerExits | null;
withdrawalRequests: electra.ExecutionLayerWithdrawalRequests | null;
};

export type ExecutionPayloadRpc = {
Expand All @@ -149,7 +149,7 @@ export type ExecutionPayloadRpc = {
excessBlobGas?: QUANTITY; // DENEB
parentBeaconBlockRoot?: QUANTITY; // DENEB
depositReceipts?: DepositReceiptRpc[]; // ELECTRA
exits?: ExecutionLayerExitRpc[]; // ELECTRA
withdrawalRequests?: ExecutionLayerWithdrawalRequestRpc[]; // ELECTRA
};

export type WithdrawalRpc = {
Expand All @@ -160,7 +160,7 @@ export type WithdrawalRpc = {
};

export type DepositReceiptRpc = DepositReceiptV1;
export type ExecutionLayerExitRpc = ExecutionLayerExitV1;
export type ExecutionLayerWithdrawalRequestRpc = ExecutionLayerWithdrawalRequestV1;

export type VersionedHashesRpc = DATA[];

Expand Down Expand Up @@ -215,9 +215,9 @@ export function serializeExecutionPayload(fork: ForkName, data: ExecutionPayload

// ELECTRA adds depositReceipts to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.electra) {
const {depositReceipts, exits} = data as electra.ExecutionPayload;
const {depositReceipts, withdrawalRequests} = data as electra.ExecutionPayload;
payload.depositReceipts = depositReceipts.map(serializeDepositReceipt);
payload.exits = exits.map(serializeExecutionLayerExit);
payload.withdrawalRequests = withdrawalRequests.map(serializeExecutionLayerWithdrawalRequest);
}

return payload;
Expand Down Expand Up @@ -306,7 +306,7 @@ export function parseExecutionPayload(
}

if (ForkSeq[fork] >= ForkSeq.electra) {
const {depositReceipts, exits} = data;
const {depositReceipts, withdrawalRequests} = data;
// Geth can also reply with null
if (depositReceipts == null) {
throw Error(
Expand All @@ -315,12 +315,14 @@ export function parseExecutionPayload(
}
(executionPayload as electra.ExecutionPayload).depositReceipts = depositReceipts.map(deserializeDepositReceipt);

if (exits == null) {
if (withdrawalRequests == null) {
throw Error(
`exits missing for ${fork} >= electra executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
`withdrawalRequests missing for ${fork} >= electra executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
(executionPayload as electra.ExecutionPayload).exits = exits.map(deserializeExecutionLayerExit);
(executionPayload as electra.ExecutionPayload).withdrawalRequests = withdrawalRequests.map(
deserializeExecutionLayerWithdrawalRequest
);
}

return {executionPayload, executionPayloadValue, blobsBundle, shouldOverrideBuilder};
Expand Down Expand Up @@ -409,17 +411,23 @@ export function deserializeDepositReceipt(serialized: DepositReceiptRpc): electr
} as electra.DepositReceipt;
}

export function serializeExecutionLayerExit(exit: electra.ExecutionLayerExit): ExecutionLayerExitRpc {
export function serializeExecutionLayerWithdrawalRequest(
withdrawalRequest: electra.ExecutionLayerWithdrawalRequest
): ExecutionLayerWithdrawalRequestRpc {
return {
sourceAddress: bytesToData(exit.sourceAddress),
validatorPubkey: bytesToData(exit.validatorPubkey),
sourceAddress: bytesToData(withdrawalRequest.sourceAddress),
validatorPubkey: bytesToData(withdrawalRequest.validatorPubkey),
amount: numToQuantity(withdrawalRequest.amount),
};
}

export function deserializeExecutionLayerExit(exit: ExecutionLayerExitRpc): electra.ExecutionLayerExit {
export function deserializeExecutionLayerWithdrawalRequest(
withdrawalRequest: ExecutionLayerWithdrawalRequestRpc
): electra.ExecutionLayerWithdrawalRequest {
return {
sourceAddress: dataToBytes(exit.sourceAddress, 20),
validatorPubkey: dataToBytes(exit.validatorPubkey, 48),
sourceAddress: dataToBytes(withdrawalRequest.sourceAddress, 20),
validatorPubkey: dataToBytes(withdrawalRequest.validatorPubkey, 48),
amount: quantityToNum(withdrawalRequest.amount),
};
}

Expand All @@ -429,7 +437,9 @@ export function deserializeExecutionPayloadBody(data: ExecutionPayloadBodyRpc |
transactions: data.transactions.map((tran) => dataToBytes(tran, null)),
withdrawals: data.withdrawals ? data.withdrawals.map(deserializeWithdrawal) : null,
depositReceipts: data.depositReceipts ? data.depositReceipts.map(deserializeDepositReceipt) : null,
exits: data.exits ? data.exits.map(deserializeExecutionLayerExit) : null,
withdrawalRequests: data.withdrawalRequests
? data.withdrawalRequests.map(deserializeExecutionLayerWithdrawalRequest)
: null,
}
: null;
}
Expand All @@ -440,7 +450,9 @@ export function serializeExecutionPayloadBody(data: ExecutionPayloadBody | null)
transactions: data.transactions.map((tran) => bytesToData(tran)),
withdrawals: data.withdrawals ? data.withdrawals.map(serializeWithdrawal) : null,
depositReceipts: data.depositReceipts ? data.depositReceipts.map(serializeDepositReceipt) : null,
exits: data.exits ? data.exits.map(serializeExecutionLayerExit) : null,
withdrawalRequests: data.withdrawalRequests
? data.withdrawalRequests.map(serializeExecutionLayerWithdrawalRequest)
: null,
}
: null;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/light-client/src/spec/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export function upgradeLightClientHeader(
case ForkName.electra:
(upgradedHeader as LightClientHeader<ForkName.electra>).execution.depositReceiptsRoot =
ssz.electra.LightClientHeader.fields.execution.fields.depositReceiptsRoot.defaultValue();
(upgradedHeader as electra.LightClientHeader).execution.exitsRoot =
ssz.electra.LightClientHeader.fields.execution.fields.exitsRoot.defaultValue();
(upgradedHeader as electra.LightClientHeader).execution.withdrawalRequestsRoot =
ssz.electra.LightClientHeader.fields.execution.fields.withdrawalRequestsRoot.defaultValue();

// Break if no further upgrades is required else fall through
if (ForkSeq[targetFork] <= ForkSeq.electra) break;
Expand Down Expand Up @@ -158,7 +158,7 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: LightC
if (epoch < config.ELECTRA_FORK_EPOCH) {
if (
(header as LightClientHeader<ForkName.electra>).execution.depositReceiptsRoot !== undefined ||
(header as LightClientHeader<ForkName.electra>).execution.exitsRoot !== undefined
(header as LightClientHeader<ForkName.electra>).execution.withdrawalRequestsRoot !== undefined
) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/params/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const {
KZG_COMMITMENT_INCLUSION_PROOF_DEPTH,

MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD,
MAX_EXECUTION_LAYER_EXITS,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
MAX_ATTESTER_SLASHINGS_ELECTRA,
MAX_ATTESTATIONS_ELECTRA,
} = activePreset;
Expand Down
2 changes: 1 addition & 1 deletion packages/params/src/presets/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const mainnetPreset: BeaconPreset = {

// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192,
MAX_EXECUTION_LAYER_EXITS: 16,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16,
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
MAX_ATTESTATIONS_ELECTRA: 8,
};
2 changes: 1 addition & 1 deletion packages/params/src/presets/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const minimalPreset: BeaconPreset = {

// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4,
MAX_EXECUTION_LAYER_EXITS: 16,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16,
MAX_ATTESTER_SLASHINGS_ELECTRA: 1,
MAX_ATTESTATIONS_ELECTRA: 8,
};
4 changes: 2 additions & 2 deletions packages/params/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export type BeaconPreset = {

// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number;
MAX_EXECUTION_LAYER_EXITS: number;
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: number;
MAX_ATTESTER_SLASHINGS_ELECTRA: number;
MAX_ATTESTATIONS_ELECTRA: number;
};
Expand Down Expand Up @@ -176,7 +176,7 @@ export const beaconPresetTypes: BeaconPresetTypes = {

// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: "number",
MAX_EXECUTION_LAYER_EXITS: "number",
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: "number",
MAX_ATTESTER_SLASHINGS_ELECTRA: "number",
MAX_ATTESTATIONS_ELECTRA: "number",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,33 @@ import {isActiveValidator} from "../util/index.js";
import {CachedBeaconStateElectra} from "../types.js";
import {initiateValidatorExit} from "./index.js";

const FULL_EXIT_REQUEST_AMOUNT = 0;
/**
* Process execution layer exit messages and initiate exit incase they belong to a valid active validator
* otherwise silent ignore.
*/
export function processExecutionLayerExit(state: CachedBeaconStateElectra, exit: electra.ExecutionLayerExit): void {
const validator = isValidExecutionLayerExit(state, exit);
if (validator === null) {
return;
}
export function processExecutionLayerWithdrawalRequest(
state: CachedBeaconStateElectra,
withdrawalRequest: electra.ExecutionLayerWithdrawalRequest
): void {
const isFullExitRequest = withdrawalRequest.amount === FULL_EXIT_REQUEST_AMOUNT;

if (isFullExitRequest) {
const validator = isValidExecutionLayerExit(state, withdrawalRequest);
if (validator === null) {
return;
}

initiateValidatorExit(state, validator);
initiateValidatorExit(state, validator);
} else {
// partial withdral request add codeblock
}
}

// TODO electra : add pending withdrawal check before exit
export function isValidExecutionLayerExit(
state: CachedBeaconStateElectra,
exit: electra.ExecutionLayerExit
exit: electra.ExecutionLayerWithdrawalRequest
): CompositeViewDU<typeof ssz.phase0.Validator> | null {
const {config, epochCtx} = state;
const validatorIndex = epochCtx.getValidatorIndex(exit.validatorPubkey);
Expand Down
8 changes: 4 additions & 4 deletions packages/state-transition/src/block/processOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {processProposerSlashing} from "./processProposerSlashing.js";
import {processAttesterSlashing} from "./processAttesterSlashing.js";
import {processDeposit} from "./processDeposit.js";
import {processVoluntaryExit} from "./processVoluntaryExit.js";
import {processExecutionLayerExit} from "./processExecutionLayerExit.js";
import {processExecutionLayerWithdrawalRequest} from "./processExecutionLayerWithdrawalRequest.js";
import {processBlsToExecutionChange} from "./processBlsToExecutionChange.js";
import {processDepositReceipt} from "./processDepositReceipt.js";
import {ProcessBlockOpts} from "./types.js";
Expand All @@ -19,7 +19,7 @@ export {
processAttestations,
processDeposit,
processVoluntaryExit,
processExecutionLayerExit,
processExecutionLayerWithdrawalRequest,
processBlsToExecutionChange,
processDepositReceipt,
};
Expand Down Expand Up @@ -55,8 +55,8 @@ export function processOperations(
processVoluntaryExit(state, voluntaryExit, opts.verifySignatures);
}
if (fork >= ForkSeq.electra) {
for (const elExit of (body as electra.BeaconBlockBody).executionPayload.exits) {
processExecutionLayerExit(state as CachedBeaconStateElectra, elExit);
for (const elWithdrawalRequest of (body as electra.BeaconBlockBody).executionPayload.withdrawalRequests) {
processExecutionLayerWithdrawalRequest(state as CachedBeaconStateElectra, elWithdrawalRequest);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache
epoch: stateDeneb.epochCtx.epoch,
});

// latestExecutionPayloadHeader's depositReceiptsRoot and exitsRoot set to zeros by default
// latestExecutionPayloadHeader's depositReceiptsRoot and withdrawalRequestsRoot set to zeros by default
// default value of depositReceiptsStartIndex is UNSET_DEPOSIT_RECEIPTS_START_INDEX
stateElectra.depositReceiptsStartIndex = UNSET_DEPOSIT_RECEIPTS_START_INDEX;

Expand Down
7 changes: 4 additions & 3 deletions packages/state-transition/src/util/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,10 @@ export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: Executio
if (fork >= ForkSeq.electra) {
(bellatrixPayloadFields as electra.ExecutionPayloadHeader).depositReceiptsRoot =
ssz.electra.DepositReceipts.hashTreeRoot((payload as electra.ExecutionPayload).depositReceipts);
(bellatrixPayloadFields as electra.ExecutionPayloadHeader).exitsRoot = ssz.electra.ExecutionLayerExits.hashTreeRoot(
(payload as electra.ExecutionPayload).exits
);
(bellatrixPayloadFields as electra.ExecutionPayloadHeader).withdrawalRequestsRoot =
ssz.electra.ExecutionLayerWithdrawalRequests.hashTreeRoot(
(payload as electra.ExecutionPayload).withdrawalRequests
);
}

return bellatrixPayloadFields;
Expand Down
16 changes: 10 additions & 6 deletions packages/types/src/electra/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
MAX_COMMITTEES_PER_SLOT,
MAX_ATTESTATIONS_ELECTRA,
MAX_ATTESTER_SLASHINGS_ELECTRA,
MAX_EXECUTION_LAYER_EXITS,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
} from "@lodestar/params";
import {ssz as primitiveSsz} from "../primitive/index.js";
import {ssz as phase0Ssz} from "../phase0/index.js";
Expand Down Expand Up @@ -117,20 +117,24 @@ export const DepositReceipt = new ContainerType(

export const DepositReceipts = new ListCompositeType(DepositReceipt, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD);

export const ExecutionLayerExit = new ContainerType(
export const ExecutionLayerWithdrawalRequest = new ContainerType(
{
sourceAddress: ExecutionAddress,
validatorPubkey: BLSPubkey,
amount: UintNum64,
},
{typeName: "ExecutionLayerExit", jsonCase: "eth2"}
{typeName: "ExecutionLayerWithdrawalRequest", jsonCase: "eth2"}
);
export const ExecutionLayerWithdrawalRequests = new ListCompositeType(
ExecutionLayerWithdrawalRequest,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD
);
export const ExecutionLayerExits = new ListCompositeType(ExecutionLayerExit, MAX_EXECUTION_LAYER_EXITS);

export const ExecutionPayload = new ContainerType(
{
...denebSsz.ExecutionPayload.fields,
depositReceipts: DepositReceipts, // New in ELECTRA
exits: ExecutionLayerExits, // New in ELECTRA
withdrawalRequests: ExecutionLayerWithdrawalRequests, // New in ELECTRA
},
{typeName: "ExecutionPayload", jsonCase: "eth2"}
);
Expand All @@ -139,7 +143,7 @@ export const ExecutionPayloadHeader = new ContainerType(
{
...denebSsz.ExecutionPayloadHeader.fields,
depositReceiptsRoot: Root, // New in ELECTRA
exitsRoot: Root, // New in ELECTRA
withdrawalRequestsRoot: Root, // New in ELECTRA
},
{typeName: "ExecutionPayloadHeader", jsonCase: "eth2"}
);
Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/electra/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type SignedAggregateAndProof = ValueOf<typeof ssz.SignedAggregateAndProof
export type DepositReceipt = ValueOf<typeof ssz.DepositReceipt>;
export type DepositReceipts = ValueOf<typeof ssz.DepositReceipts>;

export type ExecutionLayerExit = ValueOf<typeof ssz.ExecutionLayerExit>;
export type ExecutionLayerExits = ValueOf<typeof ssz.ExecutionLayerExits>;
export type ExecutionLayerWithdrawalRequest = ValueOf<typeof ssz.ExecutionLayerWithdrawalRequest>;
export type ExecutionLayerWithdrawalRequests = ValueOf<typeof ssz.ExecutionLayerWithdrawalRequests>;

export type ExecutionPayload = ValueOf<typeof ssz.ExecutionPayload>;
export type ExecutionPayloadHeader = ValueOf<typeof ssz.ExecutionPayloadHeader>;
Expand Down
2 changes: 1 addition & 1 deletion packages/validator/src/util/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit

// ELECTRA
MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: electraForkRelevant,
MAX_EXECUTION_LAYER_EXITS: electraForkRelevant,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: electraForkRelevant,
MAX_ATTESTER_SLASHINGS_ELECTRA: electraForkRelevant,
MAX_ATTESTATIONS_ELECTRA: electraForkRelevant,
};
Expand Down

0 comments on commit a6e21b2

Please sign in to comment.