diff --git a/package-lock.json b/package-lock.json index 92c89aa71f..6ca11fa95e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17931,6 +17931,7 @@ "@ethereumjs/statemanager": "^3.0.0-alpha.1", "@ethereumjs/tx": "^6.0.0-alpha.1", "@ethereumjs/util": "^10.0.0-alpha.1", + "@ethereumjs/verkle": "^0.2.0-alpha.1", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", "eventemitter3": "^5.0.1" diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts index ce36db6d25..6b5d6113ec 100644 --- a/packages/common/src/common.ts +++ b/packages/common/src/common.ts @@ -317,8 +317,8 @@ export class Common { for (const hfChanges of this.HARDFORK_CHANGES) { // EIP-referencing HF config (e.g. for berlin) if ('eips' in hfChanges[1]) { - const hfEIPs = hfChanges[1]['eips'] - for (const eip of hfEIPs!) { + const hfEIPs = hfChanges[1]['eips'] ?? [] + for (const eip of hfEIPs) { this._mergeWithParamsCache(this._params[eip] ?? {}) } } @@ -337,7 +337,7 @@ export class Common { for (const [name, hf] of this.HARDFORK_CHANGES) { if (this.gteHardfork(name) && 'eips' in hf) { - this._activatedEIPsCache = this._activatedEIPsCache.concat(hf['eips'] as number[]) + this._activatedEIPsCache = this._activatedEIPsCache.concat(hf.eips ?? []) } } this._activatedEIPsCache = this._activatedEIPsCache.concat(this._eips) diff --git a/packages/common/src/hardforks.ts b/packages/common/src/hardforks.ts index 6f7c498396..4f84adde69 100644 --- a/packages/common/src/hardforks.ts +++ b/packages/common/src/hardforks.ts @@ -169,6 +169,6 @@ export const hardforksDict: HardforksDict = { * Status : Final */ verkle: { - eips: [4762, 6800], + eips: [2935, 4762, 6800], }, } diff --git a/packages/common/src/interfaces.ts b/packages/common/src/interfaces.ts index fe17f65564..f4181cc698 100644 --- a/packages/common/src/interfaces.ts +++ b/packages/common/src/interfaces.ts @@ -101,6 +101,7 @@ export type VerkleAccessedStateWithAddress = VerkleAccessedState & { export interface VerkleAccessWitnessInterface { accesses(): Generator rawAccesses(): Generator + debugWitnessCost(): void touchAndChargeProofOfAbsence(address: Address): bigint touchAndChargeMessageCall(address: Address): bigint touchAndChargeValueTransfer(target: Address): bigint @@ -108,8 +109,12 @@ export interface VerkleAccessWitnessInterface { touchAndChargeContractCreateCompleted(address: Address): bigint touchTxOriginAndComputeGas(origin: Address): bigint touchTxTargetAndComputeGas(target: Address, { sendsValue }: { sendsValue?: boolean }): bigint - touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number): bigint - touchCodeChunksRangeOnWriteAndChargeGas(contact: Address, startPc: number, endPc: number): bigint + touchCodeChunksRangeOnReadAndComputeGas(contract: Address, startPc: number, endPc: number): bigint + touchCodeChunksRangeOnWriteAndComputeGas( + contract: Address, + startPc: number, + endPc: number, + ): bigint touchAddressOnWriteAndComputeGas( address: Address, treeIndex: number | bigint, @@ -120,7 +125,7 @@ export interface VerkleAccessWitnessInterface { treeIndex: number | bigint, subIndex: number | Uint8Array, ): bigint - touchAddressAndChargeGas( + touchAddressAndComputeGas( address: Address, treeIndex: number | bigint, subIndex: number | Uint8Array, @@ -133,6 +138,8 @@ export interface VerkleAccessWitnessInterface { { isWrite }: { isWrite?: boolean }, ): AccessEventFlags merge(accessWitness: VerkleAccessWitnessInterface): void + commit(): void + revert(): void } /* diff --git a/packages/evm/src/chunkCache.ts b/packages/evm/src/chunkCache.ts new file mode 100644 index 0000000000..5831e75a09 --- /dev/null +++ b/packages/evm/src/chunkCache.ts @@ -0,0 +1,35 @@ +import type { ChunkAccessEvent } from './verkleAccessWitness.js' +import type { PrefixedHexString } from '@ethereumjs/util' +export class ChunkCache { + cache: Map + + constructor() { + this.cache = new Map() + } + + set(stemKey: PrefixedHexString, accessedStem: ChunkAccessEvent) { + this.cache.set(stemKey, accessedStem) + } + + get(stemHex: PrefixedHexString): ChunkAccessEvent | undefined { + return this.cache.get(stemHex) + } + + del(stemHex: PrefixedHexString): void { + this.cache.delete(stemHex) + } + + commit(): [PrefixedHexString, ChunkAccessEvent][] { + const items: [PrefixedHexString, ChunkAccessEvent][] = Array.from(this.cache.entries()) + this.clear() + return items + } + + clear(): void { + this.cache.clear() + } + + size() { + return this.cache.size + } +} diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index c37000d4a5..ea05c7930f 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -287,11 +287,13 @@ export class EVM implements EVMInterface { if (this.DEBUG) { debugGas(`callAccessGas charged(${callAccessGas}) caused OOG (-> ${gasLimit})`) } + message.accessWitness.revert() return { execResult: OOGResult(message.gasLimit) } } else { if (this.DEBUG) { debugGas(`callAccessGas used (${callAccessGas} gas (-> ${gasLimit}))`) } + message.accessWitness.commit() } } @@ -323,11 +325,13 @@ export class EVM implements EVMInterface { `Proof of absence access charged(${absenceProofAccessGas}) caused OOG (-> ${gasLimit})`, ) } + message.accessWitness?.revert() return { execResult: OOGResult(message.gasLimit) } } else { if (this.DEBUG) { debugGas(`Proof of absence access used (${absenceProofAccessGas} gas (-> ${gasLimit}))`) } + message.accessWitness?.commit() } } toAccount = new Account() @@ -469,12 +473,14 @@ export class EVM implements EVMInterface { debugGas( `ContractCreateInit charge(${contractCreateAccessGas}) caused OOG (-> ${gasLimit})`, ) + message.accessWitness?.revert() } return { execResult: OOGResult(message.gasLimit) } } else { if (this.DEBUG) { debugGas(`ContractCreateInit charged (${contractCreateAccessGas} gas (-> ${gasLimit}))`) } + message.accessWitness?.commit() } } @@ -553,11 +559,15 @@ export class EVM implements EVMInterface { `ContractCreateComplete access gas (${createCompleteAccessGas}) caused OOG (-> ${gasLimit})`, ) } + message.accessWitness?.revert() return { execResult: OOGResult(message.gasLimit) } } else { - debug( - `ContractCreateComplete access used (${createCompleteAccessGas}) gas (-> ${gasLimit})`, - ) + if (this.DEBUG) { + debug( + `ContractCreateComplete access used (${createCompleteAccessGas}) gas (-> ${gasLimit})`, + ) + } + message.accessWitness?.commit() } } @@ -635,6 +645,7 @@ export class EVM implements EVMInterface { if (this.DEBUG) { debug(`Contract creation: out of gas`) } + message.accessWitness?.revert() result = { ...result, ...OOGResult(message.gasLimit) } } } else { @@ -644,12 +655,14 @@ export class EVM implements EVMInterface { if (this.DEBUG) { debug(`Not enough gas to pay the code deposit fee (Frontier)`) } + message.accessWitness?.revert() result = { ...result, ...COOGResult(totalGas - returnFee) } CodestoreOOG = true } else { if (this.DEBUG) { debug(`Contract creation: out of gas`) } + message.accessWitness?.revert() result = { ...result, ...OOGResult(message.gasLimit) } } } @@ -668,6 +681,7 @@ export class EVM implements EVMInterface { `ContractCreateComplete access gas (${createCompleteAccessGas}) caused OOG (-> ${gasLimit})`, ) } + message.accessWitness?.revert() result = { ...result, ...OOGResult(message.gasLimit) } } else { debug( @@ -686,7 +700,7 @@ export class EVM implements EVMInterface { // Add access charges for writing this code to the state if (this.common.isActivatedEIP(6800)) { const byteCodeWriteAccessfee = - message.accessWitness!.touchCodeChunksRangeOnWriteAndChargeGas( + message.accessWitness!.touchCodeChunksRangeOnWriteAndComputeGas( message.to, 0, result.returnValue.length - 1, @@ -698,9 +712,11 @@ export class EVM implements EVMInterface { `byteCodeWrite access gas (${byteCodeWriteAccessfee}) caused OOG (-> ${gasLimit})`, ) } + message.accessWitness?.revert() result = { ...result, ...OOGResult(message.gasLimit) } } else { debug(`byteCodeWrite access used (${byteCodeWriteAccessfee}) gas (-> ${gasLimit})`) + message.accessWitness?.commit() result.executionGasUsed += byteCodeWriteAccessfee } } @@ -802,6 +818,7 @@ export class EVM implements EVMInterface { } } + message.accessWitness?.commit() return { ...result, runState: { @@ -834,11 +851,11 @@ export class EVM implements EVMInterface { let callerAccount if (!message) { this._block = opts.block ?? defaultBlock() + const caller = opts.caller ?? createZeroAddress() this._tx = { gasPrice: opts.gasPrice ?? BIGINT_0, - origin: opts.origin ?? opts.caller ?? createZeroAddress(), + origin: opts.origin ?? caller, } - const caller = opts.caller ?? createZeroAddress() const value = opts.value ?? BIGINT_0 if (opts.skipBalance === true) { @@ -916,7 +933,7 @@ export class EVM implements EVMInterface { result = await this._executeCall(message as MessageWithTo) } else { if (this.DEBUG) { - debug(`Message CREATE execution (to undefined)`) + debug(`Message CREATE execution (to: undefined)`) } result = await this._executeCreate(message) } @@ -962,6 +979,7 @@ export class EVM implements EVMInterface { this.performanceLogger.stopTimer(timer!, 0) } + message.accessWitness?.commit() return result } diff --git a/packages/evm/src/interpreter.ts b/packages/evm/src/interpreter.ts index 9d5be3f8e1..9ba11f900b 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -327,6 +327,8 @@ export class Interpreter { this.performanceLogger.unpauseTimer(overheadTimer) } } catch (e: any) { + // Revert access witness changes if we revert - per EIP-4762 + this._runState.env.accessWitness?.revert() if (overheadTimer !== undefined) { this.performanceLogger.unpauseTimer(overheadTimer) } @@ -375,10 +377,17 @@ export class Interpreter { // It needs the base fee, for correct gas limit calculation for the CALL opcodes gas = await opEntry.gasHandler(this._runState, gas, this.common) } + + if (this._evm.events.listenerCount('step') > 0 || this._evm.DEBUG) { + // Only run this stepHook function if there is an event listener (e.g. test runner) + // or if the vm is running in debug mode (to display opcode debug logs) + await this._runStepHook(gas, this.getGasLeft()) + } + if (this.common.isActivatedEIP(6800) && this._env.chargeCodeAccesses === true) { const contract = this._runState.interpreter.getAddress() const statelessGas = - this._runState.env.accessWitness!.touchCodeChunksRangeOnReadAndChargeGas( + this._runState.env.accessWitness!.touchCodeChunksRangeOnReadAndComputeGas( contract, this._runState.programCounter, this._runState.programCounter, @@ -387,12 +396,6 @@ export class Interpreter { debugGas(`codechunk accessed statelessGas=${statelessGas} (-> ${gas})`) } - if (this._evm.events.listenerCount('step') > 0 || this._evm.DEBUG) { - // Only run this stepHook function if there is an event listener (e.g. test runner) - // or if the vm is running in debug mode (to display opcode debug logs) - await this._runStepHook(gas, this.getGasLeft()) - } - // Check for invalid opcode if (opInfo.isInvalid) { throw new EvmError(ERROR.INVALID_OPCODE) @@ -412,6 +415,7 @@ export class Interpreter { } else { opFn.apply(null, [this._runState, this.common]) } + this._runState.env.accessWitness?.commit() } finally { if (this.profilerOpts?.enabled === true) { this.performanceLogger.stopTimer( diff --git a/packages/evm/src/opcodes/functions.ts b/packages/evm/src/opcodes/functions.ts index 1a55a62f92..f7552608b5 100644 --- a/packages/evm/src/opcodes/functions.ts +++ b/packages/evm/src/opcodes/functions.ts @@ -959,7 +959,7 @@ export const handlers: Map = new Map([ const contract = runState.interpreter.getAddress() const startOffset = Math.min(runState.code.length, runState.programCounter + 1) const endOffset = Math.min(runState.code.length, startOffset + numToPush - 1) - const statelessGas = runState.env.accessWitness!.touchCodeChunksRangeOnReadAndChargeGas( + const statelessGas = runState.env.accessWitness!.touchCodeChunksRangeOnReadAndComputeGas( contract, startOffset, endOffset, diff --git a/packages/evm/src/opcodes/gas.ts b/packages/evm/src/opcodes/gas.ts index 8c4dec0275..d6af6993b0 100644 --- a/packages/evm/src/opcodes/gas.ts +++ b/packages/evm/src/opcodes/gas.ts @@ -154,7 +154,7 @@ export const dynamicGasHandlers: Map + + constructor() { + this.cache = new Map() + } + + set(stemKey: PrefixedHexString, accessedStem: StemAccessEvent & StemMeta) { + this.cache.set(stemKey, accessedStem) + } + + get(stemHex: PrefixedHexString): (StemAccessEvent & StemMeta) | undefined { + return this.cache.get(stemHex) + } + + del(stemHex: PrefixedHexString): void { + this.cache.delete(stemHex) + } + + commit(): [PrefixedHexString, StemAccessEvent & StemMeta][] { + const items: [PrefixedHexString, StemAccessEvent & StemMeta][] = Array.from( + this.cache.entries(), + ) + this.clear() + return items + } + + /** + * Clear cache + */ + clear(): void { + this.cache.clear() + } + + /** + * Returns the size of the cache + * @returns + */ + size() { + return this.cache.size + } +} diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index bed59e954e..8d5e7abf49 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -167,6 +167,7 @@ export interface EVMInterface { runCode(opts: EVMRunCodeOpts): Promise events?: EventEmitter verkleAccessWitness?: VerkleAccessWitnessInterface + systemVerkleAccessWitness?: VerkleAccessWitnessInterface } export type EVMProfilerOpts = { diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 65a9f77ad7..b65577bd9f 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -15,6 +15,9 @@ import { } from '@ethereumjs/util' import debugDefault from 'debug' +import { ChunkCache } from './chunkCache.js' +import { StemCache } from './stemCache.js' + import type { AccessEventFlags, RawVerkleAccessedState, @@ -36,13 +39,13 @@ const WitnessChunkWriteCost = BigInt(500) const WitnessChunkFillCost = BigInt(6200) // read is a default access event if stem or chunk is present -type StemAccessEvent = { write?: boolean } +export type StemAccessEvent = { write?: boolean } // chunk fill access event is not being charged right now in kaustinen but will be rectified // in upcoming iterations -type ChunkAccessEvent = StemAccessEvent & { fill?: boolean } +export 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 } +export type StemMeta = { address: Address; treeIndex: number | bigint } export function decodeAccessedState( treeIndex: number | bigint, @@ -82,6 +85,8 @@ export function decodeAccessedState( export class VerkleAccessWitness implements VerkleAccessWitnessInterface { stems: Map chunks: Map + stemCache: StemCache = new StemCache() + chunkCache: ChunkCache = new ChunkCache() verkleCrypto: VerkleCrypto constructor(opts: { verkleCrypto: VerkleCrypto @@ -161,16 +166,20 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { return gas } - touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number): bigint { + touchCodeChunksRangeOnReadAndComputeGas( + contract: Address, + startPc: number, + endPc: number, + ): bigint { let gas = BIGINT_0 for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) { const { treeIndex, subIndex } = getVerkleTreeIndicesForCodeChunk(chunkNum) - gas += this.touchAddressOnReadAndComputeGas(contact, treeIndex, subIndex) + gas += this.touchAddressOnReadAndComputeGas(contract, treeIndex, subIndex) } return gas } - touchCodeChunksRangeOnWriteAndChargeGas( + touchCodeChunksRangeOnWriteAndComputeGas( contact: Address, startPc: number, endPc: number, @@ -188,7 +197,7 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { treeIndex: number | bigint, subIndex: number | Uint8Array, ): bigint { - return this.touchAddressAndChargeGas(address, treeIndex, subIndex, { + return this.touchAddressAndComputeGas(address, treeIndex, subIndex, { isWrite: true, }) } @@ -198,12 +207,12 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { treeIndex: number | bigint, subIndex: number | Uint8Array, ): bigint { - return this.touchAddressAndChargeGas(address, treeIndex, subIndex, { + return this.touchAddressAndComputeGas(address, treeIndex, subIndex, { isWrite: false, }) } - touchAddressAndChargeGas( + touchAddressAndComputeGas( address: Address, treeIndex: number | bigint, subIndex: number | Uint8Array, @@ -236,7 +245,7 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { } debug( - `touchAddressAndChargeGas=${gas} address=${address} treeIndex=${treeIndex} subIndex=${subIndex}`, + `touchAddressAndComputeGas=${gas} address=${address} treeIndex=${treeIndex} subIndex=${subIndex}`, ) return gas @@ -258,11 +267,11 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { const accessedStemKey = getVerkleStem(this.verkleCrypto, address, treeIndex) const accessedStemHex = bytesToHex(accessedStemKey) - let accessedStem = this.stems.get(accessedStemHex) + let accessedStem = this.stemCache.get(accessedStemHex) ?? this.stems.get(accessedStemHex) if (accessedStem === undefined) { stemRead = true accessedStem = { address, treeIndex } - this.stems.set(accessedStemHex, accessedStem) + this.stemCache.set(accessedStemHex, accessedStem) } const accessedChunkKey = getVerkleKey( @@ -270,11 +279,12 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { typeof subIndex === 'number' ? intToBytes(subIndex) : subIndex, ) const accessedChunkKeyHex = bytesToHex(accessedChunkKey) - let accessedChunk = this.chunks.get(accessedChunkKeyHex) + let accessedChunk = + this.chunkCache.get(accessedChunkKeyHex) ?? this.chunks.get(accessedChunkKeyHex) if (accessedChunk === undefined) { chunkRead = true accessedChunk = {} - this.chunks.set(accessedChunkKeyHex, accessedChunk) + this.chunkCache.set(accessedChunkKeyHex, accessedChunk) } if (isWrite === true) { @@ -322,6 +332,56 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { } } + commit(): void { + const cachedStems = this.stemCache.commit() + for (const [stemKey, stemValue] of cachedStems) { + this.stems.set(stemKey, stemValue) + } + + const cachedChunks = this.chunkCache.commit() + for (const [chunkKey, chunkValue] of cachedChunks) { + this.chunks.set(chunkKey, chunkValue) + } + } + + revert(): void { + this.stemCache.clear() + this.chunkCache.clear() + } + + debugWitnessCost(): void { + // Calculate the aggregate gas cost for verkle access witness per type + let stemReads = 0, + stemWrites = 0, + chunkReads = 0, + chunkWrites = 0 + + for (const [_, { write }] of this.stems.entries()) { + stemReads++ + if (write === true) { + stemWrites++ + } + } + for (const [_, { write }] of this.chunks.entries()) { + chunkReads++ + if (write === true) { + chunkWrites++ + } + } + debug( + `${stemReads} stem reads, totalling ${BigInt(stemReads) * WitnessBranchReadCost} gas units`, + ) + debug( + `${stemWrites} stem writes, totalling ${BigInt(stemWrites) * WitnessBranchWriteCost} gas units`, + ) + debug( + `${chunkReads} chunk reads, totalling ${BigInt(chunkReads) * WitnessChunkReadCost} gas units`, + ) + debug( + `${chunkWrites} chunk writes, totalling ${BigInt(chunkWrites) * WitnessChunkWriteCost} gas units`, + ) + } + *rawAccesses(): Generator { for (const chunkKey of this.chunks.keys()) { // drop the last byte diff --git a/packages/evm/test/verkle.spec.ts b/packages/evm/test/verkle.spec.ts index 67452ba197..36ed5ee102 100644 --- a/packages/evm/test/verkle.spec.ts +++ b/packages/evm/test/verkle.spec.ts @@ -42,4 +42,70 @@ describe('verkle tests', () => { const retrievedValue = await sm.getStorage(address, setLengthLeft(bigIntToBytes(2n), 32)) assert.deepEqual(retrievedValue, setLengthLeft(bigIntToBytes(1n), 32)) }) + + it('should revert and access witness should not contain a write access due to OOG', async () => { + // This tests executes some very simple bytecode that stores the value 1 in slot 2 + const common = new Common({ + chain: Mainnet, + customCrypto: { verkle }, + eips: [6800], + hardfork: Hardfork.Cancun, + }) + const trie = await createVerkleTree() + const sm = new StatefulVerkleStateManager({ common, trie }) + const address = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3') + 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: verkle, + }) + const code = hexToBytes('0x6001600255') // PUSH1 01 PUSH1 02 SSTORE + const res = await evm.runCall({ + code, + caller: address, + to: address, + gasLimit: BigInt(5), // too little gas for bytecode + gasPrice: BigInt(1), + }) + const writtenChunks = Array.from(evm.verkleAccessWitness.chunks.entries()).filter( + ([_, chunk]) => chunk.write !== undefined, + ) + assert.ok(writtenChunks.length === 0) + assert.equal(res.execResult.exceptionError?.error, 'out of gas') + }) + + it('access witness should contain a write access', async () => { + // This tests executes some very simple bytecode that stores the value 1 in slot 2 + const common = new Common({ + chain: Mainnet, + customCrypto: { verkle }, + eips: [6800], + hardfork: Hardfork.Cancun, + }) + const trie = await createVerkleTree() + const sm = new StatefulVerkleStateManager({ common, trie }) + const address = createAddressFromString('0x9e5ef720fa2cdfa5291eb7e711cfd2e62196f4b3') + 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: verkle, + }) + const code = hexToBytes('0x6001600255') // PUSH1 01 PUSH1 02 SSTORE + const res = await evm.runCall({ + code, + caller: address, + to: address, + gasLimit: BigInt(21000), // sufficient gas for bytecode + gasPrice: BigInt(1), + }) + const writtenChunks = Array.from(evm.verkleAccessWitness.chunks.entries()).filter( + ([_, chunk]) => chunk.write !== undefined, + ) + assert.ok(writtenChunks.length === 1) + assert.equal(res.execResult.exceptionError?.error, undefined) + }) }) diff --git a/packages/statemanager/src/statefulVerkleStateManager.ts b/packages/statemanager/src/statefulVerkleStateManager.ts index 6f435233d9..6854352ae2 100644 --- a/packages/statemanager/src/statefulVerkleStateManager.ts +++ b/packages/statemanager/src/statefulVerkleStateManager.ts @@ -218,6 +218,9 @@ export class StatefulVerkleStateManager implements StateManagerInterface { }, {}) this._postState = postState + + this._debug(`initVerkleExecutionWitness preState=${JSON.stringify(this._preState)}`) + this._debug(`initVerkleExecutionWitness postState=${JSON.stringify(this._postState)}`) } /** @@ -647,7 +650,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { if (computedValue === undefined) { this.DEBUG && this._debug( - `Block accesses missing in canonical address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, + `Missing computed value for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, ) postFailures++ continue @@ -658,7 +661,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { if (canonicalValue === undefined) { this.DEBUG && this._debug( - `Block accesses missing in canonical address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, + `Block accesses missing from postState for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`, ) postFailures++ continue diff --git a/packages/verkle/src/node/util.ts b/packages/verkle/src/node/util.ts index 80d8459ac8..4213197aa7 100644 --- a/packages/verkle/src/node/util.ts +++ b/packages/verkle/src/node/util.ts @@ -41,7 +41,7 @@ export function isInternalVerkleNode(node: VerkleNode): node is InternalVerkleNo export const createZeroesLeafValue = () => new Uint8Array(32) -export const createDefaultLeafVerkleValues = () => new Array(256).fill(0) +export const createDefaultLeafVerkleValues: () => number[] = () => new Array(256).fill(0) /*** * Converts 128 32byte values of a leaf node into an array of 256 32 byte values representing diff --git a/packages/vm/package.json b/packages/vm/package.json index 44482b319e..11f73f6104 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -63,6 +63,7 @@ "@ethereumjs/statemanager": "^3.0.0-alpha.1", "@ethereumjs/tx": "^6.0.0-alpha.1", "@ethereumjs/util": "^10.0.0-alpha.1", + "@ethereumjs/verkle": "^0.2.0-alpha.1", "debug": "^4.3.3", "ethereum-cryptography": "^3.0.0", "eventemitter3": "^5.0.1" diff --git a/packages/vm/src/params.ts b/packages/vm/src/params.ts index dfaa021cda..9e05af794a 100644 --- a/packages/vm/src/params.ts +++ b/packages/vm/src/params.ts @@ -41,6 +41,7 @@ export const paramsVM: ParamsDict = { // config historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored historyServeWindow: 8192, // The amount of blocks to be served by the historical blockhash contract + systemAddress: '0xfffffffffffffffffffffffffffffffffffffffe', // The system address }, /** . * Reduction in refunds @@ -67,10 +68,8 @@ export const paramsVM: ParamsDict = { * Ethereum state using a unified verkle tree (experimental) */ 6800: { - // kaustinen 6 current uses this address, however this will be updated to correct address - // in next iteration // config - historyStorageAddress: '0xfffffffffffffffffffffffffffffffffffffffe', // The address where the historical blockhashes are stored + historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored }, /** * Execution layer triggerable withdrawals (experimental) diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 424d373491..a8a5323819 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -18,6 +18,7 @@ import { bytesToHex, concatBytes, createAddressFromString, + createPartialAccount, equalsBytes, getVerkleTreeIndicesForStorageSlot, hexToBytes, @@ -143,6 +144,9 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise { let account = await evm.stateManager.getAccount(address) if (account === undefined) { - if (common?.isActivatedEIP(6800) === true) { + if (common.isActivatedEIP(6800) === true) { if (evm.verkleAccessWitness === undefined) { throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`) } @@ -752,7 +767,7 @@ export async function rewardAccount( account.balance += reward await evm.journal.putAccount(address, account) - if (common?.isActivatedEIP(6800) === true) { + if (common.isActivatedEIP(6800) === true) { if (evm.verkleAccessWitness === undefined) { throw Error(`verkleAccessWitness required if verkle (EIP-6800) is activated`) } diff --git a/packages/vm/src/runTx.ts b/packages/vm/src/runTx.ts index 3697f810ad..691233141a 100644 --- a/packages/vm/src/runTx.ts +++ b/packages/vm/src/runTx.ts @@ -802,6 +802,11 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { ) } + if (vm.common.isActivatedEIP(6800)) { + // commit all access witness changes + vm.evm.verkleAccessWitness?.commit() + } + return results }