diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index 9cee4d0842a8..9e813194c104 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,5 +1,5 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella} from "@lodestar/types"; +import {allForks, altair, capella, eip4844} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; @@ -8,7 +8,8 @@ import {processBlockHeader} from "./processBlockHeader.js"; import {processEth1Data} from "./processEth1Data.js"; import {processOperations} from "./processOperations.js"; import {processRandao} from "./processRandao.js"; -import {BlockExternalData} from "./externalData.js"; +import {processBlobKzgCommitments} from "./processBlobKzgCommitments.js"; +import {BlockExternalData, DataAvailableStatus} from "./externalData.js"; import {processWithdrawals} from "./processWithdrawals.js"; // Spec tests @@ -46,7 +47,9 @@ export function processBlock( // on the randao_mix computed with the reveal of the previous block. if (fork >= ForkSeq.bellatrix && isExecutionEnabled(state as CachedBeaconStateBellatrix, block)) { const fullOrBlindedPayload = getFullOrBlindedPayload(block); - if (fork >= ForkSeq.capella) { + // TODO EIP-4844: Allow to disable withdrawals for interop testing + // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals + if (fork >= ForkSeq.capella && !opts?.disabledWithdrawals) { processWithdrawals( state as CachedBeaconStateCapella, fullOrBlindedPayload as capella.FullOrBlindedExecutionPayload @@ -61,4 +64,21 @@ export function processBlock( if (fork >= ForkSeq.altair) { processSyncAggregate(state, block as altair.BeaconBlock, verifySignatures); } + + if (fork >= ForkSeq.eip4844) { + processBlobKzgCommitments(block.body as eip4844.BeaconBlockBody); + + // New in EIP-4844, note: Can sync optimistically without this condition, see note on `is_data_available` + // NOTE: Ommitted and should be verified beforehand + + // assert is_data_available(block.slot, hash_tree_root(block), block.body.blob_kzg_commitments) + switch (externalData.dataAvailableStatus) { + case DataAvailableStatus.preEIP4844: + throw Error("dataAvailableStatus preEIP4844"); + case DataAvailableStatus.notAvailable: + throw Error("dataAvailableStatus notAvailable"); + case DataAvailableStatus.available: + break; // ok + } + } } diff --git a/packages/state-transition/src/block/processBlobKzgCommitments.ts b/packages/state-transition/src/block/processBlobKzgCommitments.ts new file mode 100644 index 000000000000..195ad056c26c --- /dev/null +++ b/packages/state-transition/src/block/processBlobKzgCommitments.ts @@ -0,0 +1,17 @@ +import {eip4844} from "@lodestar/types"; +import {verifyKzgCommitmentsAgainstTransactions} from "../util/index.js"; + +/** + * https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/eip4844/beacon-chain.md#blob-kzg-commitments + * + * def process_blob_kzg_commitments(state: BeaconState, body: BeaconBlockBody): + * assert verify_kzg_commitments_against_transactions( + * body.execution_payload.transactions, + * body.blob_kzg_commitments + * ) + */ +export function processBlobKzgCommitments(body: eip4844.BeaconBlockBody): void { + if (!verifyKzgCommitmentsAgainstTransactions(body.executionPayload.transactions, body.blobKzgCommitments)) { + throw Error("Invalid KZG commitments against transactions"); + } +} diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index 3db66703209c..d8d83d991475 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -1,4 +1,4 @@ -import {ssz, allForks, capella} from "@lodestar/types"; +import {ssz, allForks, capella, eip4844} from "@lodestar/types"; import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; import {ForkSeq} from "@lodestar/params"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; @@ -89,6 +89,13 @@ export function processExecutionPayload( ); } + if (fork >= ForkSeq.eip4844) { + // https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#process_execution_payload + (bellatrixPayloadFields as eip4844.ExecutionPayloadHeader).excessDataGas = (payload as + | eip4844.ExecutionPayloadHeader + | eip4844.ExecutionPayload).excessDataGas; + } + // TODO EIP-4844: Types are not happy by default. Since it's a generic allForks type going through ViewDU // transformation then into allForks, probably some weird intersection incompatibility happens state.latestExecutionPayloadHeader = state.config diff --git a/packages/state-transition/src/cache/types.ts b/packages/state-transition/src/cache/types.ts index 9010863cbab0..3d206fb16788 100644 --- a/packages/state-transition/src/cache/types.ts +++ b/packages/state-transition/src/cache/types.ts @@ -11,11 +11,12 @@ export type BeaconStateEip4844 = CompositeViewDU // - Works well as function argument and as generic type for allForks functions // // Quasy equivalent to -// CompositeViewDU +// CompositeViewDU // + future forks export type BeaconStateAllForks = | BeaconStatePhase0 | BeaconStateAltair | BeaconStateBellatrix | BeaconStateCapella | BeaconStateEip4844; + export type BeaconStateExecutions = BeaconStateBellatrix | BeaconStateCapella | BeaconStateEip4844; diff --git a/packages/state-transition/src/slot/index.ts b/packages/state-transition/src/slot/index.ts index d74a5222473f..73f1017865d6 100644 --- a/packages/state-transition/src/slot/index.ts +++ b/packages/state-transition/src/slot/index.ts @@ -6,6 +6,7 @@ import {ZERO_HASH} from "../constants/index.js"; export {upgradeStateToAltair} from "./upgradeStateToAltair.js"; export {upgradeStateToBellatrix} from "./upgradeStateToBellatrix.js"; export {upgradeStateToCapella} from "./upgradeStateToCapella.js"; +export {upgradeStateTo4844} from "./upgradeStateTo4844.js"; /** * Dial state to next slot. Common for all forks diff --git a/packages/state-transition/src/slot/upgradeStateTo4844.ts b/packages/state-transition/src/slot/upgradeStateTo4844.ts new file mode 100644 index 000000000000..0b26665be5be --- /dev/null +++ b/packages/state-transition/src/slot/upgradeStateTo4844.ts @@ -0,0 +1,33 @@ +import {ssz} from "@lodestar/types"; +import {CachedBeaconStateEip4844} from "../types.js"; +import {getCachedBeaconState} from "../cache/stateCache.js"; +import {CachedBeaconStateCapella} from "../types.js"; + +/** + * Upgrade a state from Capella to 4844. + */ +export function upgradeStateTo4844(stateCapella: CachedBeaconStateCapella): CachedBeaconStateEip4844 { + const {config} = stateCapella; + + const stateCapellaNode = ssz.capella.BeaconState.commitViewDU(stateCapella); + const state4844View = ssz.eip4844.BeaconState.getViewDU(stateCapellaNode); + + const state4844 = getCachedBeaconState(state4844View, stateCapella); + + state4844.fork = ssz.phase0.Fork.toViewDU({ + previousVersion: stateCapella.fork.currentVersion, + currentVersion: config.EIP4844_FORK_VERSION, + epoch: stateCapella.epochCtx.epoch, + }); + + // capella.latestExecutionPayloadHeader has 15 properties, adding only 1 extra will not increase the tree depth. + // The initial value of the new property 'excessDataGas' is zero, so the default tree value is correct. + // Just casting the same tree node to state4844 is sufficient and nothing has to be done. + // ``` + // state4844.latestExecutionPayloadHeader.excessDataGas = ssz.UintBn256.defaultValue() + // ``` + + state4844.commit(); + + return state4844; +} diff --git a/packages/state-transition/src/slot/upgradeStateToCapella.ts b/packages/state-transition/src/slot/upgradeStateToCapella.ts index 4dbd8b55b865..434af7c5664e 100644 --- a/packages/state-transition/src/slot/upgradeStateToCapella.ts +++ b/packages/state-transition/src/slot/upgradeStateToCapella.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js" import {getCachedBeaconState} from "../cache/stateCache.js"; /** - * Upgrade a state from altair to bellatrix. + * Upgrade a state from bellatrix to capella. */ export function upgradeStateToCapella(stateBellatrix: CachedBeaconStateBellatrix): CachedBeaconStateCapella { const {config} = stateBellatrix; diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 1dfc5fb4d5f6..0a001ec0faed 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -9,10 +9,17 @@ import { CachedBeaconStatePhase0, CachedBeaconStateAltair, CachedBeaconStateBellatrix, + CachedBeaconStateCapella, } from "./types.js"; import {computeEpochAtSlot} from "./util/index.js"; import {verifyProposerSignature} from "./signatureSets/index.js"; -import {processSlot, upgradeStateToAltair, upgradeStateToBellatrix, upgradeStateToCapella} from "./slot/index.js"; +import { + processSlot, + upgradeStateToAltair, + upgradeStateToBellatrix, + upgradeStateToCapella, + upgradeStateTo4844, +} from "./slot/index.js"; import {processBlock} from "./block/index.js"; import {processEpoch} from "./epoch/index.js"; import {BlockExternalData, DataAvailableStatus, ExecutionPayloadStatus} from "./block/externalData.js"; @@ -168,6 +175,9 @@ function processSlotsWithTransientCache( if (stateSlot === config.CAPELLA_FORK_EPOCH) { postState = upgradeStateToCapella(postState as CachedBeaconStateBellatrix) as CachedBeaconStateAllForks; } + if (stateSlot === config.EIP4844_FORK_EPOCH) { + postState = upgradeStateTo4844(postState as CachedBeaconStateCapella) as CachedBeaconStateAllForks; + } } else { postState.slot++; } diff --git a/packages/state-transition/src/util/executionEngine.ts b/packages/state-transition/src/util/executionEngine.ts deleted file mode 100644 index e1f24f61429e..000000000000 --- a/packages/state-transition/src/util/executionEngine.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {bellatrix} from "@lodestar/types"; -import {ForkSeq} from "@lodestar/params"; - -export type ExecutionEngine = { - /** - * Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. - * - * Note: `processExecutionPayload()` depends on process_randao function call as it retrieves the most recent randao - * mix from the state. Implementations that are considering parallel processing of execution payload with respect to - * beacon chain state transition function should work around this dependency. - */ - notifyNewPayload(seq: ForkSeq, executionPayload: bellatrix.ExecutionPayload): boolean; -};