Skip to content

Commit 185fb0a

Browse files
authored
vm: adjust test runner with verkle state management (#3848)
* vm: first commit general state test runner * vm: undo lowercase * add verkle common config * common: add verkle chain * vm: add statemanager option to tester * vm: modularize stateTree and stateManager * vm: minor adjustments * tester: blockchain test runner
1 parent e4d55d3 commit 185fb0a

File tree

5 files changed

+156
-30
lines changed

5 files changed

+156
-30
lines changed

packages/common/src/chains.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export const Mainnet: ChainConfig = {
121121
name: 'osaka',
122122
block: null,
123123
},
124+
{
125+
name: 'verkle',
126+
block: null,
127+
},
124128
],
125129
bootstrapNodes: [
126130
{

packages/vm/test/tester/config.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Common, Hardfork, Mainnet, createCustomCommon } from '@ethereumjs/common'
2-
import { type KZG } from '@ethereumjs/util'
2+
import * as verkle from 'micro-eth-signer/verkle'
33
import * as path from 'path'
44

55
import type { HardforkTransitionConfig } from '@ethereumjs/common'
6+
import type { KZG } from '@ethereumjs/util'
67

78
/**
89
* Default tests path (git submodule: ethereum-tests)
@@ -109,6 +110,7 @@ const normalHardforks = [
109110
'cancun',
110111
'prague',
111112
'osaka',
113+
'verkle',
112114
]
113115

114116
const transitionNetworks = {
@@ -278,6 +280,69 @@ function setupCommonWithNetworks(network: string, ttd?: number, timestamp?: numb
278280
return common
279281
}
280282

283+
/**
284+
* Returns a common instance configured for verkle
285+
* @param network Network target (this can include EIPs, such as Byzantium+2537+2929)
286+
* @param ttd If set: total terminal difficulty to switch to merge
287+
* @returns
288+
*/
289+
function setupCommonForVerkle(network: string, timestamp?: number, kzg?: KZG) {
290+
let ttd
291+
// hard fork that verkle tests are filled on
292+
const hfName = 'shanghai'
293+
const mainnetCommon = new Common({ chain: Mainnet, hardfork: hfName })
294+
const hardforks = mainnetCommon.hardforks().slice(0, 17) // skip hardforks after Shanghai
295+
const testHardforks: HardforkTransitionConfig[] = []
296+
for (const hf of hardforks) {
297+
// check if we enable this hf
298+
// disable dao hf by default (if enabled at block 0 forces the first 10 blocks to have dao-hard-fork in extraData of block header)
299+
if (mainnetCommon.gteHardfork(hf.name) && hf.name !== Hardfork.Dao) {
300+
// this hardfork should be activated at block 0
301+
testHardforks.push({
302+
name: hf.name,
303+
// Current type definition Partial<Chain> in Common is currently not allowing to pass in forkHash
304+
// forkHash: hf.forkHash,
305+
block: 0,
306+
})
307+
} else {
308+
// disable hardforks newer than the test hardfork (but do add "support" for it, it just never gets activated)
309+
if (
310+
(ttd === undefined && timestamp === undefined) ||
311+
(hf.name === 'paris' && ttd !== undefined)
312+
) {
313+
testHardforks.push({
314+
name: hf.name,
315+
block: null,
316+
})
317+
}
318+
if (timestamp !== undefined && hf.name !== Hardfork.Dao) {
319+
testHardforks.push({
320+
name: hf.name,
321+
block: null,
322+
timestamp,
323+
})
324+
}
325+
}
326+
}
327+
328+
testHardforks.push({ name: 'verkle', block: 1 })
329+
const common = createCustomCommon(
330+
{
331+
hardforks: testHardforks,
332+
defaultHardfork: 'verkle',
333+
},
334+
Mainnet,
335+
{ eips: [2935, 3607], customCrypto: { kzg, verkle } },
336+
)
337+
338+
// Activate EIPs
339+
const eips = network.match(/(?<=\+)(.\d+)/g)
340+
if (eips) {
341+
common.setEIPs(eips.map((e: string) => parseInt(e)))
342+
}
343+
return common
344+
}
345+
281346
/**
282347
* Returns a Common for the given network (a test parameter)
283348
* @param network - the network field of a test.
@@ -292,6 +357,10 @@ export function getCommon(network: string, kzg?: KZG): Common {
292357
network = retestethAlias[network as keyof typeof retestethAlias]
293358
}
294359
let networkLowercase = network.toLowerCase()
360+
// Special handler for verkle tests
361+
if (networkLowercase.includes('verkle')) {
362+
return setupCommonForVerkle(network, undefined, kzg)
363+
}
295364
if (network.includes('+')) {
296365
const index = network.indexOf('+')
297366
networkLowercase = network.slice(0, index).toLowerCase()

packages/vm/test/tester/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ async function runTests() {
142142
profile: boolean
143143
bls: EVMBLSInterface
144144
bn254: EVMBN254Interface
145+
stateManager: string
145146
} = {
146147
forkConfigVM: FORK_CONFIG_VM,
147148
forkConfigTestSuite: FORK_CONFIG_TEST_SUITE,
@@ -156,6 +157,7 @@ async function runTests() {
156157
bls,
157158
profile: RUN_PROFILER,
158159
bn254,
160+
stateManager: argv.stateManager,
159161
}
160162

161163
/**

packages/vm/test/tester/runners/BlockchainTestsRunner.ts

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ConsensusAlgorithm } from '@ethereumjs/common'
44
import { Ethash } from '@ethereumjs/ethash'
55
import { MerklePatriciaTrie } from '@ethereumjs/mpt'
66
import { RLP } from '@ethereumjs/rlp'
7-
import { Caches, MerkleStateManager } from '@ethereumjs/statemanager'
7+
import { Caches, MerkleStateManager, StatefulVerkleStateManager } from '@ethereumjs/statemanager'
88
import { createTxFromRLP } from '@ethereumjs/tx'
99
import {
1010
MapDB,
@@ -15,14 +15,16 @@ import {
1515
stripHexPrefix,
1616
toBytes,
1717
} from '@ethereumjs/util'
18+
import { createVerkleTree } from '@ethereumjs/verkle'
1819

1920
import { buildBlock, createVM, runBlock } from '../../../src/index.js'
2021
import { setupPreConditions, verifyPostConditions } from '../../util.js'
2122

2223
import type { Block } from '@ethereumjs/block'
2324
import type { Blockchain, ConsensusDict } from '@ethereumjs/blockchain'
24-
import type { Common } from '@ethereumjs/common'
25+
import type { Common, StateManagerInterface } from '@ethereumjs/common'
2526
import type { PrefixedHexString } from '@ethereumjs/util'
27+
import type { VerkleTree } from '@ethereumjs/verkle'
2628
import type * as tape from 'tape'
2729

2830
function formatBlockHeader(data: any) {
@@ -47,12 +49,23 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
4749
common.setHardforkBy({ blockNumber: 0 })
4850

4951
let cacheDB = new MapDB()
50-
let state = new MerklePatriciaTrie({ useKeyHashing: true, common })
51-
let stateManager = new MerkleStateManager({
52-
caches: new Caches(),
53-
trie: state,
54-
common,
55-
})
52+
let stateTree: MerklePatriciaTrie | VerkleTree
53+
let stateManager: StateManagerInterface
54+
55+
if (options.stateManager === 'verkle') {
56+
stateTree = await createVerkleTree()
57+
stateManager = new StatefulVerkleStateManager({
58+
trie: stateTree,
59+
common: options.common,
60+
})
61+
} else {
62+
stateTree = new MerklePatriciaTrie({ useKeyHashing: true, common })
63+
stateManager = new MerkleStateManager({
64+
caches: new Caches(),
65+
trie: stateTree,
66+
common,
67+
})
68+
}
5669

5770
let validatePow = false
5871
// Only run with block validation when sealEngine present in test file
@@ -86,7 +99,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
8699
})
87100

88101
if (validatePow) {
89-
;(blockchain.consensus as EthashConsensus)._ethash!.cacheDB = cacheDB as any
102+
;(blockchain.consensus as EthashConsensus)._ethash!.cacheDB = cacheDB
90103
}
91104

92105
const begin = Date.now()
@@ -190,7 +203,32 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
190203
await blockBuilder.revert() // will only revert if checkpointed
191204
}
192205

193-
const block = createBlockFromRLP(blockRlp, { common, setHardfork: true })
206+
let block: Block
207+
if (options.stateManager === 'verkle') {
208+
currentBlock = BigInt(raw.blockHeader.number)
209+
common.setHardforkBy({
210+
blockNumber: currentBlock,
211+
timestamp: BigInt(raw.blockHeader.timestamp),
212+
})
213+
// Create the block from the JSON block data since the RLP doesn't include the execution witness
214+
block = createBlock(
215+
{
216+
header: raw.blockHeader,
217+
transactions: raw.transactions,
218+
uncleHeaders: raw.uncleHeaders,
219+
withdrawals: raw.withdrawals,
220+
executionWitness: raw.witness,
221+
},
222+
{
223+
common,
224+
setHardfork: true,
225+
},
226+
)
227+
} else {
228+
const blockRLP = hexToBytes(raw.rlp as PrefixedHexString)
229+
block = createBlockFromRLP(blockRLP, { common, setHardfork: true })
230+
}
231+
194232
await blockchain.putBlock(block)
195233

196234
// This is a trick to avoid generating the canonical genesis
@@ -224,7 +262,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
224262
const headBlock = await (vm.blockchain as Blockchain).getIteratorHead()
225263
await vm.stateManager.setStateRoot(headBlock.header.stateRoot)
226264
} else {
227-
await verifyPostConditions(state, testData.postState, t)
265+
await verifyPostConditions(stateTree, testData.postState, t)
228266
}
229267

230268
throw e
@@ -242,7 +280,7 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
242280
}
243281

244282
t.equal(
245-
bytesToHex((blockchain as any)._headHeaderHash),
283+
bytesToHex(blockchain['_headHeaderHash']),
246284
'0x' + testData.lastblockhash,
247285
'correct last header block',
248286
)
@@ -251,6 +289,6 @@ export async function runBlockchainTest(options: any, testData: any, t: tape.Tes
251289
const timeSpent = `${(end - begin) / 1000} secs`
252290
t.comment(`Time: ${timeSpent}`)
253291

254-
// @ts-ignore Explicitly delete objects for memory optimization (early GC)
255-
common = blockchain = state = stateManager = vm = cacheDB = null // eslint-disable-line
292+
// Explicitly delete objects for memory optimization (early GC)
293+
common = blockchain = stateTree = stateManager = vm = cacheDB = null as any
256294
}

packages/vm/test/tester/runners/GeneralStateTestsRunner.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@ import { Block } from '@ethereumjs/block'
33
import { createBlockchain } from '@ethereumjs/blockchain'
44
import { type InterpreterStep } from '@ethereumjs/evm'
55
import { MerklePatriciaTrie } from '@ethereumjs/mpt'
6-
import { Caches, MerkleStateManager } from '@ethereumjs/statemanager'
6+
import { Caches, MerkleStateManager, StatefulVerkleStateManager } from '@ethereumjs/statemanager'
77
import {
88
Account,
9+
MapDB,
910
bytesToHex,
1011
createAddressFromString,
1112
equalsBytes,
1213
toBytes,
1314
} from '@ethereumjs/util'
15+
import { createVerkleTree } from '@ethereumjs/verkle'
16+
import * as verkle from 'micro-eth-signer/verkle'
1417

1518
import { createVM, runTx } from '../../../src/index.js'
1619
import { makeBlockFromEnv, makeTx, setupPreConditions } from '../../util.js'
1720

21+
import type { StateManagerInterface } from '@ethereumjs/common'
22+
import type { VerkleTree } from '@ethereumjs/verkle'
1823
import type * as tape from 'tape'
24+
const loadVerkleCrypto = () => Promise.resolve(verkle)
1925

2026
function parseTestCases(
2127
forkConfigTestSuite: string,
@@ -75,23 +81,34 @@ async function runTestCase(options: any, testData: any, t: tape.Test) {
7581
const begin = Date.now()
7682
// Copy the common object to not create long-lasting
7783
// references in memory which might prevent GC
78-
const common = options.common.copy()
84+
let common = options.common.copy()
7985
// Have to create a blockchain with empty block as genesisBlock for Merge
8086
// Otherwise mainnet genesis will throw since this has difficulty nonzero
8187
const genesisBlock = new Block(undefined, undefined, undefined, undefined, { common })
82-
const blockchain = await createBlockchain({ genesisBlock, common })
83-
const state = new MerklePatriciaTrie({ useKeyHashing: true, common })
84-
const stateManager = new MerkleStateManager({
85-
caches: new Caches(),
86-
trie: state,
87-
common,
88-
})
88+
let blockchain = await createBlockchain({ genesisBlock, common })
89+
let stateTree: VerkleTree | MerklePatriciaTrie
90+
let stateManager: StateManagerInterface
91+
if (options.stateManager === 'verkle') {
92+
const verkleCrypto = await loadVerkleCrypto()
93+
stateTree = await createVerkleTree({ verkleCrypto, db: new MapDB() })
94+
stateManager = new StatefulVerkleStateManager({
95+
common,
96+
trie: stateTree,
97+
})
98+
} else {
99+
stateTree = new MerklePatriciaTrie({ useKeyHashing: true, common })
100+
stateManager = new MerkleStateManager({
101+
caches: new Caches(),
102+
trie: stateTree,
103+
common,
104+
})
105+
}
89106

90107
const evmOpts = {
91108
bls: options.bls,
92109
bn254: options.bn254,
93110
}
94-
const vm = await createVM({
111+
let vm = await createVM({
95112
stateManager,
96113
common,
97114
blockchain,
@@ -107,8 +124,6 @@ async function runTestCase(options: any, testData: any, t: tape.Test) {
107124
try {
108125
tx = makeTx(testData.transaction, { common })
109126
} catch (e: any) {
110-
console.log('error: ', e)
111-
console.log('testData.transaction: ', testData.transaction)
112127
execInfo = 'tx instantiation exception'
113128
}
114129

@@ -180,9 +195,7 @@ async function runTestCase(options: any, testData: any, t: tape.Test) {
180195
vm.evm.events!.removeListener('step', stepHandler)
181196
vm.events.removeListener('afterTx', afterTxHandler)
182197

183-
// @ts-ignore Explicitly delete objects for memory optimization (early GC)
184-
// TODO FIXME
185-
//common = blockchain = state = stateManager = evm = vm = null // eslint-disable-line
198+
common = blockchain = stateTree = stateManager = vm = null as any
186199

187200
return parseFloat(timeSpent)
188201
}

0 commit comments

Comments
 (0)