11package stagedsync
22
33import (
4+ "fmt"
45 "math/big"
56 "testing"
67
@@ -96,6 +97,10 @@ func TestCreateBALOrdering(t *testing.T) {
9697}
9798
9899func addStorageRead (readSets map [int ]state.ReadSet , txIdx int , addr accounts.Address , slot accounts.StorageKey ) {
100+ addStorageReadVal (readSets , txIdx , addr , slot , * uint256 .NewInt (0 ))
101+ }
102+
103+ func addStorageReadVal (readSets map [int ]state.ReadSet , txIdx int , addr accounts.Address , slot accounts.StorageKey , val uint256.Int ) {
99104 rs := readSets [txIdx ]
100105 if rs == nil {
101106 rs = state.ReadSet {}
@@ -105,6 +110,7 @@ func addStorageRead(readSets map[int]state.ReadSet, txIdx int, addr accounts.Add
105110 Address : addr ,
106111 Path : state .StoragePath ,
107112 Key : slot ,
113+ Val : val ,
108114 })
109115}
110116
@@ -117,7 +123,7 @@ func addBalanceRead(readSets map[int]state.ReadSet, txIdx int, addr accounts.Add
117123 rs .Set (state.VersionedRead {
118124 Address : addr ,
119125 Path : state .BalancePath ,
120- Val : value ,
126+ Val : * uint256 . NewInt ( value ) ,
121127 })
122128}
123129
@@ -148,3 +154,225 @@ func recordAll(io *state.VersionedIO, reads map[int]state.ReadSet, writes map[in
148154 io .RecordWrites (state.Version {TxIndex : txIdx }, ws )
149155 }
150156}
157+
158+ // TestBALBlock943Direct constructs the BAL for bal-devnet-2 block 943
159+ // (first Amsterdam block, 0 user txs) directly and verifies the hash
160+ // matches the known-good value from the devnet header.
161+ //
162+ // Block 943 system calls:
163+ // - txIndex=-1: EIP-4788 beacon root (2 storage writes), EIP-2935 block hash (1 storage write)
164+ // - txIndex=0 (finalize): EIP-7002 withdrawal request dequeue (4 SLOADs + 4 SSTOREs, all net-zero),
165+ // EIP-7251 consolidation request dequeue (4 SLOADs + 4 SSTOREs, all net-zero)
166+ //
167+ // The system address (0xff..fe) is filtered out per EIP-7928.
168+ func TestBALBlock943Direct (t * testing.T ) {
169+ expectedHash := common .HexToHash ("0x0e9aff2d3f1c6d5083afd44ef786e54858d50004845d5ae2f0437dd61b83d00d" )
170+
171+ // System contract addresses (sorted lexicographically)
172+ eip7002Addr := accounts .InternAddress (common .HexToAddress ("0x00000961Ef480Eb55e80D19ad83579A64c007002" )) // EIP-7002 Withdrawal Requests
173+ eip7251Addr := accounts .InternAddress (common .HexToAddress ("0x0000BBdDc7CE488642fb579F8B00f3a590007251" )) // EIP-7251 Consolidation Requests
174+ eip2935Addr := accounts .InternAddress (common .HexToAddress ("0x0000F90827F1C53a10cb7A02335B175320002935" )) // EIP-2935 Block Hash Store
175+ eip4788Addr := accounts .InternAddress (common .HexToAddress ("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" )) // EIP-4788 Beacon Roots
176+
177+ // Storage slots and values for block 943 (from RPC)
178+ slot4788Timestamp := accounts .InternKey (common .BigToHash (big .NewInt (0x1747 ))) // timestamp % 8191
179+ slot4788Root := accounts .InternKey (common .BigToHash (big .NewInt (0x3746 ))) // (timestamp % 8191) + 8191
180+ slot2935 := accounts .InternKey (common .BigToHash (big .NewInt (0x3ae ))) // (blockNum-1) % 8191
181+
182+ val4788Timestamp := uint256 .NewInt (0x69862afc )
183+ val4788Root := new (uint256.Int )
184+ val4788Root .SetBytes (common .Hex2Bytes ("6d29857d40bc9c49248f83435de3c0f4df64b701e77c8550a40b4981802ccc9c" ))
185+ val2935 := new (uint256.Int )
186+ val2935 .SetBytes (common .Hex2Bytes ("52b76c49b7b2892ef00b543e27480f8e57ca399e8ddf4a2aba127b263988885c" ))
187+
188+ // EIP-7002 and EIP-7251 dequeue contracts read slots 0-3 (excess, count, head, tail)
189+ // when queue is empty. All SSTOREs write 0 back to zero-valued slots (net-zero → reads only).
190+ slot0 := accounts .InternKey (common .BigToHash (big .NewInt (0 )))
191+ slot1 := accounts .InternKey (common .BigToHash (big .NewInt (1 )))
192+ slot2 := accounts .InternKey (common .BigToHash (big .NewInt (2 )))
193+ slot3 := accounts .InternKey (common .BigToHash (big .NewInt (3 )))
194+
195+ // Construct BAL directly. Addresses must be sorted lexicographically:
196+ // 0x00000961... < 0x0000BBdD... < 0x0000F908... < 0x000F3df6...
197+ bal := types.BlockAccessList {
198+ // EIP-7002: only storage reads (empty queue, all writes are net-zero)
199+ {
200+ Address : eip7002Addr ,
201+ StorageReads : []accounts.StorageKey {slot0 , slot1 , slot2 , slot3 },
202+ },
203+ // EIP-7251: only storage reads (empty queue, all writes are net-zero)
204+ {
205+ Address : eip7251Addr ,
206+ StorageReads : []accounts.StorageKey {slot0 , slot1 , slot2 , slot3 },
207+ },
208+ // EIP-2935: 1 storage change at accessIndex 0 (system call txIndex=-1)
209+ {
210+ Address : eip2935Addr ,
211+ StorageChanges : []* types.SlotChanges {
212+ {
213+ Slot : slot2935 ,
214+ Changes : []* types.StorageChange {{Index : 0 , Value : * val2935 }},
215+ },
216+ },
217+ },
218+ // EIP-4788: 2 storage changes at accessIndex 0 (system call txIndex=-1)
219+ {
220+ Address : eip4788Addr ,
221+ StorageChanges : []* types.SlotChanges {
222+ {
223+ Slot : slot4788Timestamp ,
224+ Changes : []* types.StorageChange {{Index : 0 , Value : * val4788Timestamp }},
225+ },
226+ {
227+ Slot : slot4788Root ,
228+ Changes : []* types.StorageChange {{Index : 0 , Value : * val4788Root }},
229+ },
230+ },
231+ },
232+ }
233+
234+ if err := bal .Validate (); err != nil {
235+ t .Fatalf ("BAL validation failed: %v" , err )
236+ }
237+
238+ got := bal .Hash ()
239+ t .Logf ("BAL hash: %s" , got .Hex ())
240+ t .Logf ("BAL debug: %s" , bal .DebugString ())
241+ if got != expectedHash {
242+ t .Fatalf ("BAL hash mismatch:\n got: %s\n expected: %s" , got .Hex (), expectedHash .Hex ())
243+ }
244+ }
245+
246+ // TestBALBlock943ViaVersionedIO constructs the BAL through VersionedIO
247+ // (the same path used during block execution) and verifies the hash.
248+ func TestBALBlock943ViaVersionedIO (t * testing.T ) {
249+ expectedHash := common .HexToHash ("0x0e9aff2d3f1c6d5083afd44ef786e54858d50004845d5ae2f0437dd61b83d00d" )
250+
251+ // System address, system contracts
252+ systemAddr := accounts .InternAddress (common .HexToAddress ("0xfffffffffffffffffffffffffffffffffffffffe" ))
253+ eip7002Addr := accounts .InternAddress (common .HexToAddress ("0x00000961Ef480Eb55e80D19ad83579A64c007002" ))
254+ eip7251Addr := accounts .InternAddress (common .HexToAddress ("0x0000BBdDc7CE488642fb579F8B00f3a590007251" ))
255+ eip2935Addr := accounts .InternAddress (common .HexToAddress ("0x0000F90827F1C53a10cb7A02335B175320002935" ))
256+ eip4788Addr := accounts .InternAddress (common .HexToAddress ("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ))
257+
258+ // Storage slots and values
259+ slot4788Timestamp := accounts .InternKey (common .BigToHash (big .NewInt (0x1747 )))
260+ slot4788Root := accounts .InternKey (common .BigToHash (big .NewInt (0x3746 )))
261+ slot2935 := accounts .InternKey (common .BigToHash (big .NewInt (0x3ae )))
262+ slot0 := accounts .InternKey (common .BigToHash (big .NewInt (0 )))
263+ slot1 := accounts .InternKey (common .BigToHash (big .NewInt (1 )))
264+ slot2 := accounts .InternKey (common .BigToHash (big .NewInt (2 )))
265+ slot3 := accounts .InternKey (common .BigToHash (big .NewInt (3 )))
266+
267+ val4788Timestamp := uint256 .NewInt (0x69862afc )
268+ val4788Root := new (uint256.Int )
269+ val4788Root .SetBytes (common .Hex2Bytes ("6d29857d40bc9c49248f83435de3c0f4df64b701e77c8550a40b4981802ccc9c" ))
270+ val2935 := new (uint256.Int )
271+ val2935 .SetBytes (common .Hex2Bytes ("52b76c49b7b2892ef00b543e27480f8e57ca399e8ddf4a2aba127b263988885c" ))
272+
273+ // Block 943 has 0 user txs. Tasks: txIndex=-1 (Initialize), txIndex=0 (Finalize).
274+ // NewVersionedIO(numTx) where numTx is the count of user transactions (0).
275+ // However, the Finalize task at txIndex=0 needs to be accommodated.
276+ vio := state .NewVersionedIO (0 )
277+
278+ readSets := map [int ]state.ReadSet {}
279+ writeSets := map [int ]state.VersionedWrites {}
280+
281+ // === txIndex=-1: Initialize system calls (EIP-4788, EIP-2935) ===
282+
283+ // System address balance reads/writes (from SubBalance with Gnosis exception)
284+ // Balance is 0x24ac0a — read and write same value (no-op write filtered)
285+ systemBalance := uint256 .NewInt (0x24ac0a )
286+ addBalanceRead (readSets , - 1 , systemAddr , systemBalance .Uint64 ())
287+ addBalanceWrite (writeSets , - 1 , systemAddr , systemBalance .Uint64 ())
288+
289+ // EIP-4788 contract: balance read+write (no-op), 2 storage writes
290+ addBalanceRead (readSets , - 1 , eip4788Addr , 0 )
291+ addBalanceWrite (writeSets , - 1 , eip4788Addr , 0 )
292+ addStorageWrite (writeSets , - 1 , eip4788Addr , slot4788Timestamp , val4788Timestamp .Uint64 ())
293+ writeSets [- 1 ] = append (writeSets [- 1 ], & state.VersionedWrite {
294+ Address : eip4788Addr ,
295+ Path : state .StoragePath ,
296+ Key : slot4788Root ,
297+ Version : state.Version {TxIndex : - 1 },
298+ Val : * val4788Root ,
299+ })
300+
301+ // EIP-2935 contract: balance read+write (no-op), 1 storage write
302+ addBalanceRead (readSets , - 1 , eip2935Addr , 0 )
303+ addBalanceWrite (writeSets , - 1 , eip2935Addr , 0 )
304+ writeSets [- 1 ] = append (writeSets [- 1 ], & state.VersionedWrite {
305+ Address : eip2935Addr ,
306+ Path : state .StoragePath ,
307+ Key : slot2935 ,
308+ Version : state.Version {TxIndex : - 1 },
309+ Val : * val2935 ,
310+ })
311+
312+ // === txIndex=0: Finalize system calls (EIP-7002, EIP-7251 dequeue) ===
313+ // Empty queue: read slots 0-3, write 0 back to each (net-zero → reads only)
314+
315+ // EIP-7002: balance read+write (no-op), storage reads + net-zero writes
316+ addBalanceRead (readSets , 0 , eip7002Addr , 0 )
317+ addBalanceWrite (writeSets , 0 , eip7002Addr , 0 )
318+ addStorageRead (readSets , 0 , eip7002Addr , slot0 )
319+ addStorageRead (readSets , 0 , eip7002Addr , slot1 )
320+ addStorageRead (readSets , 0 , eip7002Addr , slot2 )
321+ addStorageRead (readSets , 0 , eip7002Addr , slot3 )
322+ addStorageWrite (writeSets , 0 , eip7002Addr , slot0 , 0 ) // net-zero: write 0 to 0-valued slot
323+ addStorageWrite (writeSets , 0 , eip7002Addr , slot1 , 0 )
324+ addStorageWrite (writeSets , 0 , eip7002Addr , slot2 , 0 )
325+ addStorageWrite (writeSets , 0 , eip7002Addr , slot3 , 0 )
326+
327+ // EIP-7251: balance read+write (no-op), storage reads + net-zero writes
328+ addBalanceRead (readSets , 0 , eip7251Addr , 0 )
329+ addBalanceWrite (writeSets , 0 , eip7251Addr , 0 )
330+ addStorageRead (readSets , 0 , eip7251Addr , slot0 )
331+ addStorageRead (readSets , 0 , eip7251Addr , slot1 )
332+ addStorageRead (readSets , 0 , eip7251Addr , slot2 )
333+ addStorageRead (readSets , 0 , eip7251Addr , slot3 )
334+ addStorageWrite (writeSets , 0 , eip7251Addr , slot0 , 0 )
335+ addStorageWrite (writeSets , 0 , eip7251Addr , slot1 , 0 )
336+ addStorageWrite (writeSets , 0 , eip7251Addr , slot2 , 0 )
337+ addStorageWrite (writeSets , 0 , eip7251Addr , slot3 , 0 )
338+
339+ // System address balance from Finalize Transfer calls (no-op)
340+ addBalanceRead (readSets , 0 , systemAddr , systemBalance .Uint64 ())
341+ addBalanceWrite (writeSets , 0 , systemAddr , systemBalance .Uint64 ())
342+
343+ recordAll (vio , readSets , writeSets )
344+
345+ bal := vio .AsBlockAccessList ()
346+
347+ t .Logf ("BAL accounts: %d" , len (bal ))
348+ for i , ac := range bal {
349+ t .Logf (" [%d] %s: storage_changes=%d storage_reads=%d balance_changes=%d nonce_changes=%d code_changes=%d" ,
350+ i , ac .Address .Value ().Hex (),
351+ len (ac .StorageChanges ), len (ac .StorageReads ),
352+ len (ac .BalanceChanges ), len (ac .NonceChanges ), len (ac .CodeChanges ))
353+ for _ , sc := range ac .StorageChanges {
354+ for _ , ch := range sc .Changes {
355+ t .Logf (" slot %s [%d] -> %s" , sc .Slot .Value ().Hex (), ch .Index , ch .Value .Hex ())
356+ }
357+ }
358+ for _ , sr := range ac .StorageReads {
359+ t .Logf (" read slot %s" , sr .Value ().Hex ())
360+ }
361+ }
362+
363+ got := bal .Hash ()
364+ t .Logf ("BAL hash: %s" , got .Hex ())
365+ if got != expectedHash {
366+ t .Logf ("BAL debug: %s" , bal .DebugString ())
367+ t .Fatalf ("BAL hash mismatch:\n got: %s\n expected: %s" , got .Hex (), expectedHash .Hex ())
368+ }
369+
370+ // Verify system address was filtered out
371+ for _ , ac := range bal {
372+ if ac .Address == systemAddr {
373+ t .Fatal ("system address should have been filtered from BAL" )
374+ }
375+ }
376+
377+ _ = fmt .Sprintf // suppress unused import
378+ }
0 commit comments