Skip to content

Commit c7d6191

Browse files
Construct verkle execution witness (#3864)
* Update historyStorage address * Remove execution prestate since unused * Finish generateExecutionWitness function * execution witness fixes * Fix logic in building suffix diffs * Add test * spell check * Address feedback * Lint * add lock and update comments --------- Co-authored-by: Gabriel Rocheleau <[email protected]>
1 parent b2091a6 commit c7d6191

File tree

7 files changed

+165
-28
lines changed

7 files changed

+165
-28
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/evm/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@ethereumjs/common": "^5.0.0-alpha.1",
5050
"@ethereumjs/statemanager": "^3.0.0-alpha.1",
5151
"@ethereumjs/util": "^10.0.0-alpha.1",
52+
"@ethereumjs/verkle": "^0.2.0-alpha.1",
5253
"@noble/curves": "^1.8.1",
5354
"@types/debug": "^4.1.9",
5455
"debug": "^4.3.3",

packages/evm/src/params.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export const paramsEVM: ParamsDict = {
271271
*/
272272
2935: {
273273
// evm
274-
historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored
274+
historyStorageAddress: '0x0000F90827F1C53A10CB7A02335B175320002935', // The address where the historical blockhashes are stored
275275
historyServeWindow: 8192, // The amount of blocks to be served by the historical blockhash contract
276276
systemAddress: '0xfffffffffffffffffffffffffffffffffffffffe', // The system address
277277
},

packages/evm/src/verkleAccessWitness.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import {
88
VERKLE_MAIN_STORAGE_OFFSET,
99
VERKLE_NODE_WIDTH,
1010
bytesToHex,
11+
equalsBytes,
1112
getVerkleKey,
1213
getVerkleStem,
1314
getVerkleTreeIndicesForCodeChunk,
1415
getVerkleTreeIndicesForStorageSlot,
16+
hexToBytes,
1517
intToBytes,
1618
} from '@ethereumjs/util'
1719
import debugDefault from 'debug'
@@ -26,7 +28,14 @@ import type {
2628
VerkleAccessedState,
2729
VerkleAccessedStateWithAddress,
2830
} from '@ethereumjs/common'
29-
import type { Address, PrefixedHexString, VerkleCrypto } from '@ethereumjs/util'
31+
import type { StatefulVerkleStateManager } from '@ethereumjs/statemanager'
32+
import type {
33+
Address,
34+
PrefixedHexString,
35+
VerkleCrypto,
36+
VerkleExecutionWitness,
37+
} from '@ethereumjs/util'
38+
import type { VerkleTree } from '@ethereumjs/verkle'
3039

3140
const debug = debugDefault('evm:verkle:aw')
3241

@@ -377,3 +386,70 @@ export class VerkleAccessWitness implements VerkleAccessWitnessInterface {
377386
}
378387
}
379388
}
389+
390+
/**
391+
* Generate a {@link VerkleExecutionWitness} from a state manager and an access witness.
392+
* @param stateManager - The state manager containing the state to generate the witness for.
393+
* @param accessWitness - The access witness containing the accessed states.
394+
* @param parentStateRoot - The parent state root (i.e. prestate root) to generate the witness for.
395+
* @returns The generated verkle execution witness
396+
*
397+
* Note: This does not provide the verkle proof, which is not implemented
398+
*/
399+
export const generateExecutionWitness = async (
400+
stateManager: StatefulVerkleStateManager,
401+
accessWitness: VerkleAccessWitness,
402+
parentStateRoot: Uint8Array,
403+
): Promise<VerkleExecutionWitness> => {
404+
const trie = stateManager['_trie'] as VerkleTree
405+
await trie['_lock'].acquire()
406+
const postStateRoot = await stateManager.getStateRoot()
407+
const ew: VerkleExecutionWitness = {
408+
stateDiff: [],
409+
parentStateRoot: bytesToHex(parentStateRoot),
410+
verkleProof: undefined as any, // Verkle proofs are not implemented (and never will be)
411+
}
412+
413+
// Generate a map of all stems with their accessed suffixes
414+
const accessedSuffixes = new Map<PrefixedHexString, number[]>()
415+
for (const chunkKey of accessWitness['chunks'].keys()) {
416+
const stem = chunkKey.slice(0, 64) as PrefixedHexString
417+
if (accessedSuffixes.has(stem)) {
418+
const suffixes = accessedSuffixes.get(stem)
419+
suffixes!.push(parseInt(chunkKey.slice(64), 16))
420+
accessedSuffixes.set(stem, suffixes!)
421+
} else {
422+
accessedSuffixes.set(stem, [parseInt(chunkKey.slice(64), 16)])
423+
}
424+
}
425+
426+
// Get values from the trie for each stem and suffix
427+
for (const stem of accessedSuffixes.keys()) {
428+
trie.root(parentStateRoot)
429+
const suffixes = accessedSuffixes.get(stem)
430+
if (suffixes === undefined || suffixes.length === 0) continue
431+
const currentValues = await trie.get(hexToBytes(stem), suffixes)
432+
trie.root(postStateRoot)
433+
const newValues = await trie.get(hexToBytes(stem), suffixes)
434+
const stemStateDiff = []
435+
for (let x = 0; x < suffixes.length; x++) {
436+
// skip if both are the same
437+
const currentValue = currentValues[x]
438+
const newValue = newValues[x]
439+
if (
440+
currentValue instanceof Uint8Array &&
441+
newValue instanceof Uint8Array &&
442+
equalsBytes(currentValue, newValue)
443+
)
444+
continue
445+
stemStateDiff.push({
446+
suffix: suffixes[x],
447+
currentValue: currentValue ? bytesToHex(currentValue) : null,
448+
newValue: newValue ? bytesToHex(newValue) : null,
449+
})
450+
}
451+
ew.stateDiff.push({ stem, suffixDiffs: stemStateDiff })
452+
}
453+
trie['_lock'].release()
454+
return ew
455+
}

packages/evm/test/verkle.spec.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
22
import { StatefulVerkleStateManager } from '@ethereumjs/statemanager'
33
import {
44
bigIntToBytes,
5+
bytesToHex,
56
createAccount,
67
createAddressFromString,
8+
decodeVerkleLeafBasicData,
9+
getVerkleStem,
710
hexToBytes,
811
setLengthLeft,
912
} from '@ethereumjs/util'
1013
import { createVerkleTree } from '@ethereumjs/verkle'
1114
import * as verkle from 'micro-eth-signer/verkle'
1215
import { assert, describe, it } from 'vitest'
1316

14-
import { VerkleAccessWitness, createEVM } from '../src/index.js'
17+
import { VerkleAccessWitness, createEVM, generateExecutionWitness } from '../src/index.js'
1518

1619
describe('verkle tests', () => {
1720
it('should execute bytecode and update the state', async () => {
@@ -109,3 +112,79 @@ describe('verkle tests', () => {
109112
assert.equal(res.execResult.exceptionError?.error, undefined)
110113
})
111114
})
115+
describe('generate an execution witness', () => {
116+
it('should generate the correct execution witness from a prestate and changes', async () => {
117+
const preStateVKT = {
118+
'0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d00':
119+
'0x00000000000000000000000000000000000000000000003635c9adc5dea00000',
120+
'0x0365b079a274a1808d56484ce5bd97914629907d75767f51439102e22cd50d01':
121+
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
122+
'0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad700':
123+
'0x0000000000000036000000000000000100000000000000000000000000000000',
124+
'0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad701':
125+
'0xdf61faef43babbb1ebde8fd82ab9cb4cb74c240d0025138521477e073f72080a',
126+
'0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad780':
127+
'0x0060203611603157600143035f35116029575f35612000014311602957612000',
128+
'0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad781':
129+
'0x005f3506545f5260205ff35b5f5f5260205ff35b5f5ffd000000000000000000',
130+
}
131+
const tx = {
132+
type: '0x0',
133+
chainId: '0x1',
134+
nonce: '0x0',
135+
gasPrice: '0xa',
136+
gas: '0x5f5e100',
137+
to: '0x8a0a19589531694250d570040a0c4b74576919b8',
138+
value: '0x0',
139+
input: '0x',
140+
v: '0x25',
141+
r: '0x50ae258f0b1f7c44e5227b43c338aa7f2d9805115b90a6baeaaee2358796e074',
142+
s: '0xec910ad0244580c17e1d6a512b3574c62e92840184109e3037760d39b20cb94',
143+
sender: '0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b',
144+
}
145+
146+
const common = new Common({
147+
chain: Mainnet,
148+
customCrypto: { verkle },
149+
eips: [6800],
150+
hardfork: Hardfork.Prague,
151+
})
152+
const trie = await createVerkleTree()
153+
// Setup prestate
154+
for (const [key, value] of Object.entries(preStateVKT)) {
155+
const stem = hexToBytes(key).slice(0, 31)
156+
const suffix = parseInt(key.slice(64), 16)
157+
await trie.put(stem, [suffix], [hexToBytes(value)])
158+
}
159+
const preStateRoot = trie.root()
160+
const sm = new StatefulVerkleStateManager({ common, trie })
161+
const evm = await createEVM({ common, stateManager: sm })
162+
evm.verkleAccessWitness = new VerkleAccessWitness({
163+
verkleCrypto: verkle,
164+
})
165+
evm.systemVerkleAccessWitness = new VerkleAccessWitness({
166+
verkleCrypto: verkle,
167+
})
168+
// Run tx
169+
await evm.runCall({
170+
code: hexToBytes(tx.input),
171+
caller: createAddressFromString(tx.sender),
172+
to: createAddressFromString(tx.to),
173+
gasLimit: BigInt(tx.gas),
174+
gasPrice: BigInt(tx.gasPrice),
175+
})
176+
const executionWitness = await generateExecutionWitness(
177+
sm,
178+
evm.verkleAccessWitness,
179+
preStateRoot,
180+
)
181+
const stem = bytesToHex(getVerkleStem(verkle, createAddressFromString(tx.sender)))
182+
assert.ok(executionWitness.stateDiff.findIndex((diff) => diff.stem === stem) !== -1)
183+
const stemDiff =
184+
executionWitness.stateDiff[executionWitness.stateDiff.findIndex((diff) => diff.stem === stem)]
185+
const suffixDiff = stemDiff.suffixDiffs.find((diff) => diff.suffix === 0)
186+
assert.ok(suffixDiff?.newValue !== undefined)
187+
// Ensure sender account nonce is 1 in execution witness
188+
assert.equal(decodeVerkleLeafBasicData(hexToBytes(suffixDiff!.newValue!)).nonce, 1n)
189+
})
190+
})

packages/statemanager/src/statefulVerkleStateManager.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
6363
protected _debug: Debugger
6464
protected _caches?: Caches
6565

66+
preStateRoot: Uint8Array
6667
originalStorageCache: OriginalStorageCache
6768
verkleCrypto: VerkleCrypto
6869

@@ -75,7 +76,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
7576
// Post-state provided from the executionWitness.
7677
// Should not update. Used for comparing our computed post-state with the canonical one.
7778
private _postState: VerkleState = {}
78-
private _preState: VerkleState = {}
7979

8080
/**
8181
* StateManager is run in DEBUG mode (default: false)
@@ -88,6 +88,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
8888
protected readonly DEBUG: boolean = false
8989

9090
private keccakFunction: Function
91+
9192
constructor(opts: StatefulVerkleStateManagerOpts) {
9293
// Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
9394
// Additional window check is to prevent vite browser bundling (and potentially other) to break
@@ -118,6 +119,7 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
118119
this._caches = opts.caches
119120
this.keccakFunction = opts.common.customCrypto.keccak256 ?? keccak256
120121
this.verkleCrypto = opts.common.customCrypto.verkle
122+
this.preStateRoot = new Uint8Array(32) // Initial state root is zeroes
121123
}
122124

123125
/**
@@ -179,23 +181,9 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
179181
throw Error(errorMsg)
180182
}
181183

182-
// Populate the pre-state and post-state from the executionWitness
183-
const preStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => {
184-
const suffixDiffPairs = suffixDiffs.map(({ currentValue, suffix }) => {
185-
const key = `${stem}${padToEven(Number(suffix).toString(16))}`
186-
return {
187-
[key]: currentValue,
188-
}
189-
})
190-
191-
return suffixDiffPairs
192-
})
184+
this.preStateRoot = hexToBytes(executionWitness.parentStateRoot) // set prestate root if given
193185

194-
// also maintain a separate preState unaffected by any changes in _state
195-
this._preState = preStateRaw.reduce((prevValue, currentValue) => {
196-
const acc = { ...prevValue, ...currentValue }
197-
return acc
198-
}, {})
186+
// Populate the post-state from the executionWitness
199187

200188
const postStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => {
201189
const suffixDiffPairs = suffixDiffs.map(({ newValue, currentValue, suffix }) => {
@@ -219,7 +207,6 @@ export class StatefulVerkleStateManager implements StateManagerInterface {
219207

220208
this._postState = postState
221209

222-
this._debug(`initVerkleExecutionWitness preState=${JSON.stringify(this._preState)}`)
223210
this._debug(`initVerkleExecutionWitness postState=${JSON.stringify(this._postState)}`)
224211
}
225212

packages/vm/src/params.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,6 @@ export const paramsVM: ParamsDict = {
7070
// config
7171
historicalRootsLength: 8191, // The modulo parameter of the beaconroot ring buffer in the beaconroot stateful precompile
7272
},
73-
/**
74-
* Ethereum state using a unified verkle tree (experimental)
75-
*/
76-
6800: {
77-
// config
78-
historyStorageAddress: '0x0aae40965e6800cd9b1f4b05ff21581047e3f91e', // The address where the historical blockhashes are stored
79-
},
8073
/**
8174
* Execution layer triggerable withdrawals (experimental)
8275
*/

0 commit comments

Comments
 (0)