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 <contact@rockwaterweb.com>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy