@@ -21,6 +21,7 @@ import { createVM, runBlock } from '../../src/index.ts'
2121import { setupPreConditions } from '../util.ts'
2222import { createCommonForFork , loadExecutionSpecFixtures } from './executionSpecTestLoader.ts'
2323import { compareBAL } from './util/balComparatorAI.ts'
24+ import { annotateFixture } from './util/perDirectoryReporter.ts'
2425
2526const customFixturesPath = process . env . TEST_PATH ?? '../execution-spec-tests'
2627const fixturesPath = path . resolve ( customFixturesPath )
@@ -77,8 +78,9 @@ if (fs.existsSync(fixturesPath) === false) {
7778 return
7879 }
7980
80- for ( const { id, fork, data } of fixtures ) {
81- it ( `${ fork } : ${ id } ` , async ( ) => {
81+ for ( const { id, fork, filePath, data } of fixtures ) {
82+ it ( `${ fork } : ${ id } ` , async ( { task } ) => {
83+ annotateFixture ( task , filePath , fixturesPath , 'blockchain tests' )
8284 await runBlockchainTestCase ( fork , data , assert , kzg )
8385 } , 360000 ) // 6 minutes
8486 }
@@ -121,124 +123,170 @@ export async function runBlockchainTestCase(
121123
122124 let parentBlock = genesisBlock
123125
124- for ( const {
125- rlp,
126- expectException,
127- blockHeader,
128- rlp_decoded,
129- blockAccessList,
130- } of testData . blocks ) {
131- const expectedHash = blockHeader ?. hash ?? rlp_decoded ?. blockHeader ?. hash ?? undefined
132- let block : Block | undefined
133- try {
134- block = createBlockFromRLP ( hexToBytes ( rlp ) , { common : vm . common , setHardfork : true } )
135- //t.equal(bytesToHex(block.serialize()), rlp, 'correct block RLP')
136- if ( expectedHash !== undefined ) {
137- //t.equal(bytesToHex(block.hash()), expectedHash, 'correct block hash')
138- }
139- const result = await runBlock ( vm , {
140- block,
141- root : parentBlock . header . stateRoot ,
142- setHardfork : true ,
143- } )
144- await vm . blockchain . putBlock ( block )
145- parentBlock = block
146- t . notExists ( expectException , `Should have thrown with: ${ expectException } ` )
126+ // Capture errors from block processing so post-state checks still run; this
127+ // surfaces state-divergence details even when a block throws unexpectedly.
128+ let runError : Error | undefined
147129
148- // Check if the block level access list is correct
149- if ( common . isActivatedEIP ( 7928 ) ) {
150- let balDiffMessage = ''
151- if ( blockAccessList !== undefined ) {
152- const expectedBAL = createBlockLevelAccessListFromJSON ( blockAccessList )
153- // Use the BAL comparator to show a colored diff of any mismatches
154- // Pass false to skip console output during test, we'll include it in the assertion
155- const { diffString } = compareBAL (
156- expectedBAL . raw ( ) ,
157- result . blockLevelAccessList ! . raw ( ) ,
158- false ,
159- )
160- balDiffMessage = diffString
130+ try {
131+ for ( const {
132+ rlp,
133+ expectException,
134+ blockHeader,
135+ rlp_decoded,
136+ blockAccessList,
137+ } of testData . blocks ) {
138+ const expectedHash = blockHeader ?. hash ?? rlp_decoded ?. blockHeader ?. hash ?? undefined
139+ let block : Block | undefined
140+ try {
141+ block = createBlockFromRLP ( hexToBytes ( rlp ) , { common : vm . common , setHardfork : true } )
142+ //t.equal(bytesToHex(block.serialize()), rlp, 'correct block RLP')
143+ if ( expectedHash !== undefined ) {
144+ //t.equal(bytesToHex(block.hash()), expectedHash, 'correct block hash')
145+ }
146+ const result = await runBlock ( vm , {
147+ block,
148+ root : parentBlock . header . stateRoot ,
149+ setHardfork : true ,
150+ } )
151+ await vm . blockchain . putBlock ( block )
152+ parentBlock = block
153+ t . notExists ( expectException , `Should have thrown with: ${ expectException } ` )
154+
155+ // Check if the block level access list is correct
156+ if ( common . isActivatedEIP ( 7928 ) ) {
157+ let balDiffMessage = ''
158+ if ( blockAccessList !== undefined ) {
159+ const expectedBAL = createBlockLevelAccessListFromJSON ( blockAccessList )
160+ // Use the BAL comparator to show a colored diff of any mismatches
161+ // Pass false to skip console output during test, we'll include it in the assertion
162+ const { diffString } = compareBAL (
163+ expectedBAL . raw ( ) ,
164+ result . blockLevelAccessList ! . raw ( ) ,
165+ false ,
166+ )
167+ balDiffMessage = diffString
168+ t . deepEqual (
169+ bytesToHex ( expectedBAL . hash ( ) ) ,
170+ bytesToHex ( block . header . blockAccessListHash ! ) ,
171+ `expected block level access list correct${ balDiffMessage } ` ,
172+ )
173+ }
161174 t . deepEqual (
162- bytesToHex ( expectedBAL . hash ( ) ) ,
175+ bytesToHex ( result . blockLevelAccessList ! . hash ( ) ) ,
163176 bytesToHex ( block . header . blockAccessListHash ! ) ,
164- `expected block level access list correct${ balDiffMessage } ` ,
177+ `generated block level access list correct${ balDiffMessage } ` ,
165178 )
166179 }
167- t . deepEqual (
168- bytesToHex ( result . blockLevelAccessList ! . hash ( ) ) ,
169- bytesToHex ( block . header . blockAccessListHash ! ) ,
170- `generated block level access list correct${ balDiffMessage } ` ,
180+ } catch ( e : any ) {
181+ if ( expectException === undefined ) {
182+ throw e
183+ }
184+ // Check if the block failed due to an expected exception
185+ t . exists (
186+ expectException ,
187+ `expectException should be defined. Error: ${ e . message } \n${ e . stack } ` ,
171188 )
172- }
173- } catch ( e : any ) {
174- if ( expectException === undefined ) {
175- throw e
176- }
177- // Check if the block failed due to an expected exception
178- t . exists (
179- expectException ,
180- `expectException should be defined. Error: ${ e . message } \n${ e . stack } ` ,
181- )
182189
183- if ( expectException . includes ( '|' ) === true ) {
184- const exceptions = expectException . split ( '|' )
185- let i = 0
186- while ( i < exceptions . length ) {
187- try {
188- t . isTrue (
189- exceptions [ i ] in exceptionMessages ,
190- `expectException: (${ exceptions [ i ] } ) should be in exceptionMessages. Error: ${ e . message } \n${ e . stack } ` ,
191- )
192- t . match (
193- e . message ,
194- exceptionMessages [ exceptions [ i ] ] ,
195- `Should have correct error for ${ exceptions [ i ] } ` ,
196- )
197- break
198- } catch {
199- if ( i === exceptions . length - 1 ) {
200- t . fail (
201- `Should have thrown one of the following exceptions: ${ expectException } . Threw: ${ e . message } ` ,
190+ if ( expectException . includes ( '|' ) === true ) {
191+ const exceptions = expectException . split ( '|' )
192+ let i = 0
193+ while ( i < exceptions . length ) {
194+ try {
195+ t . isTrue (
196+ exceptions [ i ] in exceptionMessages ,
197+ `expectException: (${ exceptions [ i ] } ) should be in exceptionMessages. Error: ${ e . message } \n${ e . stack } ` ,
198+ )
199+ t . match (
200+ e . message ,
201+ exceptionMessages [ exceptions [ i ] ] ,
202+ `Should have correct error for ${ exceptions [ i ] } ` ,
202203 )
203204 break
205+ } catch {
206+ if ( i === exceptions . length - 1 ) {
207+ t . fail (
208+ `Should have thrown one of the following exceptions: ${ expectException } . Threw: ${ e . message } ` ,
209+ )
210+ break
211+ }
212+ i ++
204213 }
205- i ++
206214 }
215+ } else {
216+ t . isTrue (
217+ expectException in exceptionMessages ,
218+ `expectException: (${ expectException } ) should be in exceptionMessages. Error: ${ e . message } \n${ e . stack } ` ,
219+ )
220+ // Check if the error message matches the expected exception
221+ t . match (
222+ e . message ,
223+ exceptionMessages [ expectException ] ,
224+ `Should have correct error for ${ expectException } -- got: ${ e . message } ` ,
225+ )
207226 }
208- } else {
209- t . isTrue (
210- expectException in exceptionMessages ,
211- `expectException: (${ expectException } ) should be in exceptionMessages. Error: ${ e . message } \n${ e . stack } ` ,
212- )
213- // Check if the error message matches the expected exception
214- t . match (
215- e . message ,
216- exceptionMessages [ expectException ] ,
217- `Should have correct error for ${ expectException } -- got: ${ e . message } ` ,
218- )
219227 }
220228 }
229+ } catch ( e : any ) {
230+ runError = e
221231 }
222232
223- // Check final state after all blocks are processed
224- const head = await blockchain . getCanonicalHeadBlock ( )
225- t . equal ( bytesToHex ( head . hash ( ) ) , testData . lastblockhash , `head block hash matches lastblockhash` )
233+ // Always check final state and post state, even if block processing threw.
234+ // Individual diffs go into `postFailures` so we can show them alongside any
235+ // block-processing error rather than masking them with an early throw.
236+ const postFailures : string [ ] = [ ]
226237
227- // Check post state
228- for ( const address of Object . keys ( testData . postState ) ) {
229- const account = await vm . stateManager . getAccount ( createAddressFromString ( address ) )
230- t . exists ( account , `account should be defined. Got: ${ address } ` )
231- const accountInfo = testData . postState [ address ]
232- t . equal ( account . balance , hexToBigInt ( accountInfo . balance ) , 'correct balance' )
233- t . equal ( account . nonce , hexToBigInt ( accountInfo . nonce ) , 'correct nonce' )
234- t . deepEqual ( account . codeHash , keccak_256 ( hexToBytes ( accountInfo . code ) ) , 'correct code' )
238+ try {
239+ const head = await blockchain . getCanonicalHeadBlock ( )
240+ t . equal (
241+ bytesToHex ( head . hash ( ) ) ,
242+ testData . lastblockhash ,
243+ `head block hash matches lastblockhash` ,
244+ )
245+ } catch ( e : any ) {
246+ postFailures . push ( `head block hash: ${ e ?. message ?? String ( e ) } ` )
247+ }
235248
236- for ( const [ key , value ] of Object . entries ( accountInfo . storage ) ) {
237- const keyBytes = setLengthLeft ( hexToBytes ( key as `0x${string } `) , 32 )
238- const storage = await vm . stateManager . getStorage ( createAddressFromString ( address ) , keyBytes )
239- t . equal ( bytesToHex ( storage ) , value , 'correct storage' )
249+ const postState = testData . postState ?? { }
250+ for ( const address of Object . keys ( postState ) ) {
251+ try {
252+ const account = await vm . stateManager . getAccount ( createAddressFromString ( address ) )
253+ t . exists ( account , `account should be defined. Got: ${ address } ` )
254+ const accountInfo = postState [ address ]
255+ t . equal ( account . balance , hexToBigInt ( accountInfo . balance ) , `correct balance (${ address } )` )
256+ t . equal ( account . nonce , hexToBigInt ( accountInfo . nonce ) , `correct nonce (${ address } )` )
257+ t . deepEqual (
258+ account . codeHash ,
259+ keccak_256 ( hexToBytes ( accountInfo . code ) ) ,
260+ `correct code (${ address } )` ,
261+ )
262+
263+ for ( const [ key , value ] of Object . entries ( accountInfo . storage ) ) {
264+ const keyBytes = setLengthLeft ( hexToBytes ( key as `0x${string } `) , 32 )
265+ const storage = await vm . stateManager . getStorage ( createAddressFromString ( address ) , keyBytes )
266+ t . equal ( bytesToHex ( storage ) , value , `correct storage[${ key } ] (${ address } )` )
267+ }
268+ } catch ( e : any ) {
269+ postFailures . push ( e ?. message ?? String ( e ) )
240270 }
241271 }
272+
273+ if ( runError === undefined && postFailures . length === 0 ) return
274+
275+ // Build a combined failure. If a block-run error was the root cause, keep
276+ // its original stack so vitest's source-mapped frames still point there.
277+ const sections : string [ ] = [ ]
278+ if ( runError !== undefined ) sections . push ( runError . message )
279+ if ( postFailures . length > 0 ) {
280+ const header =
281+ postFailures . length === 1
282+ ? `Post-run state issue:`
283+ : `Post-run state issues (${ postFailures . length } ):`
284+ sections . push ( [ header , ...postFailures . map ( ( m ) => ` - ${ m } ` ) ] . join ( '\n' ) )
285+ }
286+
287+ const combined = new Error ( sections . join ( '\n\n' ) )
288+ if ( runError ?. stack !== undefined ) combined . stack = runError . stack
289+ throw combined
242290}
243291
244292// EthJS error messages mapped to expected exception types
0 commit comments