diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index 89dc2b9d7a..fe17f65564 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -69,7 +69,38 @@ export type AccessEventFlags = { * * Experimental (do not implement) */ -export interface AccessWitnessInterface { + +export enum VerkleAccessedStateType { + BasicData = 'basicData', + CodeHash = 'codeHash', + Code = 'code', + Storage = 'storage', +} + +export type RawVerkleAccessedState = { + address: Address + treeIndex: number | bigint + chunkIndex: number + chunkKey: PrefixedHexString +} + +export type VerkleAccessedState = + | { + type: Exclude< + VerkleAccessedStateType, + VerkleAccessedStateType.Code | VerkleAccessedStateType.Storage + > + } + | { type: VerkleAccessedStateType.Code; codeOffset: number } + | { type: VerkleAccessedStateType.Storage; slot: bigint } + +export type VerkleAccessedStateWithAddress = VerkleAccessedState & { + address: Address + chunkKey: PrefixedHexString +} +export interface VerkleAccessWitnessInterface { + accesses(): Generator + rawAccesses(): Generator touchAndChargeProofOfAbsence(address: Address): bigint touchAndChargeMessageCall(address: Address): bigint touchAndChargeValueTransfer(target: Address): bigint @@ -101,8 +132,7 @@ export interface AccessWitnessInterface { subIndex: number | Uint8Array, { isWrite }: { isWrite?: boolean }, ): AccessEventFlags - shallowCopy(): AccessWitnessInterface - merge(accessWitness: AccessWitnessInterface): void + merge(accessWitness: VerkleAccessWitnessInterface): void } /* @@ -161,14 +191,11 @@ export interface StateManagerInterface { clear(): void } generateCanonicalGenesis?(initState: any): Promise // TODO make input more typesafe - // only Verkle/EIP-6800 (experimental) - accessWitness?: AccessWitnessInterface initVerkleExecutionWitness?( blockNum: bigint, executionWitness?: VerkleExecutionWitness | null, - accessWitness?: AccessWitnessInterface, ): void - verifyPostState?(): Promise + verifyPostState?(accessWitness: VerkleAccessWitnessInterface): Promise checkChunkWitnessPresent?(contract: Address, programCounter: number): Promise getAppliedKey?(address: Uint8Array): Uint8Array // only for preimages diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index e69314e5e1..f7bc9f5cdb 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -51,6 +51,7 @@ import type { MessageWithTo } from './message.js' import type { AsyncDynamicGasHandler, SyncDynamicGasHandler } from './opcodes/gas.js' import type { OpHandler, OpcodeList, OpcodeMap } from './opcodes/index.js' import type { CustomPrecompile, PrecompileFunc } from './precompiles/index.js' +import type { VerkleAccessWitness } from './verkleAccessWitness.js' import type { Common, StateManagerInterface } from '@ethereumjs/common' const debug = debugDefault('evm:evm') @@ -98,6 +99,7 @@ export class EVM implements EVMInterface { public stateManager: StateManagerInterface public blockchain: EVMMockBlockchainInterface public journal: Journal + public verkleAccessWitness?: VerkleAccessWitness public readonly transientStorage: TransientStorage @@ -218,6 +220,7 @@ export class EVM implements EVMInterface { this._bls = opts.bls ?? new NobleBLS() this._bls.init?.() } + this._bn254 = opts.bn254! this._emit = async (topic: string, data: any): Promise => { @@ -259,20 +262,23 @@ export class EVM implements EVMInterface { const fromAddress = message.caller if (this.common.isActivatedEIP(6800)) { + if (message.accessWitness === undefined) { + throw new Error('accessWitness is required for EIP-6800') + } const sendsValue = message.value !== BIGINT_0 if (message.depth === 0) { - const originAccessGas = message.accessWitness!.touchTxOriginAndComputeGas(fromAddress) + const originAccessGas = message.accessWitness.touchTxOriginAndComputeGas(fromAddress) debugGas(`originAccessGas=${originAccessGas} waived off for origin at depth=0`) - const destAccessGas = message.accessWitness!.touchTxTargetAndComputeGas(message.to, { + const destAccessGas = message.accessWitness.touchTxTargetAndComputeGas(message.to, { sendsValue, }) debugGas(`destAccessGas=${destAccessGas} waived off for target at depth=0`) } - let callAccessGas = message.accessWitness!.touchAndChargeMessageCall(message.to) + let callAccessGas = message.accessWitness.touchAndChargeMessageCall(message.to) if (sendsValue) { - callAccessGas += message.accessWitness!.touchAndChargeValueTransfer(message.to) + callAccessGas += message.accessWitness.touchAndChargeValueTransfer(message.to) } gasLimit -= callAccessGas if (gasLimit < BIGINT_0) { @@ -860,7 +866,7 @@ export class EVM implements EVMInterface { createdAddresses: opts.createdAddresses ?? new Set(), delegatecall: opts.delegatecall, blobVersionedHashes: opts.blobVersionedHashes, - accessWitness: opts.accessWitness, + accessWitness: this.verkleAccessWitness, }) } diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 2386c948de..17b280c15f 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -61,3 +61,4 @@ export { export * from './constructors.js' export * from './params.js' +export * from './verkleAccessWitness.js' diff --git a/packages/evm/src/interpreter.ts b/packages/evm/src/interpreter.ts index f3619e9648..9d5be3f8e1 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -34,7 +34,11 @@ import type { EVMResult, Log, } from './types.js' -import type { AccessWitnessInterface, Common, StateManagerInterface } from '@ethereumjs/common' +import type { + Common, + StateManagerInterface, + VerkleAccessWitnessInterface, +} from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' const debugGas = debugDefault('evm:gas') @@ -78,7 +82,7 @@ export interface Env { eof?: EOFEnv /* Optional EOF environment in case of EOF execution */ blobVersionedHashes: PrefixedHexString[] /** Versioned hashes for blob transactions */ createdAddresses?: Set - accessWitness?: AccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface chargeCodeAccesses?: boolean } diff --git a/packages/evm/src/message.ts b/packages/evm/src/message.ts index 24555ddbc5..193b99940a 100644 --- a/packages/evm/src/message.ts +++ b/packages/evm/src/message.ts @@ -2,7 +2,7 @@ import { BIGINT_0, createZeroAddress } from '@ethereumjs/util' import type { PrecompileFunc } from './precompiles/index.js' import type { EOFEnv } from './types.js' -import type { AccessWitnessInterface } from '@ethereumjs/common' +import type { VerkleAccessWitnessInterface } from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' const defaults = { @@ -40,7 +40,7 @@ interface MessageOpts { delegatecall?: boolean gasRefund?: bigint blobVersionedHashes?: PrefixedHexString[] - accessWitness?: AccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface } export class Message { @@ -73,7 +73,7 @@ export class Message { * List of versioned hashes if message is a blob transaction in the outer VM */ blobVersionedHashes?: PrefixedHexString[] - accessWitness?: AccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface constructor(opts: MessageOpts) { this.to = opts.to diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 4663efbfd1..bed59e954e 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -7,10 +7,10 @@ import type { OpHandler } from './opcodes/index.js' import type { CustomPrecompile } from './precompiles/index.js' import type { PrecompileFunc } from './precompiles/types.js' import type { - AccessWitnessInterface, Common, ParamsDict, StateManagerInterface, + VerkleAccessWitnessInterface, } from '@ethereumjs/common' import type { Account, Address, PrefixedHexString } from '@ethereumjs/util' import type { EventEmitter } from 'eventemitter3' @@ -128,7 +128,7 @@ export interface EVMRunCallOpts extends EVMRunOpts { */ message?: Message - accessWitness?: AccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface } interface NewContractEvent { @@ -166,6 +166,7 @@ export interface EVMInterface { runCall(opts: EVMRunCallOpts): Promise runCode(opts: EVMRunCodeOpts): Promise events?: EventEmitter + verkleAccessWitness?: VerkleAccessWitnessInterface } export type EVMProfilerOpts = { @@ -212,6 +213,7 @@ export interface EVMOpts { * - [EIP-5656](https://eips.ethereum.org/EIPS/eip-5656) - MCOPY - Memory copying instruction (Cancun) * - [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110) - Supply validator deposits on chain (Prague) * - [EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) - SELFDESTRUCT only in same transaction (Cancun) + * - [EIP-6800](https://eips.ethereum.org/EIPS/eip-6800) - Verkle state tree (Experimental) * - [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) - Execution layer triggerable withdrawals (Prague) * - [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) - Execution layer triggerable validator consolidations (Prague) * - [EIP-7516](https://eips.ethereum.org/EIPS/eip-7516) - BLOBBASEFEE opcode (Cancun) diff --git a/packages/statemanager/src/accessWitness.ts b/packages/evm/src/verkleAccessWitness.ts similarity index 83% rename from packages/statemanager/src/accessWitness.ts rename to packages/evm/src/verkleAccessWitness.ts index f47317319a..114e50d77d 100644 --- a/packages/statemanager/src/accessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -1,3 +1,4 @@ +import { VerkleAccessedStateType } from '@ethereumjs/common' import { BIGINT_0, VERKLE_BASIC_DATA_LEAF_KEY, @@ -14,7 +15,13 @@ import { } from '@ethereumjs/util' import debugDefault from 'debug' -import type { AccessEventFlags, AccessWitnessInterface } from '@ethereumjs/common' +import type { + AccessEventFlags, + RawVerkleAccessedState, + VerkleAccessWitnessInterface, + VerkleAccessedState, + VerkleAccessedStateWithAddress, +} from '@ethereumjs/common' import type { Address, PrefixedHexString, VerkleCrypto } from '@ethereumjs/util' const debug = debugDefault('statemanager:verkle:aw') @@ -36,40 +43,16 @@ type ChunkAccessEvent = StemAccessEvent & { fill?: boolean } // Since stem is pedersen hashed, it is useful to maintain the reverse relationship type StemMeta = { address: Address; treeIndex: number | bigint } -type RawAccessedState = { - address: Address - treeIndex: number | bigint - chunkIndex: number - chunkKey: PrefixedHexString -} - -export enum AccessedStateType { - BasicData = 'basicData', - CodeHash = 'codeHash', - Code = 'code', - Storage = 'storage', -} -type AccessedState = - | { type: Exclude } - | { type: AccessedStateType.Code; codeOffset: number } - | { type: AccessedStateType.Storage; slot: bigint } -export type AccessedStateWithAddress = AccessedState & { - address: Address - chunkKey: PrefixedHexString -} - -export class AccessWitness implements AccessWitnessInterface { +export class VerkleAccessWitness implements VerkleAccessWitnessInterface { stems: Map chunks: Map verkleCrypto: VerkleCrypto - constructor( - opts: { - verkleCrypto?: VerkleCrypto - stems?: Map - chunks?: Map - } = {}, - ) { + constructor(opts: { + verkleCrypto: VerkleCrypto + stems?: Map + chunks?: Map + }) { if (opts.verkleCrypto === undefined) { throw new Error('verkle crypto required') } @@ -275,12 +258,7 @@ export class AccessWitness implements AccessWitnessInterface { return { stemRead, stemWrite, chunkRead, chunkWrite, chunkFill } } - /**Create a shallow copy, could clone some caches in future for optimizations */ - shallowCopy(): AccessWitness { - return new AccessWitness({ verkleCrypto: this.verkleCrypto }) - } - - merge(accessWitness: AccessWitness): void { + merge(accessWitness: VerkleAccessWitness): void { for (const [chunkKey, chunkValue] of accessWitness.chunks.entries()) { const stemKey = chunkKey.slice(0, chunkKey.length - 2) as PrefixedHexString const stem = accessWitness.stems.get(stemKey) @@ -305,7 +283,7 @@ export class AccessWitness implements AccessWitnessInterface { } } - *rawAccesses(): Generator { + *rawAccesses(): Generator { for (const chunkKey of this.chunks.keys()) { // drop the last byte const stemKey = chunkKey.slice(0, chunkKey.length - 2) as PrefixedHexString @@ -320,7 +298,7 @@ export class AccessWitness implements AccessWitnessInterface { } } - *accesses(): Generator { + *accesses(): Generator { for (const rawAccess of this.rawAccesses()) { const { address, treeIndex, chunkIndex, chunkKey } = rawAccess const accessedState = decodeAccessedState(treeIndex, chunkIndex) @@ -329,13 +307,16 @@ export class AccessWitness implements AccessWitnessInterface { } } -export function decodeAccessedState(treeIndex: number | bigint, chunkIndex: number): AccessedState { +export function decodeAccessedState( + treeIndex: number | bigint, + chunkIndex: number, +): VerkleAccessedState { const position = BigInt(treeIndex) * BigInt(VERKLE_NODE_WIDTH) + BigInt(chunkIndex) switch (position) { case BigInt(0): - return { type: AccessedStateType.BasicData } + return { type: VerkleAccessedStateType.BasicData } case BigInt(1): - return { type: AccessedStateType.CodeHash } + return { type: VerkleAccessedStateType.CodeHash } default: if (position < VERKLE_HEADER_STORAGE_OFFSET) { throw Error(`No attribute yet stored >=2 and <${VERKLE_HEADER_STORAGE_OFFSET}`) @@ -343,13 +324,13 @@ export function decodeAccessedState(treeIndex: number | bigint, chunkIndex: numb if (position >= VERKLE_HEADER_STORAGE_OFFSET && position < VERKLE_CODE_OFFSET) { const slot = position - BigInt(VERKLE_HEADER_STORAGE_OFFSET) - return { type: AccessedStateType.Storage, slot } + return { type: VerkleAccessedStateType.Storage, slot } } else if (position >= VERKLE_CODE_OFFSET && position < VERKLE_MAIN_STORAGE_OFFSET) { const codeChunkIdx = Number(position) - VERKLE_CODE_OFFSET - return { type: AccessedStateType.Code, codeOffset: codeChunkIdx * 31 } + return { type: VerkleAccessedStateType.Code, codeOffset: codeChunkIdx * 31 } } else if (position >= VERKLE_MAIN_STORAGE_OFFSET) { const slot = BigInt(position - VERKLE_MAIN_STORAGE_OFFSET) - return { type: AccessedStateType.Storage, slot } + return { type: VerkleAccessedStateType.Storage, slot } } else { throw Error( `Invalid treeIndex=${treeIndex} chunkIndex=${chunkIndex} for verkle tree access`, @@ -357,18 +338,3 @@ export function decodeAccessedState(treeIndex: number | bigint, chunkIndex: numb } } } - -export function decodeValue(type: AccessedStateType, value: PrefixedHexString | null): string { - if (value === null) { - return '' - } - - switch (type) { - case AccessedStateType.BasicData: - case AccessedStateType.CodeHash: - case AccessedStateType.Code: - case AccessedStateType.Storage: { - return value - } - } -} diff --git a/packages/evm/test/verkle.spec.ts b/packages/evm/test/verkle.spec.ts index 235023726b..68a48c42b5 100644 --- a/packages/evm/test/verkle.spec.ts +++ b/packages/evm/test/verkle.spec.ts @@ -1,5 +1,5 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { AccessWitness, StatefulVerkleStateManager } from '@ethereumjs/statemanager' +import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' import { bigIntToBytes, createAccount, @@ -11,7 +11,7 @@ import { createVerkleTree } from '@ethereumjs/verkle' import { loadVerkleCrypto } from 'verkle-cryptography-wasm' import { assert, beforeAll, describe, it } from 'vitest' -import { createEVM } from '../src/index.js' +import { VerkleAccessWitness, createEVM } from '../src/index.js' import type { VerkleCrypto } from '@ethereumjs/util' @@ -29,12 +29,12 @@ describe('verkle tests', () => { const account = createAccount({ nonce: 3n, balance: 0xffffffffn }) await sm.putAccount(address, account) const evm = await createEVM({ common, stateManager: sm }) + // Initialize verkleAccess Witness manually (in real context, it is done by the VM, but we are bypassing that here) + evm.verkleAccessWitness = new VerkleAccessWitness({ verkleCrypto }) const code = hexToBytes('0x6001600255') // PUSH1 01 PUSH1 02 SSTORE - const accessWitness = new AccessWitness({ verkleCrypto }) const res = await evm.runCall({ code, caller: address, - accessWitness, to: address, }) assert.deepEqual(res.execResult.returnValue, new Uint8Array()) diff --git a/packages/statemanager/docs/README.md b/packages/statemanager/docs/README.md index a17e9f552e..dec8db9c23 100644 --- a/packages/statemanager/docs/README.md +++ b/packages/statemanager/docs/README.md @@ -6,7 +6,7 @@ ### Enumerations -- [AccessedStateType](enums/AccessedStateType.md) +- [VerkleAccessedStateType](enums/VerkleAccessedStateType.md) - [CacheType](enums/CacheType.md) ### Classes @@ -246,7 +246,7 @@ ___ | Name | Type | | :------ | :------ | -| `type` | [`AccessedStateType`](enums/AccessedStateType.md) | +| `type` | [`VerkleAccessedStateType`](enums/VerkleAccessedStateType.md) | | `value` | ``null`` \| `string` | #### Returns diff --git a/packages/statemanager/docs/enums/AccessedStateType.md b/packages/statemanager/docs/enums/VerkleAccessedStateType.md similarity index 77% rename from packages/statemanager/docs/enums/AccessedStateType.md rename to packages/statemanager/docs/enums/VerkleAccessedStateType.md index fa6d6e674f..e6069e7fa4 100644 --- a/packages/statemanager/docs/enums/AccessedStateType.md +++ b/packages/statemanager/docs/enums/VerkleAccessedStateType.md @@ -1,18 +1,18 @@ -[@ethereumjs/statemanager](../README.md) / AccessedStateType +[@ethereumjs/statemanager](../README.md) / VerkleAccessedStateType -# Enumeration: AccessedStateType +# Enumeration: VerkleAccessedStateType ## Table of contents ### Enumeration Members -- [Balance](AccessedStateType.md#balance) -- [Code](AccessedStateType.md#code) -- [CodeHash](AccessedStateType.md#codehash) -- [CodeSize](AccessedStateType.md#codesize) -- [Nonce](AccessedStateType.md#nonce) -- [Storage](AccessedStateType.md#storage) -- [Version](AccessedStateType.md#version) +- [Balance](VerkleAccessedStateType.md#balance) +- [Code](VerkleAccessedStateType.md#code) +- [CodeHash](VerkleAccessedStateType.md#codehash) +- [CodeSize](VerkleAccessedStateType.md#codesize) +- [Nonce](VerkleAccessedStateType.md#nonce) +- [Storage](VerkleAccessedStateType.md#storage) +- [Version](VerkleAccessedStateType.md#version) ## Enumeration Members diff --git a/packages/statemanager/src/index.ts b/packages/statemanager/src/index.ts index ae9a906743..bb9d5e76be 100644 --- a/packages/statemanager/src/index.ts +++ b/packages/statemanager/src/index.ts @@ -1,4 +1,3 @@ -export * from './accessWitness.js' export * from './cache/index.js' export * from './merkleStateManager.js' export * from './proofs/index.js' diff --git a/packages/statemanager/src/statefulVerkleStateManager.ts b/packages/statemanager/src/statefulVerkleStateManager.ts index 3ce0e80a42..42b610326d 100644 --- a/packages/statemanager/src/statefulVerkleStateManager.ts +++ b/packages/statemanager/src/statefulVerkleStateManager.ts @@ -1,4 +1,4 @@ -import { Common, Mainnet } from '@ethereumjs/common' +import { Common, Mainnet, VerkleAccessedStateType } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { Account, @@ -35,11 +35,9 @@ import { LeafVerkleNodeValue, VerkleTree } from '@ethereumjs/verkle' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { AccessWitness, AccessedStateType, decodeValue } from './accessWitness.js' import { OriginalStorageCache } from './cache/originalStorageCache.js' import { modifyAccountFields } from './util.js' -import type { AccessedStateWithAddress } from './accessWitness.js' import type { Caches } from './cache/caches.js' import type { StatefulVerkleStateManagerOpts, VerkleState } from './types.js' import type { @@ -47,6 +45,8 @@ import type { StateManagerInterface, StorageDump, StorageRange, + VerkleAccessWitnessInterface, + VerkleAccessedStateWithAddress, } from '@ethereumjs/common' import type { PrefixedHexString, VerkleCrypto, VerkleExecutionWitness } from '@ethereumjs/util' import type { Debugger } from 'debug' @@ -57,6 +57,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { protected _caches?: Caches originalStorageCache: OriginalStorageCache + verkleCrypto: VerkleCrypto protected _trie: VerkleTree @@ -69,7 +70,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { private _postState: VerkleState = {} private _preState: VerkleState = {} - protected verkleCrypto: VerkleCrypto /** * StateManager is run in DEBUG mode (default: false) * Taken from DEBUG environment variable @@ -82,7 +82,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { private keccakFunction: Function - accessWitness?: AccessWitness constructor(opts: StatefulVerkleStateManagerOpts) { // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables // Additional window check is to prevent vite browser bundling (and potentially other) to break @@ -108,7 +107,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { this._caches = opts.caches this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256 this.verkleCrypto = opts.verkleCrypto - this.accessWitness = new AccessWitness({ verkleCrypto: this.verkleCrypto }) } /** @@ -163,7 +161,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { public initVerkleExecutionWitness( _blockNum: bigint, executionWitness?: VerkleExecutionWitness | null, - accessWitness?: AccessWitness, ) { if (executionWitness === null || executionWitness === undefined) { const errorMsg = `Invalid executionWitness=${executionWitness} for initVerkleExecutionWitness` @@ -189,8 +186,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { return acc }, {}) - this.accessWitness = accessWitness ?? new AccessWitness({ verkleCrypto: this.verkleCrypto }) - const postStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => { const suffixDiffPairs = suffixDiffs.map(({ newValue, currentValue, suffix }) => { const key = `${stem}${padToEven(Number(suffix).toString(16))}` as PrefixedHexString @@ -528,12 +523,12 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } async getComputedValue( - accessedState: AccessedStateWithAddress, + accessedState: VerkleAccessedStateWithAddress, ): Promise { const { address, type } = accessedState switch (type) { - case AccessedStateType.BasicData: { + case VerkleAccessedStateType.BasicData: { if (this._caches === undefined) { const accountData = await this.getAccount(address) if (accountData === undefined) { @@ -554,7 +549,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } } - case AccessedStateType.CodeHash: { + case VerkleAccessedStateType.CodeHash: { if (this._caches === undefined) { const accountData = await this.getAccount(address) if (accountData === undefined) { @@ -571,7 +566,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } } - case AccessedStateType.Code: { + case VerkleAccessedStateType.Code: { const { codeOffset } = accessedState let code: Uint8Array | undefined | null = null if (this._caches === undefined) { @@ -597,7 +592,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { ) } - case AccessedStateType.Storage: { + case VerkleAccessedStateType.Storage: { const { slot } = accessedState const key = setLengthLeft(bigIntToBytes(slot), 32) let storage: Uint8Array | undefined | null = null @@ -618,25 +613,26 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } // Verifies that the witness post-state matches the computed post-state - async verifyPostState(): Promise { + async verifyPostState(accessWitness: VerkleAccessWitnessInterface): Promise { // track what all chunks were accessed so as to compare in the end if any chunks were missed // in access while comparing against the provided poststate in the execution witness const accessedChunks = new Map() // switch to false if postVerify fails let postFailures = 0 - for (const accessedState of this.accessWitness?.accesses() ?? []) { + for (const accessedState of accessWitness?.accesses() ?? []) { const { address, type } = accessedState let extraMeta = '' - if (accessedState.type === AccessedStateType.Code) { + if (accessedState.type === VerkleAccessedStateType.Code) { extraMeta = `codeOffset=${accessedState.codeOffset}` - } else if (accessedState.type === AccessedStateType.Storage) { + } else if (accessedState.type === VerkleAccessedStateType.Storage) { extraMeta = `slot=${accessedState.slot}` } const { chunkKey } = accessedState accessedChunks.set(chunkKey, true) - const computedValue = await this.getComputedValue(accessedState) + const computedValue: PrefixedHexString | null | undefined = + await this.getComputedValue(accessedState) if (computedValue === undefined) { this.DEBUG && this._debug( @@ -659,11 +655,11 @@ export class StatefulVerkleStateManager implements StateManagerInterface { // if the access type is code, then we can't match the first byte because since the computed value // doesn't has the first byte for push data since previous chunk code itself might not be available - if (accessedState.type === AccessedStateType.Code) { + if (accessedState.type === VerkleAccessedStateType.Code) { // computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null canonicalValue = canonicalValue !== null ? `0x${canonicalValue.slice(4)}` : null } else if ( - accessedState.type === AccessedStateType.Storage && + accessedState.type === VerkleAccessedStateType.Storage && canonicalValue === null && computedValue === ZEROVALUE ) { @@ -671,37 +667,26 @@ export class StatefulVerkleStateManager implements StateManagerInterface { } if (computedValue !== canonicalValue) { - const decodedComputedValue = decodeValue(accessedState.type, computedValue) - const decodedCanonicalValue = decodeValue(accessedState.type, canonicalValue) - - const displayComputedValue = - computedValue === decodedComputedValue - ? computedValue - : `${computedValue} (${decodedComputedValue})` - const displayCanonicalValue = - canonicalValue === decodedCanonicalValue - ? canonicalValue - : `${canonicalValue} (${decodedCanonicalValue})` - if (type === AccessedStateType.BasicData) { + if (type === VerkleAccessedStateType.BasicData) { + this.DEBUG && + this._debug( + `canonical value: `, + canonicalValue === null + ? null + : decodeVerkleLeafBasicData(hexToBytes(canonicalValue)), + ) this.DEBUG && this._debug( `computed value: `, - decodeVerkleLeafBasicData(hexToBytes(displayComputedValue)), + computedValue === null ? null : decodeVerkleLeafBasicData(hexToBytes(computedValue)), ) - displayCanonicalValue.startsWith('0x') - ? this.DEBUG && - this._debug( - `canonical value: `, - decodeVerkleLeafBasicData(hexToBytes(displayCanonicalValue)), - ) - : this._debug(`canonical value: `, displayCanonicalValue) } this.DEBUG && this._debug( `Block accesses mismatch address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, ) - this.DEBUG && this._debug(`expected=${displayCanonicalValue}`) - this.DEBUG && this._debug(`computed=${displayComputedValue}`) + this.DEBUG && this._debug(`expected=${canonicalValue}`) + this.DEBUG && this._debug(`computed=${computedValue}`) postFailures++ } } diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index 90e0c2f7d8..0557b15932 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -1,3 +1,4 @@ +import { VerkleAccessedStateType } from '@ethereumjs/common' import { Account, KECCAK256_NULL, @@ -24,15 +25,18 @@ import { import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { AccessWitness, AccessedStateType, decodeValue } from './accessWitness.js' import { OriginalStorageCache } from './cache/index.js' import { modifyAccountFields } from './util.js' -import type { AccessedStateWithAddress } from './accessWitness.js' import type { Caches } from './cache/index.js' import type { StatelessVerkleStateManagerOpts, VerkleState } from './index.js' import type { MerkleStateManager } from './merkleStateManager.js' -import type { AccountFields, StateManagerInterface } from '@ethereumjs/common' +import type { + AccountFields, + StateManagerInterface, + VerkleAccessWitnessInterface, + VerkleAccessedStateWithAddress, +} from '@ethereumjs/common' import type { Address, PrefixedHexString, @@ -97,7 +101,6 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // Checkpointing private _checkpoints: VerkleState[] = [] - accessWitness?: AccessWitness private keccakFunction: Function @@ -131,7 +134,6 @@ export class StatelessVerkleStateManager implements StateManagerInterface { public initVerkleExecutionWitness( blockNum: bigint, executionWitness?: VerkleExecutionWitness | null, - accessWitness?: AccessWitness, ) { this._blockNum = blockNum if (executionWitness === null || executionWitness === undefined) { @@ -141,8 +143,6 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } this._executionWitness = executionWitness - this.accessWitness = accessWitness ?? new AccessWitness({ verkleCrypto: this.verkleCrypto }) - this._proof = executionWitness.verkleProof // Populate the pre-state and post-state from the executionWitness @@ -490,19 +490,19 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } // Verifies that the witness post-state matches the computed post-state - verifyPostState(): Promise { + verifyPostState(accessWitness: VerkleAccessWitnessInterface): Promise { // track what all chunks were accessed so as to compare in the end if any chunks were missed // in access while comparing against the provided poststate in the execution witness const accessedChunks = new Map() // switch to false if postVerify fails let postFailures = 0 - for (const accessedState of this.accessWitness?.accesses() ?? []) { + for (const accessedState of accessWitness?.accesses() ?? []) { const { address, type } = accessedState let extraMeta = '' - if (accessedState.type === AccessedStateType.Code) { + if (accessedState.type === VerkleAccessedStateType.Code) { extraMeta = `codeOffset=${accessedState.codeOffset}` - } else if (accessedState.type === AccessedStateType.Storage) { + } else if (accessedState.type === VerkleAccessedStateType.Storage) { extraMeta = `slot=${accessedState.slot}` } @@ -531,11 +531,11 @@ export class StatelessVerkleStateManager implements StateManagerInterface { // if the access type is code, then we can't match the first byte because since the computed value // doesn't has the first byte for push data since previous chunk code itself might not be available - if (accessedState.type === AccessedStateType.Code) { + if (accessedState.type === VerkleAccessedStateType.Code) { // computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null canonicalValue = canonicalValue !== null ? `0x${canonicalValue.slice(4)}` : null } else if ( - accessedState.type === AccessedStateType.Storage && + accessedState.type === VerkleAccessedStateType.Storage && canonicalValue === null && computedValue === ZEROVALUE ) { @@ -543,24 +543,12 @@ export class StatelessVerkleStateManager implements StateManagerInterface { } if (computedValue !== canonicalValue) { - const decodedComputedValue = decodeValue(accessedState.type, computedValue) - const decodedCanonicalValue = decodeValue(accessedState.type, canonicalValue) - - const displayComputedValue = - computedValue === decodedComputedValue - ? computedValue - : `${computedValue} (${decodedComputedValue})` - const displayCanonicalValue = - canonicalValue === decodedCanonicalValue - ? canonicalValue - : `${canonicalValue} (${decodedCanonicalValue})` - this.DEBUG && this._debug( `Block accesses mismatch address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, ) - this.DEBUG && this._debug(`expected=${displayCanonicalValue}`) - this.DEBUG && this._debug(`computed=${displayComputedValue}`) + this.DEBUG && this._debug(`expected=${canonicalValue}`) + this.DEBUG && this._debug(`computed=${computedValue}`) postFailures++ } } @@ -580,10 +568,10 @@ export class StatelessVerkleStateManager implements StateManagerInterface { return Promise.resolve(verifyPassed) } - getComputedValue(accessedState: AccessedStateWithAddress): PrefixedHexString | null { + getComputedValue(accessedState: VerkleAccessedStateWithAddress): PrefixedHexString | null { const { address, type } = accessedState switch (type) { - case AccessedStateType.BasicData: { + case VerkleAccessedStateType.BasicData: { const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null @@ -594,7 +582,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { return bytesToHex(basicDataBytes) } - case AccessedStateType.CodeHash: { + case VerkleAccessedStateType.CodeHash: { const encodedAccount = this._caches?.account?.get(address)?.accountRLP if (encodedAccount === undefined) { return null @@ -602,7 +590,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { return bytesToHex(createPartialAccountFromRLP(encodedAccount).codeHash) } - case AccessedStateType.Code: { + case VerkleAccessedStateType.Code: { const { codeOffset } = accessedState const code = this._caches?.code?.get(address)?.code if (code === undefined) { @@ -620,7 +608,7 @@ export class StatelessVerkleStateManager implements StateManagerInterface { ) } - case AccessedStateType.Storage: { + case VerkleAccessedStateType.Storage: { const { slot } = accessedState const key = setLengthLeft(bigIntToBytes(slot), 32) diff --git a/packages/statemanager/src/types.ts b/packages/statemanager/src/types.ts index 3952757bd4..7cb7e1a483 100644 --- a/packages/statemanager/src/types.ts +++ b/packages/statemanager/src/types.ts @@ -1,6 +1,6 @@ import { type PrefixedHexString } from '@ethereumjs/util' -import type { AccessWitness, Caches } from './index.js' +import type { Caches } from './index.js' import type { Common } from '@ethereumjs/common' import type { MerklePatriciaTrie } from '@ethereumjs/mpt' import type { VerkleCrypto } from '@ethereumjs/util' @@ -69,9 +69,7 @@ export interface MerkleStateManagerOpts extends BaseStateManagerOpts { * Options dictionary. */ export interface StatelessVerkleStateManagerOpts extends BaseStateManagerOpts { - accesses?: AccessWitness verkleCrypto: VerkleCrypto - initialStateRoot?: Uint8Array caches?: Caches } diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 580f97dcf0..0ec31d43d4 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -1,5 +1,6 @@ import { createBlock, genRequestsTrieRoot } from '@ethereumjs/block' import { ConsensusType, Hardfork } from '@ethereumjs/common' +import { type EVM, type EVMInterface, VerkleAccessWitness } from '@ethereumjs/evm' import { MerklePatriciaTrie } from '@ethereumjs/mpt' import { RLP } from '@ethereumjs/rlp' import { StatelessVerkleStateManager, verifyVerkleStateProof } from '@ethereumjs/statemanager' @@ -46,7 +47,6 @@ import type { import type { VM } from './vm.js' import type { Block } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' -import type { EVM, EVMInterface } from '@ethereumjs/evm' import type { CLRequest, CLRequestType, PrefixedHexString } from '@ethereumjs/util' const debug = debugDefault('vm:block') @@ -151,6 +151,10 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise { async function _runTx(vm: VM, opts: RunTxOpts): Promise { const state = vm.stateManager - let stateAccesses + let stateAccesses: VerkleAccessWitnessInterface | undefined + let txAccesses: VerkleAccessWitnessInterface | undefined if (vm.common.isActivatedEIP(6800)) { - if (vm.stateManager.accessWitness === undefined) { - throw Error(`Verkle State Manager needed for execution of verkle blocks`) + if (vm.evm.verkleAccessWitness === undefined) { + throw Error(`Verkle access witness needed for execution of verkle blocks`) } - stateAccesses = vm.stateManager.accessWitness! + + if ( + !(vm.stateManager instanceof StatefulVerkleStateManager) && + !(vm.stateManager instanceof StatelessVerkleStateManager) + ) { + throw new Error(`Verkle State Manager needed for execution of verkle blocks`) + } + stateAccesses = vm.evm.verkleAccessWitness + txAccesses = new VerkleAccessWitness({ verkleCrypto: vm.stateManager.verkleCrypto }) } - const txAccesses = stateAccesses?.shallowCopy() const { tx, block } = opts @@ -630,7 +639,10 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { let minerAccount = await state.getAccount(miner) if (minerAccount === undefined) { if (vm.common.isActivatedEIP(6800)) { - state.accessWitness!.touchAndChargeProofOfAbsence(miner) + if (vm.evm.verkleAccessWitness === undefined) { + throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`) + } + vm.evm.verkleAccessWitness.touchAndChargeProofOfAbsence(miner) } minerAccount = new Account() } @@ -641,8 +653,11 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { minerAccount.balance += results.minerValue if (vm.common.isActivatedEIP(6800)) { + if (vm.evm.verkleAccessWitness === undefined) { + throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`) + } // use vm utility to build access but the computed gas is not charged and hence free - state.accessWitness!.touchTxTargetAndComputeGas(miner, { + vm.evm.verkleAccessWitness.touchTxTargetAndComputeGas(miner, { sendsValue: true, }) } diff --git a/packages/vm/src/types.ts b/packages/vm/src/types.ts index 1aec711032..9b0379fdbb 100644 --- a/packages/vm/src/types.ts +++ b/packages/vm/src/types.ts @@ -14,6 +14,7 @@ import type { CLRequest, CLRequestType, PrefixedHexString, + VerkleCrypto, WithdrawalData, } from '@ethereumjs/util' export type TxReceipt = PreByzantiumTxReceipt | PostByzantiumTxReceipt | EIP4844BlobTxReceipt @@ -184,6 +185,11 @@ export interface VMOpts { evmOpts?: EVMOpts profilerOpts?: VMProfilerOpts + + /** + * (Experimental) Verkle Crypto instance for EIP-6800 + */ + verkleCrypto?: VerkleCrypto } /**