From 49d3e9de80a5a451e5de3aaa9fa6addf883f595e Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:06:23 -0500 Subject: [PATCH 01/25] Update historyStorage address --- packages/evm/src/params.ts | 2 +- packages/vm/src/params.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/evm/src/params.ts b/packages/evm/src/params.ts index fa0339721ea..4b28e728ffb 100644 --- a/packages/evm/src/params.ts +++ b/packages/evm/src/params.ts @@ -271,7 +271,7 @@ export const paramsEVM: ParamsDict = { */ 2935: { // evm - historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored + historyStorageAddress: '0x0000F90827F1C53A10CB7A02335B175320002935', // 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 }, diff --git a/packages/vm/src/params.ts b/packages/vm/src/params.ts index ff8b3e4fc0e..5a6a4f05991 100644 --- a/packages/vm/src/params.ts +++ b/packages/vm/src/params.ts @@ -70,13 +70,6 @@ export const paramsVM: ParamsDict = { // config historicalRootsLength: 8191, // The modulo parameter of the beaconroot ring buffer in the beaconroot stateful precompile }, - /** - * Ethereum state using a unified verkle tree (experimental) - */ - 6800: { - // config - historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored - }, /** * Execution layer triggerable withdrawals (experimental) */ From a49acd6ec07129e3864a4a13d2e3d46780d798e5 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:13:50 -0500 Subject: [PATCH 02/25] Remove execution prestate since unused --- .../src/statefulVerkleStateManager.ts | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/packages/statemanager/src/statefulVerkleStateManager.ts b/packages/statemanager/src/statefulVerkleStateManager.ts index 80e645e84e6..61f456a5678 100644 --- a/packages/statemanager/src/statefulVerkleStateManager.ts +++ b/packages/statemanager/src/statefulVerkleStateManager.ts @@ -63,6 +63,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { protected _debug: Debugger protected _caches?: Caches + preStateRoot: Uint8Array originalStorageCache: OriginalStorageCache verkleCrypto: VerkleCrypto @@ -75,7 +76,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { // Post-state provided from the executionWitness. // Should not update. Used for comparing our computed post-state with the canonical one. private _postState: VerkleState = {} - private _preState: VerkleState = {} /** * StateManager is run in DEBUG mode (default: false) @@ -88,6 +88,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { protected readonly DEBUG: boolean = false private keccakFunction: Function + 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 @@ -118,6 +119,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface { this._caches = opts.caches this.keccakFunction = opts.common.customCrypto.keccak256 ?? keccak256 this.verkleCrypto = opts.common.customCrypto.verkle + this.preStateRoot = new Uint8Array(32) // Initial state root is zeroes } /** @@ -179,23 +181,9 @@ export class StatefulVerkleStateManager implements StateManagerInterface { throw Error(errorMsg) } - // Populate the pre-state and post-state from the executionWitness - const preStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => { - const suffixDiffPairs = suffixDiffs.map(({ currentValue, suffix }) => { - const key = `${stem}${padToEven(Number(suffix).toString(16))}` - return { - [key]: currentValue, - } - }) - - return suffixDiffPairs - }) + this.preStateRoot = hexToBytes(executionWitness.parentStateRoot) // set prestate root if given - // also maintain a separate preState unaffected by any changes in _state - this._preState = preStateRaw.reduce((prevValue, currentValue) => { - const acc = { ...prevValue, ...currentValue } - return acc - }, {}) + // Populate the post-state from the executionWitness const postStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => { const suffixDiffPairs = suffixDiffs.map(({ newValue, currentValue, suffix }) => { @@ -219,7 +207,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface { this._postState = postState - this._debug(`initVerkleExecutionWitness preState=${JSON.stringify(this._preState)}`) this._debug(`initVerkleExecutionWitness postState=${JSON.stringify(this._postState)}`) } From 9f86549ef0b2c06b833033eaa10b166a4f1489ab Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:56:24 -0500 Subject: [PATCH 03/25] Finish generateExecutionWitness function --- packages/evm/src/verkleAccessWitness.ts | 56 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 33cb7184ac0..839efc64032 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -12,6 +12,7 @@ import { getVerkleStem, getVerkleTreeIndicesForCodeChunk, getVerkleTreeIndicesForStorageSlot, + hexToBytes, intToBytes, } from '@ethereumjs/util' import debugDefault from 'debug' @@ -26,7 +27,17 @@ import type { VerkleAccessedState, VerkleAccessedStateWithAddress, } from '@ethereumjs/common' -import type { Address, PrefixedHexString, VerkleCrypto } from '@ethereumjs/util' +import type { + StatefulVerkleStateManager, + StatelessVerkleStateManager, +} from '@ethereumjs/statemanager' +import type { + Address, + PrefixedHexString, + VerkleCrypto, + VerkleExecutionWitness, +} from '@ethereumjs/util' +import type { VerkleTree } from '@ethereumjs/verkle' const debug = debugDefault('evm:verkle:aw') @@ -377,3 +388,46 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { } } } + +export const generateExecutionWitness = async ( + stateManager: StatefulVerkleStateManager | StatelessVerkleStateManager, + accessWitness: VerkleAccessWitness, + parentStateRoot: Uint8Array, +) => { + const trie = (stateManager as StatefulVerkleStateManager)['_trie'] as VerkleTree + const postStateRoot = await stateManager.getStateRoot() + const ew: VerkleExecutionWitness = { + stateDiff: [], + parentStateRoot: bytesToHex(parentStateRoot), + verkleProof: undefined as any, + } + const accessedSuffixes = new Map() + for (const chunkKey of accessWitness['chunks'].keys()) { + const stem = chunkKey.slice(0, 34) as PrefixedHexString + if (accessedSuffixes.has(stem)) { + const suffixes = accessedSuffixes.get(stem) + suffixes!.push(parseInt(chunkKey.slice(34), 16)) + accessedSuffixes.set(stem, suffixes!) + } else { + accessedSuffixes.set(stem, [parseInt(chunkKey.slice(34), 16)]) + } + } + // Get values + for (const stem of accessedSuffixes.keys()) { + trie.root(parentStateRoot) + const currentValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) + trie.root(postStateRoot) + const newValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) + const stemStateDiff = [] + for (const suffix of accessedSuffixes.get(stem)!) { + if (currentValues[suffix] === newValues[suffix]) continue + stemStateDiff.push({ + suffix, + currentValue: currentValues[suffix] ? bytesToHex(currentValues[suffix]) : null, + newValue: newValues[suffix] ? bytesToHex(newValues[suffix]) : null, + }) + } + ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) + } + return ew +} From b92efedc3361dad47639ba08d26c12dbbb6078ca Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:37:49 -0500 Subject: [PATCH 04/25] execution witness fixes --- package-lock.json | 1 + packages/evm/package.json | 1 + packages/evm/src/verkleAccessWitness.ts | 22 ++++++++++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cd24fbf67e..087a6597d2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16716,6 +16716,7 @@ "@ethereumjs/common": "^5.0.0-alpha.1", "@ethereumjs/statemanager": "^3.0.0-alpha.1", "@ethereumjs/util": "^10.0.0-alpha.1", + "@ethereumjs/verkle": "^0.2.0-alpha.1", "@noble/curves": "^1.8.1", "@types/debug": "^4.1.9", "debug": "^4.3.3", diff --git a/packages/evm/package.json b/packages/evm/package.json index cdfac82456e..d5edbf64393 100644 --- a/packages/evm/package.json +++ b/packages/evm/package.json @@ -49,6 +49,7 @@ "@ethereumjs/common": "^5.0.0-alpha.1", "@ethereumjs/statemanager": "^3.0.0-alpha.1", "@ethereumjs/util": "^10.0.0-alpha.1", + "@ethereumjs/verkle": "^0.2.0-alpha.1", "@noble/curves": "^1.8.1", "@types/debug": "^4.1.9", "debug": "^4.3.3", diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 839efc64032..31610894507 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -8,6 +8,7 @@ import { VERKLE_MAIN_STORAGE_OFFSET, VERKLE_NODE_WIDTH, bytesToHex, + equalsBytes, getVerkleKey, getVerkleStem, getVerkleTreeIndicesForCodeChunk, @@ -403,15 +404,16 @@ export const generateExecutionWitness = async ( } const accessedSuffixes = new Map() for (const chunkKey of accessWitness['chunks'].keys()) { - const stem = chunkKey.slice(0, 34) as PrefixedHexString + const stem = chunkKey.slice(0, 64) as PrefixedHexString if (accessedSuffixes.has(stem)) { const suffixes = accessedSuffixes.get(stem) - suffixes!.push(parseInt(chunkKey.slice(34), 16)) + suffixes!.push(parseInt(chunkKey.slice(64), 16)) accessedSuffixes.set(stem, suffixes!) } else { - accessedSuffixes.set(stem, [parseInt(chunkKey.slice(34), 16)]) + accessedSuffixes.set(stem, [parseInt(chunkKey.slice(64), 16)]) } } + // Get values for (const stem of accessedSuffixes.keys()) { trie.root(parentStateRoot) @@ -420,14 +422,22 @@ export const generateExecutionWitness = async ( const newValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) const stemStateDiff = [] for (const suffix of accessedSuffixes.get(stem)!) { - if (currentValues[suffix] === newValues[suffix]) continue + // skip if both are the same + if ( + notNullish(currentValues[suffix]) && + notNullish(newValues[suffix]) && + equalsBytes(currentValues[suffix]!, newValues[suffix]!) + ) + continue stemStateDiff.push({ suffix, - currentValue: currentValues[suffix] ? bytesToHex(currentValues[suffix]) : null, - newValue: newValues[suffix] ? bytesToHex(newValues[suffix]) : null, + currentValue: currentValues[suffix] ? bytesToHex(currentValues[suffix]!) : null, + newValue: newValues[suffix] ? bytesToHex(newValues[suffix]!) : null, }) } ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) } return ew } + +const notNullish = (value: any) => value !== null && value !== undefined From 4454abddedf0ed8e1cf0ecb2b56efc2da36953cc Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:33:29 -0500 Subject: [PATCH 05/25] Fix logic in building suffix diffs --- packages/evm/src/verkleAccessWitness.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 31610894507..5bc6734d0cc 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -417,22 +417,24 @@ export const generateExecutionWitness = async ( // Get values for (const stem of accessedSuffixes.keys()) { trie.root(parentStateRoot) + const suffixes = accessedSuffixes.get(stem) + if (suffixes === undefined || suffixes.length === 0) continue const currentValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) trie.root(postStateRoot) const newValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) const stemStateDiff = [] - for (const suffix of accessedSuffixes.get(stem)!) { + for (let x = 0; x < suffixes.length; x++) { // skip if both are the same if ( - notNullish(currentValues[suffix]) && - notNullish(newValues[suffix]) && - equalsBytes(currentValues[suffix]!, newValues[suffix]!) + notNullish(currentValues[x]) && + notNullish(newValues[x]) && + equalsBytes(currentValues[x]!, newValues[x]!) ) continue stemStateDiff.push({ - suffix, - currentValue: currentValues[suffix] ? bytesToHex(currentValues[suffix]!) : null, - newValue: newValues[suffix] ? bytesToHex(newValues[suffix]!) : null, + suffix: suffixes[x], + currentValue: currentValues[x] ? bytesToHex(currentValues[x]!) : null, + newValue: newValues[x] ? bytesToHex(newValues[x]!) : null, }) } ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) From 9094e38fc254b71e11c877e6433aef7783574867 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:05:17 -0500 Subject: [PATCH 06/25] Add test --- packages/evm/test/verkle.spec.ts | 81 +++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/evm/test/verkle.spec.ts b/packages/evm/test/verkle.spec.ts index 36ed5ee1028..6ed17e9a1e8 100644 --- a/packages/evm/test/verkle.spec.ts +++ b/packages/evm/test/verkle.spec.ts @@ -2,8 +2,11 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' import { bigIntToBytes, + bytesToHex, createAccount, createAddressFromString, + decodeVerkleLeafBasicData, + getVerkleStem, hexToBytes, setLengthLeft, } from '@ethereumjs/util' @@ -11,7 +14,7 @@ import { createVerkleTree } from '@ethereumjs/verkle' import * as verkle from 'micro-eth-signer/verkle' import { assert, describe, it } from 'vitest' -import { VerkleAccessWitness, createEVM } from '../src/index.js' +import { VerkleAccessWitness, createEVM, generateExecutionWitness } from '../src/index.js' describe('verkle tests', () => { it('should execute bytecode and update the state', async () => { @@ -109,3 +112,79 @@ describe('verkle tests', () => { assert.equal(res.execResult.exceptionError?.error, undefined) }) }) +describe('generate an execution witness', () => { + it('should generate the correct execution witness from a prestate and changes', async () => { + const preStateVKT = { + '0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d00': + '0x00000000000000000000000000000000000000000000003635c9adc5dea00000', + '0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d01': + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + '0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad700': + '0x0000000000000036000000000000000100000000000000000000000000000000', + '0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad701': + '0xdf61faef43babbb1ebde8fd82ab9cb4cb74c240d0025138521477e073f72080a', + '0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad780': + '0x0060203611603157600143035f35116029575f35612000014311602957612000', + '0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad781': + '0x005f3506545f5260205ff35b5f5f5260205ff35b5f5ffd000000000000000000', + } + const tx = { + type: '0x0', + chainId: '0x1', + nonce: '0x0', + gasPrice: '0xa', + gas: '0x5f5e100', + to: '0x8a0a19589531694250d570040a0c4b74576919b8', + value: '0x0', + input: '0x', + v: '0x25', + r: '0x50ae258f0b1f7c44e5227b43c338aa7f2d9805115b90a6baeaaee2358796e074', + s: '0xec910ad0244580c17e1d6a512b3574c62e92840184109e3037760d39b20cb94', + sender: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b', + } + + const common = new Common({ + chain: Mainnet, + customCrypto: { verkle }, + eips: [6800], + hardfork: Hardfork.Prague, + }) + const trie = await createVerkleTree() + // Setup prestate + for (const [key, value] of Object.entries(preStateVKT)) { + const stem = hexToBytes(key).slice(0, 31) + const suffix = parseInt(key.slice(64), 16) + await trie.put(stem, [suffix], [hexToBytes(value)]) + } + const preStateRoot = trie.root() + const sm = new StatefulVerkleStateManager({ common, trie }) + const evm = await createEVM({ common, stateManager: sm }) + evm.verkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + evm.systemVerkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + // Run tx + await evm.runCall({ + code: hexToBytes(tx.input), + caller: createAddressFromString(tx.sender), + to: createAddressFromString(tx.to), + gasLimit: BigInt(tx.gas), + gasPrice: BigInt(tx.gasPrice), + }) + const executionWitness = await generateExecutionWitness( + sm, + evm.verkleAccessWitness, + preStateRoot, + ) + const stem = bytesToHex(getVerkleStem(verkle, createAddressFromString(tx.sender))) + assert.ok(executionWitness.stateDiff.findIndex((diff) => diff.stem === stem) !== -1) + const diff = + executionWitness.stateDiff[executionWitness.stateDiff.findIndex((diff) => diff.stem === stem)] + const suffixDiff = diff.suffixDiffs.find((suffDiff) => suffDiff.suffix === 0) + assert.ok(suffixDiff?.newValue !== undefined) + // Ensure sender account nonce is 1 in execution witness + assert.equal(decodeVerkleLeafBasicData(hexToBytes(suffixDiff!.newValue!)).nonce, 1n) + }) +}) From 45923289479a11afde4d297853bf0e5267c284a0 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:08:09 -0500 Subject: [PATCH 07/25] spell check --- packages/evm/test/verkle.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/evm/test/verkle.spec.ts b/packages/evm/test/verkle.spec.ts index 6ed17e9a1e8..7d40ad008b7 100644 --- a/packages/evm/test/verkle.spec.ts +++ b/packages/evm/test/verkle.spec.ts @@ -180,9 +180,9 @@ describe('generate an execution witness', () => { ) const stem = bytesToHex(getVerkleStem(verkle, createAddressFromString(tx.sender))) assert.ok(executionWitness.stateDiff.findIndex((diff) => diff.stem === stem) !== -1) - const diff = + const stemDiff = executionWitness.stateDiff[executionWitness.stateDiff.findIndex((diff) => diff.stem === stem)] - const suffixDiff = diff.suffixDiffs.find((suffDiff) => suffDiff.suffix === 0) + const suffixDiff = stemDiff.suffixDiffs.find((diff) => diff.suffix === 0) assert.ok(suffixDiff?.newValue !== undefined) // Ensure sender account nonce is 1 in execution witness assert.equal(decodeVerkleLeafBasicData(hexToBytes(suffixDiff!.newValue!)).nonce, 1n) From 2251c2c528491b8402060a2f08edac1f4f1880c0 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:12:58 -0500 Subject: [PATCH 08/25] Address feedback --- packages/evm/src/verkleAccessWitness.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 5bc6734d0cc..176f3909488 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -391,11 +391,11 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface { } export const generateExecutionWitness = async ( - stateManager: StatefulVerkleStateManager | StatelessVerkleStateManager, + stateManager: StatefulVerkleStateManager, accessWitness: VerkleAccessWitness, parentStateRoot: Uint8Array, ) => { - const trie = (stateManager as StatefulVerkleStateManager)['_trie'] as VerkleTree + const trie = stateManager['_trie'] as VerkleTree const postStateRoot = await stateManager.getStateRoot() const ew: VerkleExecutionWitness = { stateDiff: [], @@ -419,27 +419,27 @@ export const generateExecutionWitness = async ( trie.root(parentStateRoot) const suffixes = accessedSuffixes.get(stem) if (suffixes === undefined || suffixes.length === 0) continue - const currentValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) + const currentValues = await trie.get(hexToBytes(stem), suffixes) trie.root(postStateRoot) - const newValues = await trie.get(hexToBytes(stem), accessedSuffixes.get(stem)!) + const newValues = await trie.get(hexToBytes(stem), suffixes) const stemStateDiff = [] for (let x = 0; x < suffixes.length; x++) { // skip if both are the same + const currentValue = currentValues[x] + const newValue = newValues[x] if ( - notNullish(currentValues[x]) && - notNullish(newValues[x]) && - equalsBytes(currentValues[x]!, newValues[x]!) + currentValue instanceof Uint8Array && + newValue instanceof Uint8Array && + equalsBytes(currentValue, newValue) ) continue stemStateDiff.push({ suffix: suffixes[x], - currentValue: currentValues[x] ? bytesToHex(currentValues[x]!) : null, - newValue: newValues[x] ? bytesToHex(newValues[x]!) : null, + currentValue: currentValue ? bytesToHex(currentValue) : null, + newValue: newValue ? bytesToHex(newValue) : null, }) } ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) } return ew } - -const notNullish = (value: any) => value !== null && value !== undefined From ab928098c4179e2e14aa7a72201504ba43e23c5e Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:29:45 -0500 Subject: [PATCH 09/25] Lint --- packages/evm/src/verkleAccessWitness.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 176f3909488..9b666d1315e 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -28,10 +28,7 @@ import type { VerkleAccessedState, VerkleAccessedStateWithAddress, } from '@ethereumjs/common' -import type { - StatefulVerkleStateManager, - StatelessVerkleStateManager, -} from '@ethereumjs/statemanager' +import type { StatefulVerkleStateManager } from '@ethereumjs/statemanager' import type { Address, PrefixedHexString, From 12f5180b6feb2a53ed97c170414071e6c6106b8a Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Mon, 3 Feb 2025 20:32:55 -0500 Subject: [PATCH 10/25] the current set of hacks --- packages/common/src/eips.ts | 3 + packages/evm/src/evm.ts | 2 +- packages/evm/src/params.ts | 7 ++ packages/evm/src/precompiles/12-execute.ts | 45 +++++++++ .../evm/test/precompiles/12-execute.spec.ts | 39 ++++++++ .../test/precompiles/executionWitness.json | 99 +++++++++++++++++++ packages/vm/src/runBlock.ts | 5 +- 7 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 packages/evm/src/precompiles/12-execute.ts create mode 100644 packages/evm/test/precompiles/12-execute.spec.ts create mode 100644 packages/evm/test/precompiles/executionWitness.json diff --git a/packages/common/src/eips.ts b/packages/common/src/eips.ts index 1c6e31aab00..df20a341776 100644 --- a/packages/common/src/eips.ts +++ b/packages/common/src/eips.ts @@ -470,4 +470,7 @@ export const eipsDict: EIPsDict = { minimumHardfork: Hardfork.Chainstart, requiredEIPs: [2935], }, + 9999: { + minimumHardfork: Hardfork.Cancun, + }, } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index a07ea4dade4..be023349fed 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -184,7 +184,7 @@ export class EVM implements EVMInterface { const supportedEIPs = [ 663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670, 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6780, 6800, - 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709, + 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709, 9999, ] for (const eip of this.common.eips()) { diff --git a/packages/evm/src/params.ts b/packages/evm/src/params.ts index 4b28e728ffb..7ac9f18ab4c 100644 --- a/packages/evm/src/params.ts +++ b/packages/evm/src/params.ts @@ -409,4 +409,11 @@ export const paramsEVM: ParamsDict = { eofcreateGas: 32000, // Base fee of the EOFCREATE opcode (Same as CREATE/CREATE2) returncontractGas: 0, // Base fee of the RETURNCONTRACT opcode }, + 9999: { + /* Not an actual EIP, but a placeholder for future EXECUTE precompile EIP */ + // gasPrices + executeGasCost: 50000, + executeCumulativeGasLimit: 10000000, + executeCumulativeGasTarget: 100000, + }, } diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts new file mode 100644 index 00000000000..07a0c532c82 --- /dev/null +++ b/packages/evm/src/precompiles/12-execute.ts @@ -0,0 +1,45 @@ +import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' + +import { createEVM } from '../constructors.js' +import { EvmErrorResult, OOGResult } from '../evm.js' +import { ERROR, EvmError } from '../exceptions.js' + +import { gasLimitCheck } from './util.js' + +import { getPrecompileName } from './index.js' + +import type { ExecResult } from '../types.js' +import type { PrecompileInput } from './types.js' + +export function precompile12(opts: PrecompileInput): ExecResult { + const pName = getPrecompileName('12') + const data = opts.data + + const gasUsed = opts.common.param('executeGasCost') + if (!gasLimitCheck(opts, gasUsed, pName)) { + return OOGResult(opts.gasLimit) + } + + if (data.length < 128) { + return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + } + + const preStateRoot = data.subarray(0, 32) // prestateroot for L2 state + const postStateRoot = data.subarray(32, 64) // post state root for L2 state + const trace = data.subarray(64, 96) // reference to state access and + const executeGasUsed = data.subarray(96) + + const executionResult = true + + const stateManager = new StatelessVerkleStateManager({ common: opts.common }) + const evm = createEVM({ stateManager, common: opts.common }) + + opts._debug?.(`${pName} trace executed successfully=${executionResult}`) + + const returnValue = executionResult ? new Uint8Array(1).fill(1) : new Uint8Array(1).fill(0) + + return { + executionGasUsed: gasUsed, + returnValue, + } +} diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts new file mode 100644 index 00000000000..89cc985b035 --- /dev/null +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -0,0 +1,39 @@ +import { Common, Hardfork, Mainnet } from '@ethereumjs/common' +import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' +import { createTx, createTxFromRLP } from '@ethereumjs/tx' +import { trustedSetup } from '@paulmillr/trusted-setups/fast.js' +import { hexToBytes } from 'ethereum-cryptography/utils' +import { KZG as microEthKZG } from 'micro-eth-signer/kzg' +import * as verkle from 'micro-eth-signer/verkle' +import { assert, describe, it } from 'vitest' + +import { createEVM, getActivePrecompiles } from '../../src/index.js' + +import witness from './executionWitness.json' + +describe('Precompiles: EXECUTE', () => { + it('should execute a trace', async () => { + const kzg = new microEthKZG(trustedSetup) + const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.Prague, + eips: [6800, 9999], + customCrypto: { + kzg, + verkle, + }, + }) + const stateManager = new StatelessVerkleStateManager({ common }) + stateManager.initVerkleExecutionWitness(1n, witness.witness) + const evm = await createEVM({ stateManager, common }) + const txs = witness.txs.map((tx) => createTxFromRLP(hexToBytes(tx), { common })) + for (const tx of txs) { + const res = await evm.runCall(tx) + console.log(res) + } + const addressStr = '0000000000000000000000000000000000000012' + const EXECUTE = getActivePrecompiles(common).get(addressStr)! + + const result = await EXECUTE({}) + }) +}) diff --git a/packages/evm/test/precompiles/executionWitness.json b/packages/evm/test/precompiles/executionWitness.json new file mode 100644 index 00000000000..78bd108dd65 --- /dev/null +++ b/packages/evm/test/precompiles/executionWitness.json @@ -0,0 +1,99 @@ +{ + "witness": { + "stateDiff": [ + { + "stem": "0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d", + "suffixDiffs": [ + { + "suffix": 0, + "currentValue": "0x00000000000000000000000000000000000000000000003635c9adc5dea00000", + "newValue": "0x00000000000000000000000000000001000000000000003635c9adc5de9ccbb0" + }, + { + "suffix": 1, + "currentValue": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "newValue": null + } + ] + }, + { + "stem": "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", + "suffixDiffs": [ + { + "suffix": 64, + "newValue": "0x00b2e892fbf04dcdbb33d71633d7cea0722aed27f8a9d0cf9912f97b34f9dadd", + "currentValue": null + } + ] + }, + { + "stem": "0x914ec5f0e0c27fe094862fbd89a6abe684939af6940434d8bf218cedb2d624", + "suffixDiffs": [ + { + "suffix": 0, + "newValue": "0x000000000000000000000000000000000000000000000000000000000000f618", + "currentValue": null + }, + { + "suffix": 1, + "newValue": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "currentValue": null + } + ] + }, + { + "stem": "0xecb505156708480caf702cd85124f67f3ed78ae4bc890a6dcb62574ba9a90c", + "suffixDiffs": [ + { + "suffix": 0, + "currentValue": null, + "newValue": null + }, + { + "suffix": 1, + "currentValue": null, + "newValue": null + } + ] + } + ], + "verkleProof": { + "otherStems": [], + "depthExtensionPresent": "0x0a0a0808", + "commitmentsByPath": [ + "0x360bcc4c15a10f6f058d1d07e13e9a271da3da0cd69026d5a9ab25e1c0a5d697", + "0x324e7b7405bbfae46866bad5c94ac2983093b86f85035fe5984ec40a1b5e5ce3", + "0x61506c1cc4f27c9231a546a4b0d249d5f3ba4ba9e63c5ebb84087665eb396e95", + "0x4909fc1889e10819cda41706a26373145c4e4575d68b96569af12db69040b6e1" + ], + "d": "0x530b7b926f178a1ce3fb92922303111735f9e5395f041b8d2b6089dbf9a73c31", + "ipaProof": { + "cl": [ + "0x3d87ee7a9da30dba329f168f203cff8690921bf51e03edeb439ccd838abf68af", + "0x5ddc74d79bedcd053f53432f873bebceff9acb5e9eb1c55b7b2228c40fab1e9b", + "0x67eacc9c3d3cf158686d0196256d9b858d00aede48fe7d74345628fa43e2f423", + "0x3a4ee7713b54286822dd4250fe7713ff89bd73f5dd25a8850cb95813df63bdf7", + "0x5341f88ef80898f8550f70466b363626c0e5a4749159e24acb4f7b1fade6e1d6", + "0x18d2a1b99473a3a8f94bb9032e2a30e9f303bc37950fc0f67cb62825f42808ac", + "0x56a7d7f239c132fd999991d2de37bdbc88b0edc780b61e132fd5e57986d20edf", + "0x0bab046e2e62e4c51f807df783f9dc3bef37a943c0dfcf01ec0bc9733f144a38" + ], + "cr": [ + "0x27e8b165e814dd45d7c628e1f3dcb31b50b0c1818f87cacf2c041b1e42abe5bd", + "0x71dc9a4412d0ae3ce105f16d772c62cefbe7088673b523496b37bf16c303dffd", + "0x0baed508372a64eca49e8e9cc379b848bedd37d040ac683ccde28debac3793ca", + "0x6b96bfb4b9c044137613dc62be2ab377b31fb93a587742bca109525726799b84", + "0x0b26a2aa70fe810cadf1d7cf47b7823ede1a4129fdc714c7b8bee60e01d6d0e9", + "0x4ae3cd3a9e08425363ca98dce1f264e3cd2fd81a92758a372f412547017dd57e", + "0x44ecde38a0a3e046352cac96b63602a73381308fce1c0900197273fe14ab615d", + "0x28bf63a6e0aa789d798fd116fb29e3820aefff43aaa5226c28985871ee519f81" + ], + "finalEvaluation": "0x00095c56030aeac61ba23e9468ddf059f5b2dac95af6a13e78720c44ca7c4ed7" + } + }, + "parentStateRoot": "0x3901899f4bfd31a5d77611f70c4eb98cffdbbe5280fba656631f04336afe6f5a" + }, + "txs": [ + "0xf861800a8405f5e100948a0a19589531694250d570040a0c4b74576919b8808025a050ae258f0b1f7c44e5227b43c338aa7f2d9805115b90a6baeaaee2358796e074a00ec910ad0244580c17e1d6a512b3574c62e92840184109e3037760d39b20cb94" + ] +} diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 0d8c1f122f1..26696300cd1 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -191,6 +191,7 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise bytesToHex(tx.serialize()))) // Checkpoint state await vm.evm.journal.checkpoint() if (vm.DEBUG) { @@ -354,6 +355,8 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise Date: Wed, 5 Feb 2025 21:20:44 -0500 Subject: [PATCH 11/25] Finish constructing test --- packages/evm/src/evm.ts | 5 + packages/evm/src/precompiles/12-execute.ts | 87 ++++++++++++++-- packages/evm/src/precompiles/index.ts | 11 +++ .../evm/test/precompiles/12-execute.spec.ts | 98 +++++++++++++++---- 4 files changed, 175 insertions(+), 26 deletions(-) diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index be023349fed..3ec8d5c0dc6 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -54,6 +54,7 @@ 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' +import type { PrefixedHexString } from '@ethereumjs/util' const debug = debugDefault('evm:evm') const debugGas = debugDefault('evm:gas') @@ -150,6 +151,8 @@ export class EVM implements EVMInterface { private _bn254: EVMBN254Interface + private executionBlobs: Map // Map of + /** * * Creates new EVM object @@ -245,6 +248,8 @@ export class EVM implements EVMInterface { // Additional window check is to prevent vite browser bundling (and potentially other) to break this.DEBUG = typeof window === 'undefined' ? (process?.env?.DEBUG?.includes('ethjs') ?? false) : false + + this.executionBlobs = new Map() } /** diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index 07a0c532c82..c8b5e3122c0 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -1,38 +1,107 @@ -import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' +import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' +import { + bytesToBigInt, + bytesToHex, + createAddressFromString, + equalsBytes, + hexToBytes, +} from '@ethereumjs/util' +import { createVerkleTree } from '@ethereumjs/verkle' import { createEVM } from '../constructors.js' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' +import { VerkleAccessWitness } from '../verkleAccessWitness.js' import { gasLimitCheck } from './util.js' import { getPrecompileName } from './index.js' +import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' +import type { VerkleExecutionWitness } from '@ethereumjs/util' -export function precompile12(opts: PrecompileInput): ExecResult { +export async function precompile12(opts: PrecompileInput): Promise { const pName = getPrecompileName('12') const data = opts.data - + const evm = opts._EVM as EVM const gasUsed = opts.common.param('executeGasCost') if (!gasLimitCheck(opts, gasUsed, pName)) { return OOGResult(opts.gasLimit) } - if (data.length < 128) { + if (data.length !== 128) { return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) } - const preStateRoot = data.subarray(0, 32) // prestateroot for L2 state + const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state const postStateRoot = data.subarray(32, 64) // post state root for L2 state - const trace = data.subarray(64, 96) // reference to state access and - const executeGasUsed = data.subarray(96) + const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions + if (traceBlob === undefined) { + opts._debug?.(`${pName} error - trace not found`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } + const decodedTrace = JSON.parse(new TextDecoder().decode(traceBlob)) + + if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { + opts._debug?.(`${pName} error - trace is invalid`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } + const executeGasUsed = bytesToBigInt(data.subarray(96)) + + const witness = decodedTrace.witness as VerkleExecutionWitness + const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) + + // Populate the L2 state trie with the prestate + for (const stateDiff of witness.stateDiff) { + const suffixes: number[] = [] + const values: Uint8Array[] = [] + for (const diff of stateDiff.suffixDiffs) { + if (diff.currentValue !== null) { + suffixes.push(Number(diff.suffix)) + values.push(hexToBytes(diff.currentValue)) + } + } + const stem = hexToBytes(stateDiff.stem) + await tree.put(stem, suffixes, values) + } const executionResult = true - const stateManager = new StatelessVerkleStateManager({ common: opts.common }) - const evm = createEVM({ stateManager, common: opts.common }) + const stateManager = new StatefulVerkleStateManager({ common: opts.common, trie: tree }) + const l2EVM = await createEVM({ stateManager, common: opts.common }) + + l2EVM.verkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: opts.common.customCrypto.verkle!, + }) + l2EVM.systemVerkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: opts.common.customCrypto.verkle!, + }) + let computedGasUsed = 0n + + // Run each transaction in the trace + for (const tx of decodedTrace.txs) { + const res = await l2EVM.runCall({ + to: createAddressFromString(tx.to), + caller: createAddressFromString(tx.from), + gasLimit: BigInt(tx.gasLimit), + gasPrice: BigInt(tx.gasPrice), + value: BigInt(tx.value), + data: tx.data !== undefined ? hexToBytes(tx.data) : undefined, + }) + computedGasUsed += res.execResult.executionGasUsed + } + + if (computedGasUsed !== executeGasUsed) { + opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } + + if (!equalsBytes(postStateRoot, tree.root())) { + opts._debug?.(`${pName} post state root mismatch`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } opts._debug?.(`${pName} trace executed successfully=${executionResult}`) diff --git a/packages/evm/src/precompiles/index.ts b/packages/evm/src/precompiles/index.ts index 55c25c95b1d..b2a2168cab5 100644 --- a/packages/evm/src/precompiles/index.ts +++ b/packages/evm/src/precompiles/index.ts @@ -18,6 +18,7 @@ import { precompile0e } from './0e-bls12-g2msm.js' import { precompile0f } from './0f-bls12-pairing.js' import { precompile10 } from './10-bls12-map-fp-to-g1.js' import { precompile11 } from './11-bls12-map-fp2-to-g2.js' +import { precompile12 } from './12-execute.js' import { MCLBLS, NobleBLS } from './bls12_381/index.js' import { NobleBN254, RustBN254 } from './bn254/index.js' @@ -210,6 +211,15 @@ const precompileEntries: PrecompileEntry[] = [ precompile: precompile11, name: 'BLS12_MAP_FP_TO_G2 (0x11)', }, + { + address: BYTES_19 + '12', + check: { + type: PrecompileAvailabilityCheck.EIP, + param: 9999, + }, + precompile: precompile12, + name: 'EXECUTE (0x12)', + }, ] const precompiles: Precompiles = { @@ -230,6 +240,7 @@ const precompiles: Precompiles = { [BYTES_19 + '0f']: precompile0f, [BYTES_19 + '10']: precompile10, [BYTES_19 + '11']: precompile11, + [BYTES_19 + '12']: precompile12, } type DeletePrecompile = { diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 89cc985b035..c16caa0e60c 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -1,39 +1,103 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { StatelessVerkleStateManager } from '@ethereumjs/statemanager' -import { createTx, createTxFromRLP } from '@ethereumjs/tx' -import { trustedSetup } from '@paulmillr/trusted-setups/fast.js' -import { hexToBytes } from 'ethereum-cryptography/utils' -import { KZG as microEthKZG } from 'micro-eth-signer/kzg' +import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' +import { + bigIntToBytes, + bytesToHex, + concatBytes, + createAccount, + createAddressFromPrivateKey, + createAddressFromString, + hexToBytes, + setLengthLeft, +} from '@ethereumjs/util' +import { createVerkleTree } from '@ethereumjs/verkle' +import { sha256 } from 'ethereum-cryptography/sha256' import * as verkle from 'micro-eth-signer/verkle' import { assert, describe, it } from 'vitest' -import { createEVM, getActivePrecompiles } from '../../src/index.js' - -import witness from './executionWitness.json' +import { + VerkleAccessWitness, + createEVM, + generateExecutionWitness, + getActivePrecompiles, +} from '../../src/index.js' describe('Precompiles: EXECUTE', () => { it('should execute a trace', async () => { - const kzg = new microEthKZG(trustedSetup) const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague, eips: [6800, 9999], customCrypto: { - kzg, verkle, }, }) - const stateManager = new StatelessVerkleStateManager({ common }) - stateManager.initVerkleExecutionWitness(1n, witness.witness) + const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) + const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') + const receiver = createAddressFromPrivateKey( + hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), + ) + const tx = { + to: receiver.toString(), + from: address.toString(), + gasLimit: '0xffffffffff', + gasPrice: '0x1', + value: '0x1', + } + const tree = await createVerkleTree({ verkleCrypto: verkle }) + const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) + await stateManager.putAccount(address, account) + const preStateRoot = tree.root() const evm = await createEVM({ stateManager, common }) - const txs = witness.txs.map((tx) => createTxFromRLP(hexToBytes(tx), { common })) - for (const tx of txs) { - const res = await evm.runCall(tx) - console.log(res) + + evm.verkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + evm.systemVerkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + const res = await evm.runCall({ + to: receiver, + caller: address, + gasLimit: BigInt(tx.gasLimit), + gasPrice: BigInt(tx.gasPrice), + value: BigInt(tx.value), + }) + const executionGasUsed = res.execResult.executionGasUsed + const postStateRoot = tree.root() + const execWitness = await generateExecutionWitness( + stateManager, + evm.verkleAccessWitness, + preStateRoot, + ) + + // Create a trace + const trace = { + witness: execWitness, + txs: [tx], } + // TODO: Replace this with proper serialization + const bytes = new TextEncoder().encode(JSON.stringify(trace)) + // We use the hash as a reference to the trace. This should be replaced with a proper blob commitment (or versionedHash) + // and the data should be stored as a proper Ethereum blob + const hash = bytesToHex(sha256(bytes)) + evm['executionBlobs'].set(hash, bytes) + const addressStr = '0000000000000000000000000000000000000012' const EXECUTE = getActivePrecompiles(common).get(addressStr)! + const input = concatBytes( + preStateRoot, + postStateRoot, + hexToBytes(hash), + setLengthLeft(bigIntToBytes(executionGasUsed), 32), + ) - const result = await EXECUTE({}) + const result = await EXECUTE({ + data: input, + gasLimit: 1000000000n, + common, + _EVM: evm, + }) + assert.equal(result.returnValue[0], 1) }) }) From 44a447a781df111c19b37760956fab29eb0bc6e6 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:29:25 -0500 Subject: [PATCH 12/25] Add clarifying comments --- packages/evm/test/precompiles/12-execute.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index c16caa0e60c..32756267bfb 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -32,6 +32,7 @@ describe('Precompiles: EXECUTE', () => { verkle, }, }) + // Construct L2 state and transaction const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') const receiver = createAddressFromPrivateKey( @@ -71,6 +72,8 @@ describe('Precompiles: EXECUTE', () => { preStateRoot, ) + // End of L2 state construction + // Create a trace const trace = { witness: execWitness, From bfb14c5294dc881a4f660f1592afe833fd6121bf Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:54:42 -0500 Subject: [PATCH 13/25] Convert trace to ssz container --- packages/evm/src/precompiles/12-execute.ts | 65 +++++++++++++++++-- .../evm/test/precompiles/12-execute.spec.ts | 28 ++++---- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index c8b5e3122c0..bfa94ffd2f4 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -7,6 +7,7 @@ import { hexToBytes, } from '@ethereumjs/util' import { createVerkleTree } from '@ethereumjs/verkle' +import * as ssz from 'micro-eth-signer/ssz' import { createEVM } from '../constructors.js' import { EvmErrorResult, OOGResult } from '../evm.js' @@ -22,6 +23,56 @@ import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' import type { VerkleExecutionWitness } from '@ethereumjs/util' +const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas +export const traceContainer = ssz.container({ + txs: ssz.list( + 256, + ssz.container({ + to: ssz.bytevector(20), + from: ssz.bytevector(20), + gasLimit: ssz.uint64, + gasPrice: ssz.uint64, + value: ssz.uint64, + data: ssz.bytelist(MAX_CALL_DATA_SIZE), + }), + ), + witness: ssz.container({ + stateDiff: ssz.list( + 256, + ssz.container({ + stem: ssz.bytevector(31), + suffixDiffs: ssz.list( + 256, + ssz.container({ + suffix: ssz.uint64, + currentValue: ssz.bytevector(32), + newValue: ssz.bytevector(32), + }), + ), + }), + ), + parentStateRoot: ssz.bytevector(32), + }), +}) + +export const executionWitnessJSONToSSZ = (witness: VerkleExecutionWitness) => { + return { + stateDiff: witness.stateDiff.map((diff) => ({ + stem: hexToBytes(diff.stem), + suffixDiffs: diff.suffixDiffs.map((suffixDiff) => ({ + suffix: BigInt(suffixDiff.suffix), + currentValue: + suffixDiff.currentValue !== null + ? hexToBytes(suffixDiff.currentValue) + : new Uint8Array(32), + newValue: + suffixDiff.newValue !== null ? hexToBytes(suffixDiff.newValue) : new Uint8Array(32), + })), + })), + parentStateRoot: hexToBytes(witness.parentStateRoot), + } +} + export async function precompile12(opts: PrecompileInput): Promise { const pName = getPrecompileName('12') const data = opts.data @@ -43,7 +94,7 @@ export async function precompile12(opts: PrecompileInput): Promise { return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) } - const decodedTrace = JSON.parse(new TextDecoder().decode(traceBlob)) + const decodedTrace = traceContainer.decode(traceBlob) if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { opts._debug?.(`${pName} error - trace is invalid`) @@ -51,7 +102,7 @@ export async function precompile12(opts: PrecompileInput): Promise { } const executeGasUsed = bytesToBigInt(data.subarray(96)) - const witness = decodedTrace.witness as VerkleExecutionWitness + const witness = decodedTrace.witness const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) // Populate the L2 state trie with the prestate @@ -61,10 +112,10 @@ export async function precompile12(opts: PrecompileInput): Promise { for (const diff of stateDiff.suffixDiffs) { if (diff.currentValue !== null) { suffixes.push(Number(diff.suffix)) - values.push(hexToBytes(diff.currentValue)) + values.push(diff.currentValue) } } - const stem = hexToBytes(stateDiff.stem) + const stem = stateDiff.stem await tree.put(stem, suffixes, values) } const executionResult = true @@ -83,12 +134,12 @@ export async function precompile12(opts: PrecompileInput): Promise { // Run each transaction in the trace for (const tx of decodedTrace.txs) { const res = await l2EVM.runCall({ - to: createAddressFromString(tx.to), - caller: createAddressFromString(tx.from), + to: createAddressFromString(bytesToHex(tx.to)), + caller: createAddressFromString(bytesToHex(tx.from)), gasLimit: BigInt(tx.gasLimit), gasPrice: BigInt(tx.gasPrice), value: BigInt(tx.value), - data: tx.data !== undefined ? hexToBytes(tx.data) : undefined, + data: tx.data !== undefined ? tx.data : undefined, }) computedGasUsed += res.execResult.executionGasUsed } diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 32756267bfb..59dc08d0599 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -21,6 +21,7 @@ import { generateExecutionWitness, getActivePrecompiles, } from '../../src/index.js' +import { executionWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.js' describe('Precompiles: EXECUTE', () => { it('should execute a trace', async () => { @@ -39,11 +40,12 @@ describe('Precompiles: EXECUTE', () => { hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), ) const tx = { - to: receiver.toString(), - from: address.toString(), - gasLimit: '0xffffffffff', - gasPrice: '0x1', - value: '0x1', + to: receiver.toBytes(), + from: address.toBytes(), + gasLimit: BigInt('0xffffffffff'), + gasPrice: BigInt('0x1'), + value: BigInt('0x1'), + data: new Uint8Array(), } const tree = await createVerkleTree({ verkleCrypto: verkle }) const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) @@ -60,9 +62,9 @@ describe('Precompiles: EXECUTE', () => { const res = await evm.runCall({ to: receiver, caller: address, - gasLimit: BigInt(tx.gasLimit), - gasPrice: BigInt(tx.gasPrice), - value: BigInt(tx.value), + gasLimit: tx.gasLimit, + gasPrice: tx.gasPrice, + value: tx.value, }) const executionGasUsed = res.execResult.executionGasUsed const postStateRoot = tree.root() @@ -76,15 +78,15 @@ describe('Precompiles: EXECUTE', () => { // Create a trace const trace = { - witness: execWitness, + witness: executionWitnessJSONToSSZ(execWitness), txs: [tx], } - // TODO: Replace this with proper serialization - const bytes = new TextEncoder().encode(JSON.stringify(trace)) + const traceBytes = traceContainer.encode(trace) + // We use the hash as a reference to the trace. This should be replaced with a proper blob commitment (or versionedHash) // and the data should be stored as a proper Ethereum blob - const hash = bytesToHex(sha256(bytes)) - evm['executionBlobs'].set(hash, bytes) + const hash = bytesToHex(sha256(traceBytes)) + evm['executionBlobs'].set(hash, traceBytes) const addressStr = '0000000000000000000000000000000000000012' const EXECUTE = getActivePrecompiles(common).get(addressStr)! From 7d6506249d4011358e24261737e8ab12db8de33b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:24:36 -0500 Subject: [PATCH 14/25] Fix return value computation --- packages/evm/src/precompiles/12-execute.ts | 6 +++--- packages/evm/test/precompiles/12-execute.spec.ts | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index bfa94ffd2f4..f03e3e0f998 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -118,7 +118,7 @@ export async function precompile12(opts: PrecompileInput): Promise { const stem = stateDiff.stem await tree.put(stem, suffixes, values) } - const executionResult = true + let executionResult = true const stateManager = new StatefulVerkleStateManager({ common: opts.common, trie: tree }) const l2EVM = await createEVM({ stateManager, common: opts.common }) @@ -146,12 +146,12 @@ export async function precompile12(opts: PrecompileInput): Promise { if (computedGasUsed !== executeGasUsed) { opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + executionResult = false } if (!equalsBytes(postStateRoot, tree.root())) { opts._debug?.(`${pName} post state root mismatch`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + executionResult = false } opts._debug?.(`${pName} trace executed successfully=${executionResult}`) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 59dc08d0599..1e13ecb696c 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -50,6 +50,7 @@ describe('Precompiles: EXECUTE', () => { const tree = await createVerkleTree({ verkleCrypto: verkle }) const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) await stateManager.putAccount(address, account) + const preStateRoot = tree.root() const evm = await createEVM({ stateManager, common }) @@ -83,8 +84,8 @@ describe('Precompiles: EXECUTE', () => { } const traceBytes = traceContainer.encode(trace) - // We use the hash as a reference to the trace. This should be replaced with a proper blob commitment (or versionedHash) - // and the data should be stored as a proper Ethereum blob + // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // once we have the trace properly converted to an Ethereum blob. const hash = bytesToHex(sha256(traceBytes)) evm['executionBlobs'].set(hash, traceBytes) From d63fd82acc9172c6a196ee38e11ab11d3fc8be8b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:45:21 -0500 Subject: [PATCH 15/25] add runCall test --- .../evm/test/precompiles/12-execute.spec.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 1e13ecb696c..870456d3811 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -1,6 +1,7 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' import { + Address, bigIntToBytes, bytesToHex, concatBytes, @@ -107,3 +108,106 @@ describe('Precompiles: EXECUTE', () => { assert.equal(result.returnValue[0], 1) }) }) + +describe('runCall', () => { + it.only('should execute runCall', async () => { + const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.Prague, + eips: [6800, 9999], + customCrypto: { + verkle, + }, + }) + // Construct L2 state and transaction + const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) + const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') + const receiver = createAddressFromPrivateKey( + hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), + ) + const l2Tx = { + to: receiver.toBytes(), + from: address.toBytes(), + gasLimit: BigInt('0xffffffffff'), + gasPrice: BigInt('0x1'), + value: BigInt('0x1'), + data: new Uint8Array(), + } + const l2Tree = await createVerkleTree({ verkleCrypto: verkle }) + const l2StateManager = new StatefulVerkleStateManager({ common, trie: l2Tree }) + await l2StateManager.putAccount(address, account) + + const preStateRoot = l2Tree.root() + const l2EVM = await createEVM({ stateManager: l2StateManager, common }) + + l2EVM.verkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + l2EVM.systemVerkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + const res = await l2EVM.runCall({ + to: receiver, + caller: address, + gasLimit: l2Tx.gasLimit, + gasPrice: l2Tx.gasPrice, + value: l2Tx.value, + }) + const executionGasUsed = res.execResult.executionGasUsed + const postStateRoot = l2Tree.root() + const execWitness = await generateExecutionWitness( + l2StateManager, + l2EVM.verkleAccessWitness, + preStateRoot, + ) + + // End of L2 state construction + + // Create mainnet state and EVM + const tree = await createVerkleTree({ verkleCrypto: verkle }) + const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) + const evm = await createEVM({ stateManager, common }) + + evm.verkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + evm.systemVerkleAccessWitness = new VerkleAccessWitness({ + verkleCrypto: verkle, + }) + + // Create a trace + const trace = { + witness: executionWitnessJSONToSSZ(execWitness), + txs: [l2Tx], + } + const traceBytes = traceContainer.encode(trace) + + // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // once we have the trace properly converted to an Ethereum blob. + const hash = bytesToHex(sha256(traceBytes)) + evm['executionBlobs'].set(hash, traceBytes) + + const caller = createAddressFromString('0x0000000000000000000000000000000000001234') + await evm.stateManager.putAccount(caller, createAccount({ balance: 0xffffffffffffffffn })) + const precompileAddrStr = '0x0000000000000000000000000000000000000012' + + const input = concatBytes( + preStateRoot, + postStateRoot, + hexToBytes(hash), + setLengthLeft(bigIntToBytes(executionGasUsed), 32), + ) + + const mainnetTx = { + to: createAddressFromString(precompileAddrStr), + caller, + gasLimit: BigInt('0xffffffffff'), + gasPrice: BigInt('0x1'), + value: BigInt('0x1'), + data: input, + } + + const res2 = await evm.runCall(mainnetTx) + assert.equal(res2.execResult.returnValue[0], 1) + }) +}) From 5db3a51841c9b073bed878c41322dee61384530b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:57:39 -0500 Subject: [PATCH 16/25] simplify account setup --- packages/evm/test/precompiles/12-execute.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 870456d3811..7b72fd98f0f 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -187,8 +187,6 @@ describe('runCall', () => { const hash = bytesToHex(sha256(traceBytes)) evm['executionBlobs'].set(hash, traceBytes) - const caller = createAddressFromString('0x0000000000000000000000000000000000001234') - await evm.stateManager.putAccount(caller, createAccount({ balance: 0xffffffffffffffffn })) const precompileAddrStr = '0x0000000000000000000000000000000000000012' const input = concatBytes( @@ -200,14 +198,10 @@ describe('runCall', () => { const mainnetTx = { to: createAddressFromString(precompileAddrStr), - caller, - gasLimit: BigInt('0xffffffffff'), - gasPrice: BigInt('0x1'), - value: BigInt('0x1'), data: input, } - const res2 = await evm.runCall(mainnetTx) + const res2 = await evm.runCall({ ...mainnetTx, skipBalance: true }) assert.equal(res2.execResult.returnValue[0], 1) }) }) From 11bdca84f55939788401732a75da33cbc6d2cc06 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:49:23 -0500 Subject: [PATCH 17/25] Remove unneeded test data --- .../test/precompiles/executionWitness.json | 99 ------------------- 1 file changed, 99 deletions(-) delete mode 100644 packages/evm/test/precompiles/executionWitness.json diff --git a/packages/evm/test/precompiles/executionWitness.json b/packages/evm/test/precompiles/executionWitness.json deleted file mode 100644 index 78bd108dd65..00000000000 --- a/packages/evm/test/precompiles/executionWitness.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "witness": { - "stateDiff": [ - { - "stem": "0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d", - "suffixDiffs": [ - { - "suffix": 0, - "currentValue": "0x00000000000000000000000000000000000000000000003635c9adc5dea00000", - "newValue": "0x00000000000000000000000000000001000000000000003635c9adc5de9ccbb0" - }, - { - "suffix": 1, - "currentValue": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "newValue": null - } - ] - }, - { - "stem": "0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad7", - "suffixDiffs": [ - { - "suffix": 64, - "newValue": "0x00b2e892fbf04dcdbb33d71633d7cea0722aed27f8a9d0cf9912f97b34f9dadd", - "currentValue": null - } - ] - }, - { - "stem": "0x914ec5f0e0c27fe094862fbd89a6abe684939af6940434d8bf218cedb2d624", - "suffixDiffs": [ - { - "suffix": 0, - "newValue": "0x000000000000000000000000000000000000000000000000000000000000f618", - "currentValue": null - }, - { - "suffix": 1, - "newValue": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "currentValue": null - } - ] - }, - { - "stem": "0xecb505156708480caf702cd85124f67f3ed78ae4bc890a6dcb62574ba9a90c", - "suffixDiffs": [ - { - "suffix": 0, - "currentValue": null, - "newValue": null - }, - { - "suffix": 1, - "currentValue": null, - "newValue": null - } - ] - } - ], - "verkleProof": { - "otherStems": [], - "depthExtensionPresent": "0x0a0a0808", - "commitmentsByPath": [ - "0x360bcc4c15a10f6f058d1d07e13e9a271da3da0cd69026d5a9ab25e1c0a5d697", - "0x324e7b7405bbfae46866bad5c94ac2983093b86f85035fe5984ec40a1b5e5ce3", - "0x61506c1cc4f27c9231a546a4b0d249d5f3ba4ba9e63c5ebb84087665eb396e95", - "0x4909fc1889e10819cda41706a26373145c4e4575d68b96569af12db69040b6e1" - ], - "d": "0x530b7b926f178a1ce3fb92922303111735f9e5395f041b8d2b6089dbf9a73c31", - "ipaProof": { - "cl": [ - "0x3d87ee7a9da30dba329f168f203cff8690921bf51e03edeb439ccd838abf68af", - "0x5ddc74d79bedcd053f53432f873bebceff9acb5e9eb1c55b7b2228c40fab1e9b", - "0x67eacc9c3d3cf158686d0196256d9b858d00aede48fe7d74345628fa43e2f423", - "0x3a4ee7713b54286822dd4250fe7713ff89bd73f5dd25a8850cb95813df63bdf7", - "0x5341f88ef80898f8550f70466b363626c0e5a4749159e24acb4f7b1fade6e1d6", - "0x18d2a1b99473a3a8f94bb9032e2a30e9f303bc37950fc0f67cb62825f42808ac", - "0x56a7d7f239c132fd999991d2de37bdbc88b0edc780b61e132fd5e57986d20edf", - "0x0bab046e2e62e4c51f807df783f9dc3bef37a943c0dfcf01ec0bc9733f144a38" - ], - "cr": [ - "0x27e8b165e814dd45d7c628e1f3dcb31b50b0c1818f87cacf2c041b1e42abe5bd", - "0x71dc9a4412d0ae3ce105f16d772c62cefbe7088673b523496b37bf16c303dffd", - "0x0baed508372a64eca49e8e9cc379b848bedd37d040ac683ccde28debac3793ca", - "0x6b96bfb4b9c044137613dc62be2ab377b31fb93a587742bca109525726799b84", - "0x0b26a2aa70fe810cadf1d7cf47b7823ede1a4129fdc714c7b8bee60e01d6d0e9", - "0x4ae3cd3a9e08425363ca98dce1f264e3cd2fd81a92758a372f412547017dd57e", - "0x44ecde38a0a3e046352cac96b63602a73381308fce1c0900197273fe14ab615d", - "0x28bf63a6e0aa789d798fd116fb29e3820aefff43aaa5226c28985871ee519f81" - ], - "finalEvaluation": "0x00095c56030aeac61ba23e9468ddf059f5b2dac95af6a13e78720c44ca7c4ed7" - } - }, - "parentStateRoot": "0x3901899f4bfd31a5d77611f70c4eb98cffdbbe5280fba656631f04336afe6f5a" - }, - "txs": [ - "0xf861800a8405f5e100948a0a19589531694250d570040a0c4b74576919b8808025a050ae258f0b1f7c44e5227b43c338aa7f2d9805115b90a6baeaaee2358796e074a00ec910ad0244580c17e1d6a512b3574c62e92840184109e3037760d39b20cb94" - ] -} From e2b51956439990f0b636339ed4b90d64bab6afa4 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:33:54 -0500 Subject: [PATCH 18/25] Change execution witness to stateWitness --- packages/evm/src/precompiles/12-execute.ts | 70 +++++++++---------- packages/evm/src/verkleAccessWitness.ts | 43 ++++++++++++ .../evm/test/precompiles/12-execute.spec.ts | 17 +++-- 3 files changed, 85 insertions(+), 45 deletions(-) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index f03e3e0f998..42ce27f0d89 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -20,8 +20,8 @@ import { getPrecompileName } from './index.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' +import type { generateStateWitness } from '../verkleAccessWitness.js' import type { PrecompileInput } from './types.js' -import type { VerkleExecutionWitness } from '@ethereumjs/util' const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas export const traceContainer = ssz.container({ @@ -37,38 +37,42 @@ export const traceContainer = ssz.container({ }), ), witness: ssz.container({ - stateDiff: ssz.list( - 256, + reads: ssz.list( + 1024, ssz.container({ - stem: ssz.bytevector(31), - suffixDiffs: ssz.list( - 256, - ssz.container({ - suffix: ssz.uint64, - currentValue: ssz.bytevector(32), - newValue: ssz.bytevector(32), - }), - ), + key: ssz.bytevector(32), + currentValue: ssz.bytevector(32), + }), + ), + writes: ssz.list( + 1024, + ssz.container({ + key: ssz.bytevector(32), + currentValue: ssz.bytevector(32), + newValue: ssz.bytevector(32), }), ), parentStateRoot: ssz.bytevector(32), }), }) -export const executionWitnessJSONToSSZ = (witness: VerkleExecutionWitness) => { +export const stateWitnessJSONToSSZ = ( + witness: Awaited>, +) => { + const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ + key: hexToBytes(key), + currentValue: hexToBytes(value), + })) + + const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ + key: hexToBytes(key), + currentValue: hexToBytes(value.currentValue), + newValue: hexToBytes(value.newValue), + })) + return { - stateDiff: witness.stateDiff.map((diff) => ({ - stem: hexToBytes(diff.stem), - suffixDiffs: diff.suffixDiffs.map((suffixDiff) => ({ - suffix: BigInt(suffixDiff.suffix), - currentValue: - suffixDiff.currentValue !== null - ? hexToBytes(suffixDiff.currentValue) - : new Uint8Array(32), - newValue: - suffixDiff.newValue !== null ? hexToBytes(suffixDiff.newValue) : new Uint8Array(32), - })), - })), + reads, + writes, parentStateRoot: hexToBytes(witness.parentStateRoot), } } @@ -106,17 +110,11 @@ export async function precompile12(opts: PrecompileInput): Promise { const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) // Populate the L2 state trie with the prestate - for (const stateDiff of witness.stateDiff) { - const suffixes: number[] = [] - const values: Uint8Array[] = [] - for (const diff of stateDiff.suffixDiffs) { - if (diff.currentValue !== null) { - suffixes.push(Number(diff.suffix)) - values.push(diff.currentValue) - } - } - const stem = stateDiff.stem - await tree.put(stem, suffixes, values) + for (const chunk of witness.reads) { + await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + } + for (const chunk of witness.writes) { + await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) } let executionResult = true diff --git a/packages/evm/src/verkleAccessWitness.ts b/packages/evm/src/verkleAccessWitness.ts index 9b666d1315e..b88687c583a 100644 --- a/packages/evm/src/verkleAccessWitness.ts +++ b/packages/evm/src/verkleAccessWitness.ts @@ -440,3 +440,46 @@ export const generateExecutionWitness = async ( } return ew } + +export const generateStateWitness = async ( + stateManager: StatefulVerkleStateManager, + accessWitness: VerkleAccessWitness, + parentStateRoot: Uint8Array, +) => { + const trie = stateManager['_trie'] as VerkleTree + const postStateRoot = await stateManager.getStateRoot() + const stateWitness = { + reads: new Map(), + writes: new Map< + PrefixedHexString, + { currentValue: PrefixedHexString; newValue: PrefixedHexString } + >(), + parentStateRoot: bytesToHex(parentStateRoot), + } + for (const chunk of accessWitness['chunks']) { + const write = chunk[1].write + const stem = chunk[0].slice(0, 64) as PrefixedHexString + const suffix = parseInt(chunk[0].slice(64)) + trie.root(parentStateRoot) + if (write !== undefined && write === true) { + const curVal = await trie.get(hexToBytes(stem), [suffix]) + trie.root(postStateRoot) + const newVal = await trie.get(hexToBytes(stem), [suffix]) + stateWitness.writes.set(chunk[0], { + currentValue: + curVal instanceof Uint8Array ? bytesToHex(curVal) : bytesToHex(new Uint8Array(32)), + newValue: + newVal instanceof Uint8Array ? bytesToHex(newVal) : bytesToHex(new Uint8Array(32)), + }) + } else { + trie.root(parentStateRoot) + const curVal = await trie.get(hexToBytes(stem), [suffix]) + stateWitness.reads.set( + chunk[0], + curVal[0] instanceof Uint8Array ? bytesToHex(curVal[0]) : bytesToHex(new Uint8Array(32)), + ) + } + } + + return stateWitness +} diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 7b72fd98f0f..696eaf84329 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -1,7 +1,6 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common' import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' import { - Address, bigIntToBytes, bytesToHex, concatBytes, @@ -19,10 +18,10 @@ import { assert, describe, it } from 'vitest' import { VerkleAccessWitness, createEVM, - generateExecutionWitness, + generateStateWitness, getActivePrecompiles, } from '../../src/index.js' -import { executionWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.js' +import { stateWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.js' describe('Precompiles: EXECUTE', () => { it('should execute a trace', async () => { @@ -70,7 +69,7 @@ describe('Precompiles: EXECUTE', () => { }) const executionGasUsed = res.execResult.executionGasUsed const postStateRoot = tree.root() - const execWitness = await generateExecutionWitness( + const stateWitness = await generateStateWitness( stateManager, evm.verkleAccessWitness, preStateRoot, @@ -80,7 +79,7 @@ describe('Precompiles: EXECUTE', () => { // Create a trace const trace = { - witness: executionWitnessJSONToSSZ(execWitness), + witness: stateWitnessJSONToSSZ(stateWitness), txs: [tx], } const traceBytes = traceContainer.encode(trace) @@ -110,7 +109,7 @@ describe('Precompiles: EXECUTE', () => { }) describe('runCall', () => { - it.only('should execute runCall', async () => { + it('should execute runCall', async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague, @@ -155,12 +154,11 @@ describe('runCall', () => { }) const executionGasUsed = res.execResult.executionGasUsed const postStateRoot = l2Tree.root() - const execWitness = await generateExecutionWitness( + const stateWitness = await generateStateWitness( l2StateManager, l2EVM.verkleAccessWitness, preStateRoot, ) - // End of L2 state construction // Create mainnet state and EVM @@ -177,9 +175,10 @@ describe('runCall', () => { // Create a trace const trace = { - witness: executionWitnessJSONToSSZ(execWitness), + witness: stateWitnessJSONToSSZ(stateWitness), txs: [l2Tx], } + const traceBytes = traceContainer.encode(trace) // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use From deb9aa72567c94fdae9fd780e7c38c507ed6319a Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:18:37 -0500 Subject: [PATCH 19/25] Update comments --- packages/evm/src/precompiles/12-execute.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index 42ce27f0d89..6a7d8be80fa 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -26,6 +26,7 @@ import type { PrecompileInput } from './types.js' const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas export const traceContainer = ssz.container({ txs: ssz.list( + // An ssz list of tx objects that match the `eth_call` tx object format 256, ssz.container({ to: ssz.bytevector(20), @@ -37,15 +38,16 @@ export const traceContainer = ssz.container({ }), ), witness: ssz.container({ + // A state witness that contains all the reads and writes to the state that are part of the tx list reads: ssz.list( - 1024, + 1024, // arbitrary limit for now ssz.container({ key: ssz.bytevector(32), currentValue: ssz.bytevector(32), }), ), writes: ssz.list( - 1024, + 1024, // arbitrary limit for now ssz.container({ key: ssz.bytevector(32), currentValue: ssz.bytevector(32), From f498932a4c59ccd9acf2abf08ccd68ea86c85728 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:57:21 -0500 Subject: [PATCH 20/25] update test title --- packages/evm/test/precompiles/12-execute.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 696eaf84329..a55b81b1d30 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -109,7 +109,7 @@ describe('Precompiles: EXECUTE', () => { }) describe('runCall', () => { - it('should execute runCall', async () => { + it('should execute runCall successfully', async () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague, From 08f21c7f29a27f701f41f83aaca917428dd9b70e Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 4 Mar 2025 16:01:40 -0500 Subject: [PATCH 21/25] Add binary access witness processing to evm and vm --- packages/evm/src/binaryTreeAccessWitness.ts | 24 +- packages/evm/src/evm.ts | 22 +- packages/evm/src/index.ts | 1 + packages/evm/src/interpreter.ts | 10 +- packages/evm/src/message.ts | 9 +- packages/evm/src/opcodes/EIP2929.ts | 11 +- packages/evm/src/opcodes/functions.ts | 7 +- packages/evm/src/opcodes/gas.ts | 36 +-- packages/evm/src/precompiles/12-execute.ts | 261 +++++++++----------- packages/evm/src/types.ts | 2 +- packages/vm/src/runBlock.ts | 36 ++- packages/vm/src/runTx.ts | 36 ++- 12 files changed, 257 insertions(+), 198 deletions(-) diff --git a/packages/evm/src/binaryTreeAccessWitness.ts b/packages/evm/src/binaryTreeAccessWitness.ts index 223ea8bc86e..c87d9c7f9d3 100644 --- a/packages/evm/src/binaryTreeAccessWitness.ts +++ b/packages/evm/src/binaryTreeAccessWitness.ts @@ -43,14 +43,14 @@ const WitnessChunkWriteCost = BigInt(500) const WitnessChunkFillCost = BigInt(6200) // read is a default access event if stem or chunk is present -export type StemAccessEvent = { write?: boolean } +export type BinaryStemAccessEvent = { write?: boolean } // chunk fill access event was not charged in verkle testnets, not sure if it will be charged in binary -export type ChunkAccessEvent = StemAccessEvent & { fill?: boolean } +export type BinaryChunkAccessEvent = BinaryStemAccessEvent & { fill?: boolean } // Since stem is hashed, it is useful to maintain the reverse relationship -export type StemMeta = { address: Address; treeIndex: number | bigint } +export type BinaryStemMeta = { address: Address; treeIndex: number | bigint } -export function decodeAccessedState( +export function decodeBinaryAccessState( treeIndex: number | bigint, chunkIndex: number, ): BinaryTreeAccessedState { @@ -89,19 +89,19 @@ export function decodeAccessedState( } export class BinaryTreeAccessWitness implements BinaryTreeAccessWitnessInterface { - stems: Map - chunks: Map + stems: Map + chunks: Map stemCache: StemCache = new StemCache() chunkCache: ChunkCache = new ChunkCache() hashFunction: (msg: Uint8Array) => Uint8Array constructor(opts: { hashFunction: (msg: Uint8Array) => Uint8Array - stems?: Map - chunks?: Map + stems?: Map + chunks?: Map }) { this.hashFunction = opts.hashFunction - this.stems = opts.stems ?? new Map() - this.chunks = opts.chunks ?? new Map() + this.stems = opts.stems ?? new Map() + this.chunks = opts.chunks ?? new Map() } readAccountBasicData(address: Address): bigint { @@ -374,7 +374,7 @@ export class BinaryTreeAccessWitness implements BinaryTreeAccessWitnessInterface *accesses(): Generator { for (const rawAccess of this.rawAccesses()) { const { address, treeIndex, chunkIndex, chunkKey } = rawAccess - const accessedState = decodeAccessedState(treeIndex, chunkIndex) + const accessedState = decodeBinaryAccessState(treeIndex, chunkIndex) yield { ...accessedState, address, chunkKey } } } @@ -389,7 +389,7 @@ export class BinaryTreeAccessWitness implements BinaryTreeAccessWitnessInterface * * Note: This does not provide the verkle proof, which is not implemented */ -export const generateExecutionWitness = async ( +export const generateBinaryExecutionWitness = async ( stateManager: StatefulBinaryTreeStateManager, accessWitness: BinaryTreeAccessWitness, parentStateRoot: Uint8Array, diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 30a0db3f055..badeeb2d000 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -47,6 +47,7 @@ import { type ExecResult, } from './types.js' +import type { BinaryTreeAccessWitness } from './binaryTreeAccessWitness.js' import type { InterpreterOpts } from './interpreter.js' import type { Timer } from './logger.js' import type { MessageWithTo } from './message.js' @@ -105,6 +106,8 @@ export class EVM implements EVMInterface { public journal: Journal public verkleAccessWitness?: VerkleAccessWitness public systemVerkleAccessWitness?: VerkleAccessWitness + public binaryAccessWitness?: BinaryTreeAccessWitness + public systemBinaryAccessWitness?: BinaryTreeAccessWitness public readonly transientStorage: TransientStorage @@ -170,7 +173,7 @@ export class EVM implements EVMInterface { this.blockchain = opts.blockchain! this.stateManager = opts.stateManager! - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { const mandatory = ['checkChunkWitnessPresent'] for (const m of mandatory) { if (!(m in this.stateManager)) { @@ -270,7 +273,7 @@ export class EVM implements EVMInterface { let gasLimit = message.gasLimit const fromAddress = message.caller - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { if (message.accessWitness === undefined) { throw EthereumJSErrorWithoutCode('accessWitness is required for EIP-6800') } @@ -325,7 +328,7 @@ export class EVM implements EVMInterface { // Load `to` account let toAccount = await this.stateManager.getAccount(message.to) if (!toAccount) { - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { const absenceProofAccessGas = message.accessWitness!.readAccountHeader(message.to) gasLimit -= absenceProofAccessGas if (gasLimit < BIGINT_0) { @@ -425,7 +428,7 @@ export class EVM implements EVMInterface { let gasLimit = message.gasLimit const fromAddress = message.caller - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { if (message.depth === 0) { const originAccessGas = message.accessWitness!.readAccountHeader(fromAddress) debugGas(`originAccessGas=${originAccessGas} waived off for origin at depth=0`) @@ -472,7 +475,7 @@ export class EVM implements EVMInterface { toAccount = new Account() } - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { const contractCreateAccessGas = message.accessWitness!.writeAccountBasicData(message.to) + message.accessWitness!.readAccountCodeHash(message.to) @@ -558,7 +561,7 @@ export class EVM implements EVMInterface { } if (exit) { - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { const createCompleteAccessGas = message.accessWitness!.writeAccountHeader(message.to) gasLimit -= createCompleteAccessGas if (gasLimit < BIGINT_0) { @@ -678,7 +681,10 @@ export class EVM implements EVMInterface { // get the fresh gas limit for the rest of the ops gasLimit = message.gasLimit - result.executionGasUsed - if (!result.exceptionError && this.common.isActivatedEIP(6800)) { + if ( + !result.exceptionError && + (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) + ) { const createCompleteAccessGas = message.accessWitness!.writeAccountHeader(message.to) gasLimit -= createCompleteAccessGas if (gasLimit < BIGINT_0) { @@ -704,7 +710,7 @@ export class EVM implements EVMInterface { result.returnValue.length !== 0 ) { // Add access charges for writing this code to the state - if (this.common.isActivatedEIP(6800)) { + if (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) { const byteCodeWriteAccessfee = message.accessWitness!.writeAccountCodeChunks( message.to, 0, diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 17b280c15f5..2d26568c796 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -59,6 +59,7 @@ export { validateEOF, } +export * from './binaryTreeAccessWitness.js' 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 f8802dfa019..654de3cc081 100644 --- a/packages/evm/src/interpreter.ts +++ b/packages/evm/src/interpreter.ts @@ -37,6 +37,7 @@ import type { Log, } from './types.js' import type { + BinaryTreeAccessWitnessInterface, Common, StateManagerInterface, VerkleAccessWitnessInterface, @@ -84,7 +85,7 @@ export interface Env { eof?: EOFEnv /* Optional EOF environment in case of EOF execution */ blobVersionedHashes: PrefixedHexString[] /** Versioned hashes for blob transactions */ createdAddresses?: Set - accessWitness?: VerkleAccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface chargeCodeAccesses?: boolean } @@ -305,7 +306,7 @@ export class Interpreter { // chunk in the witness, and throw appropriate error to distinguish from an actual invalid opcode if ( opCode === 0xfe && - this.common.isActivatedEIP(6800) && + (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) && // is this a code loaded from state using witnesses this._runState.env.chargeCodeAccesses === true ) { @@ -386,7 +387,10 @@ export class Interpreter { await this._runStepHook(gas, this.getGasLeft()) } - if (this.common.isActivatedEIP(6800) && this._env.chargeCodeAccesses === true) { + if ( + (this.common.isActivatedEIP(6800) || this.common.isActivatedEIP(7864)) && + this._env.chargeCodeAccesses === true + ) { const contract = this._runState.interpreter.getAddress() const statelessGas = this._runState.env.accessWitness!.readAccountCodeChunks( contract, diff --git a/packages/evm/src/message.ts b/packages/evm/src/message.ts index 31ca1375994..ec0b767d89d 100644 --- a/packages/evm/src/message.ts +++ b/packages/evm/src/message.ts @@ -2,7 +2,10 @@ import { BIGINT_0, EthereumJSErrorWithoutCode, createZeroAddress } from '@ethere import type { PrecompileFunc } from './precompiles/index.js' import type { EOFEnv } from './types.js' -import type { VerkleAccessWitnessInterface } from '@ethereumjs/common' +import type { + BinaryTreeAccessWitnessInterface, + VerkleAccessWitnessInterface, +} from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' const defaults = { @@ -40,7 +43,7 @@ interface MessageOpts { delegatecall?: boolean gasRefund?: bigint blobVersionedHashes?: PrefixedHexString[] - accessWitness?: VerkleAccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface } export class Message { @@ -73,7 +76,7 @@ export class Message { * List of versioned hashes if message is a blob transaction in the outer VM */ blobVersionedHashes?: PrefixedHexString[] - accessWitness?: VerkleAccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface constructor(opts: MessageOpts) { this.to = opts.to diff --git a/packages/evm/src/opcodes/EIP2929.ts b/packages/evm/src/opcodes/EIP2929.ts index bbfe3820328..61ffaf09b41 100644 --- a/packages/evm/src/opcodes/EIP2929.ts +++ b/packages/evm/src/opcodes/EIP2929.ts @@ -29,9 +29,9 @@ export function accessAddressEIP2929( // CREATE, CREATE2 opcodes have the address warmed for free. // selfdestruct beneficiary address reads are charged an *additional* cold access // if verkle not activated - if (chargeGas && !common.isActivatedEIP(6800)) { + if (chargeGas && (!common.isActivatedEIP(6800) || !common.isActivatedEIP(7864))) { return common.param('coldaccountaccessGas') - } else if (chargeGas && common.isActivatedEIP(6800)) { + } else if (chargeGas && (common.isActivatedEIP(6800) || common.isActivatedEIP(7864))) { // If Verkle is active, then the warmstoragereadGas should still be charged // This is because otherwise opcodes will have cost 0 (this is thus the base fee) return common.param('warmstoragereadGas') @@ -66,10 +66,13 @@ export function accessStorageEIP2929( // Cold (SLOAD and SSTORE) if (slotIsCold) { runState.interpreter.journal.addWarmedStorage(address, key) - if (chargeGas && !common.isActivatedEIP(6800)) { + if (chargeGas && (!common.isActivatedEIP(6800) || !common.isActivatedEIP(7864))) { return common.param('coldsloadGas') } - } else if (chargeGas && (!isSstore || common.isActivatedEIP(6800))) { + } else if ( + chargeGas && + (!isSstore || common.isActivatedEIP(6800) || common.isActivatedEIP(7864)) + ) { return common.param('warmstoragereadGas') } return BIGINT_0 diff --git a/packages/evm/src/opcodes/functions.ts b/packages/evm/src/opcodes/functions.ts index 544c081aa1b..e68830d87fd 100644 --- a/packages/evm/src/opcodes/functions.ts +++ b/packages/evm/src/opcodes/functions.ts @@ -633,7 +633,7 @@ export const handlers: Map = new Map([ const historyServeWindow = common.param('historyServeWindow') const key = setLengthLeft(bigIntToBytes(number % historyServeWindow), 32) - if (common.isActivatedEIP(6800)) { + if (common.isActivatedEIP(6800) || common.isActivatedEIP(7864)) { // create witnesses and charge gas const statelessGas = runState.env.accessWitness!.readAccountStorage( historyAddress, @@ -920,7 +920,10 @@ export const handlers: Map = new Map([ function (runState, common) { const numToPush = runState.opCode - 0x5f - if (common.isActivatedEIP(6800) && runState.env.chargeCodeAccesses === true) { + if ( + (common.isActivatedEIP(6800) || common.isActivatedEIP(7864)) && + runState.env.chargeCodeAccesses === true + ) { 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) diff --git a/packages/evm/src/opcodes/gas.ts b/packages/evm/src/opcodes/gas.ts index 48bcd14d2e2..d3f5b113560 100644 --- a/packages/evm/src/opcodes/gas.ts +++ b/packages/evm/src/opcodes/gas.ts @@ -102,7 +102,7 @@ export const dynamicGasHandlers: Map { const address = createAddressFromStackBigInt(runState.stack.peek()[0]) let charge2929Gas = true - if (common.isActivatedEIP(6800)) { + if (common.isActivatedEIP(6800) || common.isActivatedEIP(7864)) { const coldAccessGas = runState.env.accessWitness!.readAccountBasicData(address) gas += coldAccessGas @@ -139,7 +139,10 @@ export const dynamicGasHandlers: Map codeSize) { @@ -261,7 +264,7 @@ export const dynamicGasHandlers: Map BIGINT_0) { gas += runState.env.accessWitness!.writeAccountBasicData(contractAddress) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index 6a7d8be80fa..2a041ea7677 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -9,157 +9,140 @@ import { import { createVerkleTree } from '@ethereumjs/verkle' import * as ssz from 'micro-eth-signer/ssz' +import { BinaryTreeAccessWitness } from '../binaryTreeAccessWitness.js' import { createEVM } from '../constructors.js' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' -import { VerkleAccessWitness } from '../verkleAccessWitness.js' import { gasLimitCheck } from './util.js' import { getPrecompileName } from './index.js' +// import type { generateStateWitness } from '../binaryTreeAccessWitness.js' import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' -import type { generateStateWitness } from '../verkleAccessWitness.js' import type { PrecompileInput } from './types.js' -const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas -export const traceContainer = ssz.container({ - txs: ssz.list( - // An ssz list of tx objects that match the `eth_call` tx object format - 256, - ssz.container({ - to: ssz.bytevector(20), - from: ssz.bytevector(20), - gasLimit: ssz.uint64, - gasPrice: ssz.uint64, - value: ssz.uint64, - data: ssz.bytelist(MAX_CALL_DATA_SIZE), - }), - ), - witness: ssz.container({ - // A state witness that contains all the reads and writes to the state that are part of the tx list - reads: ssz.list( - 1024, // arbitrary limit for now - ssz.container({ - key: ssz.bytevector(32), - currentValue: ssz.bytevector(32), - }), - ), - writes: ssz.list( - 1024, // arbitrary limit for now - ssz.container({ - key: ssz.bytevector(32), - currentValue: ssz.bytevector(32), - newValue: ssz.bytevector(32), - }), - ), - parentStateRoot: ssz.bytevector(32), - }), -}) - -export const stateWitnessJSONToSSZ = ( - witness: Awaited>, -) => { - const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ - key: hexToBytes(key), - currentValue: hexToBytes(value), - })) - - const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ - key: hexToBytes(key), - currentValue: hexToBytes(value.currentValue), - newValue: hexToBytes(value.newValue), - })) - - return { - reads, - writes, - parentStateRoot: hexToBytes(witness.parentStateRoot), - } -} +// const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas +// export const traceContainer = ssz.container({ +// txs: ssz.list( +// // An ssz list of tx objects that match the `eth_call` tx object format +// 256, +// ssz.container({ +// to: ssz.bytevector(20), +// from: ssz.bytevector(20), +// gasLimit: ssz.uint64, +// gasPrice: ssz.uint64, +// value: ssz.uint64, +// data: ssz.bytelist(MAX_CALL_DATA_SIZE), +// }), +// ), +// witness: ssz.container({ +// // A state witness that contains all the reads and writes to the state that are part of the tx list +// reads: ssz.list( +// 1024, // arbitrary limit for now +// ssz.container({ +// key: ssz.bytevector(32), +// currentValue: ssz.bytevector(32), +// }), +// ), +// writes: ssz.list( +// 1024, // arbitrary limit for now +// ssz.container({ +// key: ssz.bytevector(32), +// currentValue: ssz.bytevector(32), +// newValue: ssz.bytevector(32), +// }), +// ), +// parentStateRoot: ssz.bytevector(32), +// }), +// }) + +// export const stateWitnessJSONToSSZ = ( +// witness: Awaited>, +// ) => { +// const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ +// key: hexToBytes(key), +// currentValue: hexToBytes(value), +// })) + +// const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ +// key: hexToBytes(key), +// currentValue: hexToBytes(value.currentValue), +// newValue: hexToBytes(value.newValue), +// })) + +// return { +// reads, +// writes, +// parentStateRoot: hexToBytes(witness.parentStateRoot), +// } +// } export async function precompile12(opts: PrecompileInput): Promise { - const pName = getPrecompileName('12') - const data = opts.data - const evm = opts._EVM as EVM - const gasUsed = opts.common.param('executeGasCost') - if (!gasLimitCheck(opts, gasUsed, pName)) { - return OOGResult(opts.gasLimit) - } - - if (data.length !== 128) { - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) - } - - const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state - const postStateRoot = data.subarray(32, 64) // post state root for L2 state - const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions - if (traceBlob === undefined) { - opts._debug?.(`${pName} error - trace not found`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) - } - - const decodedTrace = traceContainer.decode(traceBlob) - - if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { - opts._debug?.(`${pName} error - trace is invalid`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) - } - const executeGasUsed = bytesToBigInt(data.subarray(96)) - - const witness = decodedTrace.witness - const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) - - // Populate the L2 state trie with the prestate - for (const chunk of witness.reads) { - await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) - } - for (const chunk of witness.writes) { - await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) - } - let executionResult = true - - const stateManager = new StatefulVerkleStateManager({ common: opts.common, trie: tree }) - const l2EVM = await createEVM({ stateManager, common: opts.common }) - - l2EVM.verkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: opts.common.customCrypto.verkle!, - }) - l2EVM.systemVerkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: opts.common.customCrypto.verkle!, - }) - let computedGasUsed = 0n - - // Run each transaction in the trace - for (const tx of decodedTrace.txs) { - const res = await l2EVM.runCall({ - to: createAddressFromString(bytesToHex(tx.to)), - caller: createAddressFromString(bytesToHex(tx.from)), - gasLimit: BigInt(tx.gasLimit), - gasPrice: BigInt(tx.gasPrice), - value: BigInt(tx.value), - data: tx.data !== undefined ? tx.data : undefined, - }) - computedGasUsed += res.execResult.executionGasUsed - } - - if (computedGasUsed !== executeGasUsed) { - opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) - executionResult = false - } - - if (!equalsBytes(postStateRoot, tree.root())) { - opts._debug?.(`${pName} post state root mismatch`) - executionResult = false - } - - opts._debug?.(`${pName} trace executed successfully=${executionResult}`) - - const returnValue = executionResult ? new Uint8Array(1).fill(1) : new Uint8Array(1).fill(0) - - return { - executionGasUsed: gasUsed, - returnValue, - } + // const pName = getPrecompileName('12') + // const data = opts.data + // const evm = opts._EVM as EVM + // const gasUsed = opts.common.param('executeGasCost') + // if (!gasLimitCheck(opts, gasUsed, pName)) { + // return OOGResult(opts.gasLimit) + // } + // if (data.length !== 128) { + // return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + // } + // const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state + // const postStateRoot = data.subarray(32, 64) // post state root for L2 state + // const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions + // if (traceBlob === undefined) { + // opts._debug?.(`${pName} error - trace not found`) + // return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + // } + // const decodedTrace = traceContainer.decode(traceBlob) + // if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { + // opts._debug?.(`${pName} error - trace is invalid`) + // return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + // } + // const executeGasUsed = bytesToBigInt(data.subarray(96)) + // const witness = decodedTrace.witness + // const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) + // // Populate the L2 state trie with the prestate + // for (const chunk of witness.reads) { + // await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + // } + // for (const chunk of witness.writes) { + // await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + // } + // let executionResult = true + // const stateManager = new StatefulVerkleStateManager({ common: opts.common, trie: tree }) + // const l2EVM = await createEVM({ stateManager, common: opts.common }) + // l2EVM.binaryTreeAccessWitness = new BinaryTreeAccessWitness({}) + // l2EVM.binaryTreeAccessWitness = new BinaryTreeAccessWitness({}) + // let computedGasUsed = 0n + // // Run each transaction in the trace + // for (const tx of decodedTrace.txs) { + // const res = await l2EVM.runCall({ + // to: createAddressFromString(bytesToHex(tx.to)), + // caller: createAddressFromString(bytesToHex(tx.from)), + // gasLimit: BigInt(tx.gasLimit), + // gasPrice: BigInt(tx.gasPrice), + // value: BigInt(tx.value), + // data: tx.data !== undefined ? tx.data : undefined, + // }) + // computedGasUsed += res.execResult.executionGasUsed + // } + // if (computedGasUsed !== executeGasUsed) { + // opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) + // executionResult = false + // } + // if (!equalsBytes(postStateRoot, tree.root())) { + // opts._debug?.(`${pName} post state root mismatch`) + // executionResult = false + // } + // opts._debug?.(`${pName} trace executed successfully=${executionResult}`) + // const returnValue = executionResult ? new Uint8Array(1).fill(1) : new Uint8Array(1).fill(0) + // return { + // executionGasUsed: gasUsed, + // returnValue, + // } + throw new Error('Not implemented') } diff --git a/packages/evm/src/types.ts b/packages/evm/src/types.ts index 09b553ef176..f1b15703751 100644 --- a/packages/evm/src/types.ts +++ b/packages/evm/src/types.ts @@ -129,7 +129,7 @@ export interface EVMRunCallOpts extends EVMRunOpts { */ message?: Message - accessWitness?: VerkleAccessWitnessInterface + accessWitness?: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface } interface NewContractEvent { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 69c86b89ab2..ed8608de41f 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -134,7 +134,7 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise bytesToHex(tx.serialize()))) // Checkpoint state await vm.evm.journal.checkpoint() if (vm.DEBUG) { @@ -374,8 +373,6 @@ export async function runBlock(vm: VM, opts: RunBlockOpts): Promise { async function _runTx(vm: VM, opts: RunTxOpts): Promise { const state = vm.stateManager - let stateAccesses: VerkleAccessWitnessInterface | undefined - let txAccesses: VerkleAccessWitnessInterface | undefined + let stateAccesses: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface | undefined + let txAccesses: VerkleAccessWitnessInterface | BinaryTreeAccessWitnessInterface | undefined if (vm.common.isActivatedEIP(6800)) { if (vm.evm.verkleAccessWitness === undefined) { throw Error(`Verkle access witness needed for execution of verkle blocks`) @@ -210,6 +218,20 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { } stateAccesses = vm.evm.verkleAccessWitness txAccesses = new VerkleAccessWitness({ verkleCrypto: vm.stateManager.verkleCrypto }) + } else if (vm.common.isActivatedEIP(7864)) { + if (vm.evm.binaryTreeAccessWitness === undefined) { + throw Error(`Binary tree access witness needed for execution of binary tree blocks`) + } + + if (!(vm.stateManager instanceof StatefulBinaryTreeStateManager)) { + throw EthereumJSErrorWithoutCode( + `Binary tree State Manager needed for execution of binary tree blocks`, + ) + } + stateAccesses = vm.evm.binaryTreeAccessWitness + txAccesses = new BinaryTreeAccessWitness({ + hashFunction: (vm.evm.binaryTreeAccessWitness as BinaryTreeAccessWitness).hashFunction, + }) } const { tx, block } = opts @@ -588,7 +610,9 @@ async function _runTx(vm: VM, opts: RunTxOpts): Promise { })) as RunTxResult if (vm.common.isActivatedEIP(6800)) { - stateAccesses?.merge(txAccesses!) + stateAccesses?.merge( + txAccesses! as VerkleAccessWitnessInterface & BinaryTreeAccessWitnessInterface, + ) } if (enableProfiler) { From 8ed78ca3ef90acedec38dba7a1c53c2c8402c022 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:57:35 -0500 Subject: [PATCH 22/25] convert EXECUTE to use binaryTrees --- config/cspell-ts.json | 1 + packages/evm/src/binaryTreeAccessWitness.ts | 4 +- packages/evm/src/precompiles/12-execute.ts | 227 +++++++------- .../evm/test/precompiles/12-execute.spec.ts | 277 +++++++++--------- 4 files changed, 248 insertions(+), 261 deletions(-) diff --git a/config/cspell-ts.json b/config/cspell-ts.json index f77135582ab..31672e0bb49 100644 --- a/config/cspell-ts.json +++ b/config/cspell-ts.json @@ -24,6 +24,7 @@ } ], "words": [ + "prestateroot", "bytelist", "bytestring", "binarytree", diff --git a/packages/evm/src/binaryTreeAccessWitness.ts b/packages/evm/src/binaryTreeAccessWitness.ts index 5f55beecef6..04e0c283a61 100644 --- a/packages/evm/src/binaryTreeAccessWitness.ts +++ b/packages/evm/src/binaryTreeAccessWitness.ts @@ -397,7 +397,7 @@ export const generateBinaryExecutionWitness = async ( const ew: BinaryTreeExecutionWitness = { stateDiff: [], parentStateRoot: bytesToHex(parentStateRoot), - proof: undefined as any, // Binary proofs are not implemented + proof: undefined as any, } // Generate a map of all stems with their accessed suffixes @@ -439,6 +439,8 @@ export const generateBinaryExecutionWitness = async ( }) } ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) + const proof = await tree.createBinaryProof(hexToBytes(stem)) + ew.proof[stem] = proof } tree['_lock'].release() return ew diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index 2a041ea7677..cf6549bf6d8 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -1,4 +1,5 @@ -import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' +import { createBinaryTree } from '@ethereumjs/binarytree' +import { StatefulBinaryTreeStateManager } from '@ethereumjs/statemanager' import { bytesToBigInt, bytesToHex, @@ -6,7 +7,6 @@ import { equalsBytes, hexToBytes, } from '@ethereumjs/util' -import { createVerkleTree } from '@ethereumjs/verkle' import * as ssz from 'micro-eth-signer/ssz' import { BinaryTreeAccessWitness } from '../binaryTreeAccessWitness.js' @@ -23,126 +23,115 @@ import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' -// const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas -// export const traceContainer = ssz.container({ -// txs: ssz.list( -// // An ssz list of tx objects that match the `eth_call` tx object format -// 256, -// ssz.container({ -// to: ssz.bytevector(20), -// from: ssz.bytevector(20), -// gasLimit: ssz.uint64, -// gasPrice: ssz.uint64, -// value: ssz.uint64, -// data: ssz.bytelist(MAX_CALL_DATA_SIZE), -// }), -// ), -// witness: ssz.container({ -// // A state witness that contains all the reads and writes to the state that are part of the tx list -// reads: ssz.list( -// 1024, // arbitrary limit for now -// ssz.container({ -// key: ssz.bytevector(32), -// currentValue: ssz.bytevector(32), -// }), -// ), -// writes: ssz.list( -// 1024, // arbitrary limit for now -// ssz.container({ -// key: ssz.bytevector(32), -// currentValue: ssz.bytevector(32), -// newValue: ssz.bytevector(32), -// }), -// ), -// parentStateRoot: ssz.bytevector(32), -// }), -// }) +const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas +export const traceContainer = ssz.container({ + txs: ssz.list( + // An ssz list of tx objects that match the `eth_call` tx object format + 256, + ssz.container({ + to: ssz.bytevector(20), + from: ssz.bytevector(20), + gasLimit: ssz.uint64, + gasPrice: ssz.uint64, + value: ssz.uint64, + data: ssz.bytelist(MAX_CALL_DATA_SIZE), + }), + ), + witnesses: ssz.container({ + // A state witness that contains all the reads and writes to the state that are part of the tx list + proofs: ssz.list(1024, ssz.list(248, ssz.bytevector(32))), // + parentStateRoot: ssz.bytevector(32), + }), +}) -// export const stateWitnessJSONToSSZ = ( -// witness: Awaited>, -// ) => { -// const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ -// key: hexToBytes(key), -// currentValue: hexToBytes(value), -// })) +export const stateWitnessJSONToSSZ = ( + witness: Awaited>, +) => { + const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ + key: hexToBytes(key), + currentValue: hexToBytes(value), + })) -// const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ -// key: hexToBytes(key), -// currentValue: hexToBytes(value.currentValue), -// newValue: hexToBytes(value.newValue), -// })) + const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ + key: hexToBytes(key), + currentValue: hexToBytes(value.currentValue), + newValue: hexToBytes(value.newValue), + })) -// return { -// reads, -// writes, -// parentStateRoot: hexToBytes(witness.parentStateRoot), -// } -// } + return { + reads, + writes, + parentStateRoot: hexToBytes(witness.parentStateRoot), + } +} export async function precompile12(opts: PrecompileInput): Promise { - // const pName = getPrecompileName('12') - // const data = opts.data - // const evm = opts._EVM as EVM - // const gasUsed = opts.common.param('executeGasCost') - // if (!gasLimitCheck(opts, gasUsed, pName)) { - // return OOGResult(opts.gasLimit) - // } - // if (data.length !== 128) { - // return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) - // } - // const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state - // const postStateRoot = data.subarray(32, 64) // post state root for L2 state - // const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions - // if (traceBlob === undefined) { - // opts._debug?.(`${pName} error - trace not found`) - // return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) - // } - // const decodedTrace = traceContainer.decode(traceBlob) - // if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { - // opts._debug?.(`${pName} error - trace is invalid`) - // return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) - // } - // const executeGasUsed = bytesToBigInt(data.subarray(96)) - // const witness = decodedTrace.witness - // const tree = await createVerkleTree({ verkleCrypto: opts.common.customCrypto.verkle }) - // // Populate the L2 state trie with the prestate - // for (const chunk of witness.reads) { - // await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) - // } - // for (const chunk of witness.writes) { - // await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) - // } - // let executionResult = true - // const stateManager = new StatefulVerkleStateManager({ common: opts.common, trie: tree }) - // const l2EVM = await createEVM({ stateManager, common: opts.common }) - // l2EVM.binaryTreeAccessWitness = new BinaryTreeAccessWitness({}) - // l2EVM.binaryTreeAccessWitness = new BinaryTreeAccessWitness({}) - // let computedGasUsed = 0n - // // Run each transaction in the trace - // for (const tx of decodedTrace.txs) { - // const res = await l2EVM.runCall({ - // to: createAddressFromString(bytesToHex(tx.to)), - // caller: createAddressFromString(bytesToHex(tx.from)), - // gasLimit: BigInt(tx.gasLimit), - // gasPrice: BigInt(tx.gasPrice), - // value: BigInt(tx.value), - // data: tx.data !== undefined ? tx.data : undefined, - // }) - // computedGasUsed += res.execResult.executionGasUsed - // } - // if (computedGasUsed !== executeGasUsed) { - // opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) - // executionResult = false - // } - // if (!equalsBytes(postStateRoot, tree.root())) { - // opts._debug?.(`${pName} post state root mismatch`) - // executionResult = false - // } - // opts._debug?.(`${pName} trace executed successfully=${executionResult}`) - // const returnValue = executionResult ? new Uint8Array(1).fill(1) : new Uint8Array(1).fill(0) - // return { - // executionGasUsed: gasUsed, - // returnValue, - // } - throw new Error('Not implemented') + const pName = getPrecompileName('12') + const data = opts.data + const evm = opts._EVM as EVM + const gasUsed = opts.common.param('executeGasCost') + if (!gasLimitCheck(opts, gasUsed, pName)) { + return OOGResult(opts.gasLimit) + } + if (data.length !== 128) { + return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + } + const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state + const postStateRoot = data.subarray(32, 64) // post state root for L2 state + const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions + if (traceBlob === undefined) { + opts._debug?.(`${pName} error - trace not found`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } + const decodedTrace = traceContainer.decode(traceBlob) + if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { + opts._debug?.(`${pName} error - trace is invalid`) + return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + } + const executeGasUsed = bytesToBigInt(data.subarray(96)) + const witness = decodedTrace.witness + const tree = await createBinaryTree() + // Populate the L2 state trie with the prestate + for (const chunk of witness.reads) { + await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + } + for (const chunk of witness.writes) { + await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + } + let executionResult = true + const stateManager = new StatefulBinaryTreeStateManager({ common: opts.common, tree }) + const l2EVM = await createEVM({ stateManager, common: opts.common }) + l2EVM.binaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, + }) + l2EVM.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, + }) + let computedGasUsed = 0n + // Run each transaction in the trace + for (const tx of decodedTrace.txs) { + const res = await l2EVM.runCall({ + to: createAddressFromString(bytesToHex(tx.to)), + caller: createAddressFromString(bytesToHex(tx.from)), + gasLimit: BigInt(tx.gasLimit), + gasPrice: BigInt(tx.gasPrice), + value: BigInt(tx.value), + data: tx.data !== undefined ? tx.data : undefined, + }) + computedGasUsed += res.execResult.executionGasUsed + } + if (computedGasUsed !== executeGasUsed) { + opts._debug?.(`${pName} gas used mismatch: ${computedGasUsed} !== ${executeGasUsed}`) + executionResult = false + } + if (!equalsBytes(postStateRoot, tree.root())) { + opts._debug?.(`${pName} post state root mismatch`) + executionResult = false + } + opts._debug?.(`${pName} trace executed successfully=${executionResult}`) + const returnValue = executionResult ? new Uint8Array(1).fill(1) : new Uint8Array(1).fill(0) + return { + executionGasUsed: gasUsed, + returnValue, + } } diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index a55b81b1d30..41f09fe35f3 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -1,5 +1,6 @@ +import { createBinaryTree } from '@ethereumjs/binarytree' import { Common, Hardfork, Mainnet } from '@ethereumjs/common' -import { StatefulVerkleStateManager } from '@ethereumjs/statemanager' +import { StatefulBinaryTreeStateManager } from '@ethereumjs/statemanager' import { bigIntToBytes, bytesToHex, @@ -10,15 +11,13 @@ import { hexToBytes, setLengthLeft, } from '@ethereumjs/util' -import { createVerkleTree } from '@ethereumjs/verkle' import { sha256 } from 'ethereum-cryptography/sha256' -import * as verkle from 'micro-eth-signer/verkle' import { assert, describe, it } from 'vitest' import { - VerkleAccessWitness, + BinaryTreeAccessWitness, createEVM, - generateStateWitness, + generateBinaryExecutionWitness, getActivePrecompiles, } from '../../src/index.js' import { stateWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.js' @@ -28,10 +27,7 @@ describe('Precompiles: EXECUTE', () => { const common = new Common({ chain: Mainnet, hardfork: Hardfork.Prague, - eips: [6800, 9999], - customCrypto: { - verkle, - }, + eips: [7864, 9999], }) // Construct L2 state and transaction const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) @@ -47,18 +43,18 @@ describe('Precompiles: EXECUTE', () => { value: BigInt('0x1'), data: new Uint8Array(), } - const tree = await createVerkleTree({ verkleCrypto: verkle }) - const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) + const tree = await createBinaryTree() + const stateManager = new StatefulBinaryTreeStateManager({ common, tree }) await stateManager.putAccount(address, account) const preStateRoot = tree.root() const evm = await createEVM({ stateManager, common }) - evm.verkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, + evm.binaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, }) - evm.systemVerkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, + evm.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, }) const res = await evm.runCall({ to: receiver, @@ -69,138 +65,137 @@ describe('Precompiles: EXECUTE', () => { }) const executionGasUsed = res.execResult.executionGasUsed const postStateRoot = tree.root() - const stateWitness = await generateStateWitness( + const witness = await generateBinaryExecutionWitness( stateManager, - evm.verkleAccessWitness, + evm.binaryAccessWitness, preStateRoot, ) + console.log(stateWitnessJSONToSSZ) // End of L2 state construction // Create a trace - const trace = { - witness: stateWitnessJSONToSSZ(stateWitness), - txs: [tx], - } - const traceBytes = traceContainer.encode(trace) - - // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use - // once we have the trace properly converted to an Ethereum blob. - const hash = bytesToHex(sha256(traceBytes)) - evm['executionBlobs'].set(hash, traceBytes) - - const addressStr = '0000000000000000000000000000000000000012' - const EXECUTE = getActivePrecompiles(common).get(addressStr)! - const input = concatBytes( - preStateRoot, - postStateRoot, - hexToBytes(hash), - setLengthLeft(bigIntToBytes(executionGasUsed), 32), - ) - - const result = await EXECUTE({ - data: input, - gasLimit: 1000000000n, - common, - _EVM: evm, - }) - assert.equal(result.returnValue[0], 1) - }) -}) - -describe('runCall', () => { - it('should execute runCall successfully', async () => { - const common = new Common({ - chain: Mainnet, - hardfork: Hardfork.Prague, - eips: [6800, 9999], - customCrypto: { - verkle, - }, - }) - // Construct L2 state and transaction - const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) - const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') - const receiver = createAddressFromPrivateKey( - hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), - ) - const l2Tx = { - to: receiver.toBytes(), - from: address.toBytes(), - gasLimit: BigInt('0xffffffffff'), - gasPrice: BigInt('0x1'), - value: BigInt('0x1'), - data: new Uint8Array(), - } - const l2Tree = await createVerkleTree({ verkleCrypto: verkle }) - const l2StateManager = new StatefulVerkleStateManager({ common, trie: l2Tree }) - await l2StateManager.putAccount(address, account) - - const preStateRoot = l2Tree.root() - const l2EVM = await createEVM({ stateManager: l2StateManager, common }) - - l2EVM.verkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, - }) - l2EVM.systemVerkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, - }) - const res = await l2EVM.runCall({ - to: receiver, - caller: address, - gasLimit: l2Tx.gasLimit, - gasPrice: l2Tx.gasPrice, - value: l2Tx.value, - }) - const executionGasUsed = res.execResult.executionGasUsed - const postStateRoot = l2Tree.root() - const stateWitness = await generateStateWitness( - l2StateManager, - l2EVM.verkleAccessWitness, - preStateRoot, - ) - // End of L2 state construction - - // Create mainnet state and EVM - const tree = await createVerkleTree({ verkleCrypto: verkle }) - const stateManager = new StatefulVerkleStateManager({ common, trie: tree }) - const evm = await createEVM({ stateManager, common }) - - evm.verkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, - }) - evm.systemVerkleAccessWitness = new VerkleAccessWitness({ - verkleCrypto: verkle, - }) - - // Create a trace - const trace = { - witness: stateWitnessJSONToSSZ(stateWitness), - txs: [l2Tx], - } - - const traceBytes = traceContainer.encode(trace) - - // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use - // once we have the trace properly converted to an Ethereum blob. - const hash = bytesToHex(sha256(traceBytes)) - evm['executionBlobs'].set(hash, traceBytes) - - const precompileAddrStr = '0x0000000000000000000000000000000000000012' - - const input = concatBytes( - preStateRoot, - postStateRoot, - hexToBytes(hash), - setLengthLeft(bigIntToBytes(executionGasUsed), 32), - ) - - const mainnetTx = { - to: createAddressFromString(precompileAddrStr), - data: input, - } - - const res2 = await evm.runCall({ ...mainnetTx, skipBalance: true }) - assert.equal(res2.execResult.returnValue[0], 1) + // const trace = { + // witness: stateWitnessJSONToSSZ(stateWitness), + // txs: [tx], + // } + // const traceBytes = traceContainer.encode(trace) + + // // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // // once we have the trace properly converted to an Ethereum blob. + // const hash = bytesToHex(sha256(traceBytes)) + // evm['executionBlobs'].set(hash, traceBytes) + + // const addressStr = '0000000000000000000000000000000000000012' + // const EXECUTE = getActivePrecompiles(common).get(addressStr)! + // const input = concatBytes( + // preStateRoot, + // postStateRoot, + // hexToBytes(hash), + // setLengthLeft(bigIntToBytes(executionGasUsed), 32), + // ) + + // const result = await EXECUTE({ + // data: input, + // gasLimit: 1000000000n, + // common, + // _EVM: evm, + // }) + // assert.equal(result.returnValue[0], 1) + // }) + // }) + + // describe('runCall', () => { + // it('should execute runCall successfully', async () => { + // const common = new Common({ + // chain: Mainnet, + // hardfork: Hardfork.Prague, + // eips: [7864, 9999], + // }) + + // // Construct L2 state and transaction + // const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) + // const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') + // const receiver = createAddressFromPrivateKey( + // hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), + // ) + // const l2Tx = { + // to: receiver.toBytes(), + // from: address.toBytes(), + // gasLimit: BigInt('0xffffffffff'), + // gasPrice: BigInt('0x1'), + // value: BigInt('0x1'), + // data: new Uint8Array(), + // } + // const l2Tree = await createBinaryTree() + // const l2StateManager = new StatefulBinaryTreeStateManager({ common, tree: l2Tree }) + // await l2StateManager.putAccount(address, account) + + // const preStateRoot = l2Tree.root() + // const l2EVM = await createEVM({ stateManager: l2StateManager, common }) + + // l2EVM.binaryAccessWitness = new BinaryTreeAccessWitness({ + // hashFunction: l2Tree['_opts'].hashFunction, + // }) + // l2EVM.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + // hashFunction: l2Tree['_opts'].hashFunction, + // }) + // const res = await l2EVM.runCall({ + // to: receiver, + // caller: address, + // gasLimit: l2Tx.gasLimit, + // gasPrice: l2Tx.gasPrice, + // value: l2Tx.value, + // }) + // const executionGasUsed = res.execResult.executionGasUsed + // const postStateRoot = l2Tree.root() + // const stateWitness = await generateBinaryTreeStateWitness( + // l2StateManager, + // l2EVM.binaryAccessWitness, + // preStateRoot, + // ) + // // End of L2 state construction + + // // Create mainnet state and EVM + // const tree = await createBinaryTree() + // const stateManager = new StatefulBinaryTreeStateManager({ common, tree }) + // const evm = await createEVM({ stateManager, common }) + + // evm.binaryAccessWitness = new BinaryTreeAccessWitness({ + // hashFunction: tree['_opts'].hashFunction, + // }) + // evm.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + // hashFunction: tree['_opts'].hashFunction, + // }) + + // // Create a trace + // const trace = { + // witness: stateWitnessJSONToSSZ(stateWitness), + // txs: [l2Tx], + // } + + // const traceBytes = traceContainer.encode(trace) + + // // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // // once we have the trace properly converted to an Ethereum blob. + // const hash = bytesToHex(sha256(traceBytes)) + // evm['executionBlobs'].set(hash, traceBytes) + + // const precompileAddrStr = '0x0000000000000000000000000000000000000012' + + // const input = concatBytes( + // preStateRoot, + // postStateRoot, + // hexToBytes(hash), + // setLengthLeft(bigIntToBytes(executionGasUsed), 32), + // ) + + // const mainnetTx = { + // to: createAddressFromString(precompileAddrStr), + // data: input, + // } + + // const res2 = await evm.runCall({ ...mainnetTx, skipBalance: true }) + // assert.equal(res2.execResult.returnValue[0], 1) }) }) From 61ea9970935da23dcbe300b084c3a365e15dccf0 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:41:06 -0500 Subject: [PATCH 23/25] Update shape of trace/witness --- packages/evm/src/binaryTreeAccessWitness.ts | 9 +- packages/evm/src/evm.ts | 7 +- packages/evm/src/precompiles/12-execute.ts | 94 ++++--- .../evm/test/precompiles/12-execute.spec.ts | 246 +++++++++--------- 4 files changed, 197 insertions(+), 159 deletions(-) diff --git a/packages/evm/src/binaryTreeAccessWitness.ts b/packages/evm/src/binaryTreeAccessWitness.ts index 04e0c283a61..36746f62287 100644 --- a/packages/evm/src/binaryTreeAccessWitness.ts +++ b/packages/evm/src/binaryTreeAccessWitness.ts @@ -397,7 +397,7 @@ export const generateBinaryExecutionWitness = async ( const ew: BinaryTreeExecutionWitness = { stateDiff: [], parentStateRoot: bytesToHex(parentStateRoot), - proof: undefined as any, + proof: {}, } // Generate a map of all stems with their accessed suffixes @@ -413,9 +413,12 @@ export const generateBinaryExecutionWitness = async ( } } - // Get values from the tree for each stem and suffix + // Get values and proofs from the tree for each stem and suffix for (const stem of accessedSuffixes.keys()) { tree.root(parentStateRoot) + // Generate proofs for each stem from prestate root + const proof = await tree.createBinaryProof(hexToBytes(stem)) + ew.proof[stem] = proof const suffixes = accessedSuffixes.get(stem) if (suffixes === undefined || suffixes.length === 0) continue const currentValues = await tree.get(hexToBytes(stem), suffixes) @@ -439,8 +442,6 @@ export const generateBinaryExecutionWitness = async ( }) } ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff }) - const proof = await tree.createBinaryProof(hexToBytes(stem)) - ew.proof[stem] = proof } tree['_lock'].release() return ew diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index 8dcf68b6331..5566700e9bc 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -191,7 +191,7 @@ export class EVM implements EVMInterface { const supportedEIPs = [ 663, 1153, 1559, 2537, 2565, 2718, 2929, 2930, 2935, 3198, 3529, 3540, 3541, 3607, 3651, 3670, 3855, 3860, 4200, 4399, 4750, 4788, 4844, 4895, 5133, 5450, 5656, 6110, 6206, 6780, 6800, - 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709, 9999, + 7002, 7069, 7251, 7480, 7516, 7620, 7685, 7691, 7692, 7698, 7702, 7709, 7864, 9999, ] for (const eip of this.common.eips()) { @@ -896,7 +896,10 @@ export class EVM implements EVMInterface { createdAddresses: opts.createdAddresses ?? new Set(), delegatecall: opts.delegatecall, blobVersionedHashes: opts.blobVersionedHashes, - accessWitness: this.verkleAccessWitness, + accessWitness: + this.verkleAccessWitness !== undefined + ? this.verkleAccessWitness + : this.binaryAccessWitness, }) } diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index cf6549bf6d8..cc441d8407f 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -1,4 +1,4 @@ -import { createBinaryTree } from '@ethereumjs/binarytree' +import { binaryTreeFromProof, decodeBinaryNode } from '@ethereumjs/binarytree' import { StatefulBinaryTreeStateManager } from '@ethereumjs/statemanager' import { bytesToBigInt, @@ -9,7 +9,10 @@ import { } from '@ethereumjs/util' import * as ssz from 'micro-eth-signer/ssz' -import { BinaryTreeAccessWitness } from '../binaryTreeAccessWitness.js' +import { + BinaryTreeAccessWitness, + type generateBinaryExecutionWitness, +} from '../binaryTreeAccessWitness.js' import { createEVM } from '../constructors.js' import { EvmErrorResult, OOGResult } from '../evm.js' import { ERROR, EvmError } from '../exceptions.js' @@ -18,13 +21,41 @@ import { gasLimitCheck } from './util.js' import { getPrecompileName } from './index.js' -// import type { generateStateWitness } from '../binaryTreeAccessWitness.js' +import type {} from '../binaryTreeAccessWitness.js' + import type { EVM } from '../evm.js' import type { ExecResult } from '../types.js' import type { PrecompileInput } from './types.js' +import type { BinaryNode } from '@ethereumjs/binarytree' + +// For suffix diffs in state diff +const SuffixDiff = ssz.container({ + suffix: ssz.uint8, + currentValue: ssz.bytevector(32), + newValue: ssz.bytevector(32), +}) + +// For state diff entries +const StateDiff = ssz.container({ + stem: ssz.bytevector(31), // The stem as a hex string + suffixDiffs: ssz.list(256, SuffixDiff), // List of suffix diffs +}) + +// For proof entries +const ProofEntry = ssz.container({ + stem: ssz.bytevector(31), // 31-byte vector for the stem + proofData: ssz.list(32, ssz.bytelist(16384)), // List of byte arrays, each up to 16384 bytes +}) + +// Define the BinaryTreeExecutionWitness container +const BinaryTreeExecutionWitness = ssz.container({ + stateDiff: ssz.list(1024, StateDiff), // List of state diffs + parentStateRoot: ssz.bytevector(32), // Parent state root as hex + proof: ssz.list(256, ProofEntry), // List of proof entries with stems and proof data +}) const MAX_CALL_DATA_SIZE = 7500000 // Assuming a transaction with all zero bytes fills up an entire block worth of gas -export const traceContainer = ssz.container({ +export const traceContainer: ssz.SSZCoder = ssz.container({ txs: ssz.list( // An ssz list of tx objects that match the `eth_call` tx object format 256, @@ -37,31 +68,30 @@ export const traceContainer = ssz.container({ data: ssz.bytelist(MAX_CALL_DATA_SIZE), }), ), - witnesses: ssz.container({ - // A state witness that contains all the reads and writes to the state that are part of the tx list - proofs: ssz.list(1024, ssz.list(248, ssz.bytevector(32))), // - parentStateRoot: ssz.bytevector(32), - }), + witness: BinaryTreeExecutionWitness, }) export const stateWitnessJSONToSSZ = ( - witness: Awaited>, + witness: Awaited>, ) => { - const reads = Array.from(witness.reads.entries()).map(([key, value]) => ({ - key: hexToBytes(key), - currentValue: hexToBytes(value), - })) - - const writes = Array.from(witness.writes.entries()).map(([key, value]) => ({ - key: hexToBytes(key), - currentValue: hexToBytes(value.currentValue), - newValue: hexToBytes(value.newValue), - })) - return { - reads, - writes, + stateDiff: witness.stateDiff.map((diff) => ({ + stem: hexToBytes(diff.stem), + suffixDiffs: diff.suffixDiffs.map((suffixDiff) => ({ + suffix: suffixDiff.suffix, + currentValue: + suffixDiff.currentValue !== null + ? hexToBytes(suffixDiff.currentValue) + : new Uint8Array(32), + newValue: + suffixDiff.newValue !== null ? hexToBytes(suffixDiff.newValue) : new Uint8Array(32), + })), + })), parentStateRoot: hexToBytes(witness.parentStateRoot), + proof: Object.entries(witness.proof).map(([stem, proof]) => ({ + stem: hexToBytes(stem), + proofData: proof, + })), } } @@ -89,15 +119,19 @@ export async function precompile12(opts: PrecompileInput): Promise { return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) } const executeGasUsed = bytesToBigInt(data.subarray(96)) - const witness = decodedTrace.witness - const tree = await createBinaryTree() + // Populate the L2 state trie with the prestate - for (const chunk of witness.reads) { - await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) - } - for (const chunk of witness.writes) { - await tree.put(chunk.key.slice(0, 31), [Number(chunk.key.slice(31))], [chunk.currentValue]) + + const witness = decodedTrace.witness + const tree = await binaryTreeFromProof(witness.proof[0].proofData) + for (const proof of witness.proof.slice(1)) { + const putStack: [Uint8Array, BinaryNode][] = proof.proofData.map((bytes: Uint8Array) => { + const node = decodeBinaryNode(bytes) + return [tree['merkelize'](node), node] + }) + await tree.saveStack(putStack) } + let executionResult = true const stateManager = new StatefulBinaryTreeStateManager({ common: opts.common, tree }) const l2EVM = await createEVM({ stateManager, common: opts.common }) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 41f09fe35f3..ded5fcf1853 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -71,131 +71,131 @@ describe('Precompiles: EXECUTE', () => { preStateRoot, ) - console.log(stateWitnessJSONToSSZ) // End of L2 state construction // Create a trace - // const trace = { - // witness: stateWitnessJSONToSSZ(stateWitness), - // txs: [tx], - // } - // const traceBytes = traceContainer.encode(trace) - - // // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use - // // once we have the trace properly converted to an Ethereum blob. - // const hash = bytesToHex(sha256(traceBytes)) - // evm['executionBlobs'].set(hash, traceBytes) - - // const addressStr = '0000000000000000000000000000000000000012' - // const EXECUTE = getActivePrecompiles(common).get(addressStr)! - // const input = concatBytes( - // preStateRoot, - // postStateRoot, - // hexToBytes(hash), - // setLengthLeft(bigIntToBytes(executionGasUsed), 32), - // ) - - // const result = await EXECUTE({ - // data: input, - // gasLimit: 1000000000n, - // common, - // _EVM: evm, - // }) - // assert.equal(result.returnValue[0], 1) - // }) - // }) - - // describe('runCall', () => { - // it('should execute runCall successfully', async () => { - // const common = new Common({ - // chain: Mainnet, - // hardfork: Hardfork.Prague, - // eips: [7864, 9999], - // }) - - // // Construct L2 state and transaction - // const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) - // const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') - // const receiver = createAddressFromPrivateKey( - // hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), - // ) - // const l2Tx = { - // to: receiver.toBytes(), - // from: address.toBytes(), - // gasLimit: BigInt('0xffffffffff'), - // gasPrice: BigInt('0x1'), - // value: BigInt('0x1'), - // data: new Uint8Array(), - // } - // const l2Tree = await createBinaryTree() - // const l2StateManager = new StatefulBinaryTreeStateManager({ common, tree: l2Tree }) - // await l2StateManager.putAccount(address, account) - - // const preStateRoot = l2Tree.root() - // const l2EVM = await createEVM({ stateManager: l2StateManager, common }) - - // l2EVM.binaryAccessWitness = new BinaryTreeAccessWitness({ - // hashFunction: l2Tree['_opts'].hashFunction, - // }) - // l2EVM.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ - // hashFunction: l2Tree['_opts'].hashFunction, - // }) - // const res = await l2EVM.runCall({ - // to: receiver, - // caller: address, - // gasLimit: l2Tx.gasLimit, - // gasPrice: l2Tx.gasPrice, - // value: l2Tx.value, - // }) - // const executionGasUsed = res.execResult.executionGasUsed - // const postStateRoot = l2Tree.root() - // const stateWitness = await generateBinaryTreeStateWitness( - // l2StateManager, - // l2EVM.binaryAccessWitness, - // preStateRoot, - // ) - // // End of L2 state construction - - // // Create mainnet state and EVM - // const tree = await createBinaryTree() - // const stateManager = new StatefulBinaryTreeStateManager({ common, tree }) - // const evm = await createEVM({ stateManager, common }) - - // evm.binaryAccessWitness = new BinaryTreeAccessWitness({ - // hashFunction: tree['_opts'].hashFunction, - // }) - // evm.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ - // hashFunction: tree['_opts'].hashFunction, - // }) - - // // Create a trace - // const trace = { - // witness: stateWitnessJSONToSSZ(stateWitness), - // txs: [l2Tx], - // } - - // const traceBytes = traceContainer.encode(trace) - - // // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use - // // once we have the trace properly converted to an Ethereum blob. - // const hash = bytesToHex(sha256(traceBytes)) - // evm['executionBlobs'].set(hash, traceBytes) - - // const precompileAddrStr = '0x0000000000000000000000000000000000000012' - - // const input = concatBytes( - // preStateRoot, - // postStateRoot, - // hexToBytes(hash), - // setLengthLeft(bigIntToBytes(executionGasUsed), 32), - // ) - - // const mainnetTx = { - // to: createAddressFromString(precompileAddrStr), - // data: input, - // } - - // const res2 = await evm.runCall({ ...mainnetTx, skipBalance: true }) - // assert.equal(res2.execResult.returnValue[0], 1) + const trace = { + witness: stateWitnessJSONToSSZ(witness), + txs: [tx], + } + + const traceBytes = traceContainer.encode(trace) + + // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // once we have the trace properly converted to an Ethereum blob. + const hash = bytesToHex(sha256(traceBytes)) + evm['executionBlobs'].set(hash, traceBytes) + + const addressStr = '0000000000000000000000000000000000000012' + const EXECUTE = getActivePrecompiles(common).get(addressStr)! + const input = concatBytes( + preStateRoot, + postStateRoot, + hexToBytes(hash), + setLengthLeft(bigIntToBytes(executionGasUsed), 32), + ) + + const result = await EXECUTE({ + data: input, + gasLimit: 1000000000n, + common, + _EVM: evm, + }) + assert.equal(result.returnValue[0], 1) + }) +}) + +describe('runCall', () => { + it('should execute runCall successfully', async () => { + const common = new Common({ + chain: Mainnet, + hardfork: Hardfork.Prague, + eips: [7864, 9999], + }) + + // Construct L2 state and transaction + const account = createAccount({ balance: 0xffffffffffffffffffffffffffffffffffffffffn }) + const address = createAddressFromString('0x999aebeac9619be18e0369d9cb8d0393cfb99021') + const receiver = createAddressFromPrivateKey( + hexToBytes('0xaeb51ceb07e4f6761ea6ad9a772d0e4a70367020fd6175b5e271d0d12e37d24d'), + ) + const l2Tx = { + to: receiver.toBytes(), + from: address.toBytes(), + gasLimit: BigInt('0xffffffffff'), + gasPrice: BigInt('0x1'), + value: BigInt('0x1'), + data: new Uint8Array(), + } + const l2Tree = await createBinaryTree() + const l2StateManager = new StatefulBinaryTreeStateManager({ common, tree: l2Tree }) + await l2StateManager.putAccount(address, account) + + const preStateRoot = l2Tree.root() + const l2EVM = await createEVM({ stateManager: l2StateManager, common }) + + l2EVM.binaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: l2Tree['_opts'].hashFunction, + }) + l2EVM.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: l2Tree['_opts'].hashFunction, + }) + const res = await l2EVM.runCall({ + to: receiver, + caller: address, + gasLimit: l2Tx.gasLimit, + gasPrice: l2Tx.gasPrice, + value: l2Tx.value, + }) + const executionGasUsed = res.execResult.executionGasUsed + const postStateRoot = l2Tree.root() + const stateWitness = await generateBinaryExecutionWitness( + l2StateManager, + l2EVM.binaryAccessWitness, + preStateRoot, + ) + // End of L2 state construction + + // Create mainnet state and EVM + const tree = await createBinaryTree() + const stateManager = new StatefulBinaryTreeStateManager({ common, tree }) + const evm = await createEVM({ stateManager, common }) + + evm.binaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, + }) + evm.systemBinaryAccessWitness = new BinaryTreeAccessWitness({ + hashFunction: tree['_opts'].hashFunction, + }) + + // Create a trace + const trace = { + witness: stateWitnessJSONToSSZ(stateWitness), + txs: [l2Tx], + } + + const traceBytes = traceContainer.encode(trace) + + // We use the sha256 hash of the serialized trace as a reference. This is standing in for the versionedHash that we should use + // once we have the trace properly converted to an Ethereum blob. + const hash = bytesToHex(sha256(traceBytes)) + evm['executionBlobs'].set(hash, traceBytes) + + const precompileAddrStr = '0x0000000000000000000000000000000000000012' + + const input = concatBytes( + preStateRoot, + postStateRoot, + hexToBytes(hash), + setLengthLeft(bigIntToBytes(executionGasUsed), 32), + ) + + const mainnetTx = { + to: createAddressFromString(precompileAddrStr), + data: input, + } + + const res2 = await evm.runCall({ ...mainnetTx, skipBalance: true }) + assert.equal(res2.execResult.returnValue[0], 1) }) }) From 92ab0ef32b35102551ca6446ccc5e7df0710e90b Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:52:19 -0500 Subject: [PATCH 24/25] Add extra account to state to verify state transitions --- packages/evm/test/precompiles/12-execute.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index ded5fcf1853..9a26ed87b3c 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -9,6 +9,7 @@ import { createAddressFromPrivateKey, createAddressFromString, hexToBytes, + randomBytes, setLengthLeft, } from '@ethereumjs/util' import { sha256 } from 'ethereum-cryptography/sha256' @@ -131,6 +132,11 @@ describe('runCall', () => { const l2StateManager = new StatefulBinaryTreeStateManager({ common, tree: l2Tree }) await l2StateManager.putAccount(address, account) + // Add a random account to ensure that proof is providing enough inner nodes to validate state transition + await l2StateManager.putAccount( + createAddressFromPrivateKey(randomBytes(32)), + createAccount({ balance: 0x1n }), + ) const preStateRoot = l2Tree.root() const l2EVM = await createEVM({ stateManager: l2StateManager, common }) From e4fbfcb5afa2fc51b267e8b40a962e154dada8da Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Fri, 2 May 2025 15:15:16 -0400 Subject: [PATCH 25/25] clean up error messages --- packages/evm/src/precompiles/12-execute.ts | 29 +++++++++---------- .../evm/test/precompiles/12-execute.spec.ts | 5 ++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/evm/src/precompiles/12-execute.ts b/packages/evm/src/precompiles/12-execute.ts index cc441d8407f..568a93dbe35 100644 --- a/packages/evm/src/precompiles/12-execute.ts +++ b/packages/evm/src/precompiles/12-execute.ts @@ -1,6 +1,7 @@ import { binaryTreeFromProof, decodeBinaryNode } from '@ethereumjs/binarytree' import { StatefulBinaryTreeStateManager } from '@ethereumjs/statemanager' import { + type PrefixedHexString, bytesToBigInt, bytesToHex, createAddressFromString, @@ -12,21 +13,19 @@ import * as ssz from 'micro-eth-signer/ssz' import { BinaryTreeAccessWitness, type generateBinaryExecutionWitness, -} from '../binaryTreeAccessWitness.js' -import { createEVM } from '../constructors.js' -import { EvmErrorResult, OOGResult } from '../evm.js' -import { ERROR, EvmError } from '../exceptions.js' +} from '../binaryTreeAccessWitness.ts' +import { createEVM } from '../constructors.ts' +import { EVMErrorResult, OOGResult } from '../evm.ts' -import { gasLimitCheck } from './util.js' +import { gasLimitCheck } from './util.ts' -import { getPrecompileName } from './index.js' +import { getPrecompileName } from './index.ts' -import type {} from '../binaryTreeAccessWitness.js' - -import type { EVM } from '../evm.js' -import type { ExecResult } from '../types.js' -import type { PrecompileInput } from './types.js' import type { BinaryNode } from '@ethereumjs/binarytree' +import { EVMError } from '../errors.ts' +import type { EVM } from '../evm.ts' +import type { ExecResult } from '../types.ts' +import type { PrecompileInput } from './types.ts' // For suffix diffs in state diff const SuffixDiff = ssz.container({ @@ -89,7 +88,7 @@ export const stateWitnessJSONToSSZ = ( })), parentStateRoot: hexToBytes(witness.parentStateRoot), proof: Object.entries(witness.proof).map(([stem, proof]) => ({ - stem: hexToBytes(stem), + stem: hexToBytes(stem as PrefixedHexString), proofData: proof, })), } @@ -104,19 +103,19 @@ export async function precompile12(opts: PrecompileInput): Promise { return OOGResult(opts.gasLimit) } if (data.length !== 128) { - return EvmErrorResult(new EvmError(ERROR.INVALID_INPUT_LENGTH), opts.gasLimit) + return EVMErrorResult(new EVMError(EVMError.errorMessages.INVALID_INPUT_LENGTH), opts.gasLimit) } const _preStateRoot = data.subarray(0, 32) // prestateroot for L2 state const postStateRoot = data.subarray(32, 64) // post state root for L2 state const traceBlob = evm['executionBlobs'].get(bytesToHex(data.subarray(64, 96))) // reference to state access and transactions if (traceBlob === undefined) { opts._debug?.(`${pName} error - trace not found`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + return EVMErrorResult(new EVMError(EVMError.errorMessages.REVERT), opts.gasLimit) } const decodedTrace = traceContainer.decode(traceBlob) if (decodedTrace.txs === undefined || decodedTrace.witness === undefined) { opts._debug?.(`${pName} error - trace is invalid`) - return EvmErrorResult(new EvmError(ERROR.REVERT), opts.gasLimit) + return EVMErrorResult(new EVMError(EVMError.errorMessages.REVERT), opts.gasLimit) } const executeGasUsed = bytesToBigInt(data.subarray(96)) diff --git a/packages/evm/test/precompiles/12-execute.spec.ts b/packages/evm/test/precompiles/12-execute.spec.ts index 9a26ed87b3c..efa965aeb31 100644 --- a/packages/evm/test/precompiles/12-execute.spec.ts +++ b/packages/evm/test/precompiles/12-execute.spec.ts @@ -20,9 +20,8 @@ import { createEVM, generateBinaryExecutionWitness, getActivePrecompiles, -} from '../../src/index.js' -import { stateWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.js' - +} from '../../src/index.ts' +import { stateWitnessJSONToSSZ, traceContainer } from '../../src/precompiles/12-execute.ts' describe('Precompiles: EXECUTE', () => { it('should execute a trace', async () => { const common = new Common({