55 "os"
66 "path/filepath"
77
8+ "github.com/erigontech/erigon/common"
89 "github.com/erigontech/erigon/common/dbg"
910 "github.com/erigontech/erigon/common/log/v3"
1011 "github.com/erigontech/erigon/db/kv"
@@ -14,40 +15,31 @@ import (
1415 "github.com/erigontech/erigon/execution/types"
1516)
1617
17- func CreateBAL (blockNum uint64 , txIO * state.VersionedIO , dataDir string ) types.BlockAccessList {
18+ func CreateBAL (blockNum uint64 , txIO * state.VersionedIO , dataDir string , logger log. Logger ) types.BlockAccessList {
1819 bal := txIO .AsBlockAccessList ()
1920 if dbg .TraceBlockAccessLists {
20- writeBALToFile (bal , blockNum , dataDir )
21+ writeBALToFile (bal , dataDir , fmt . Sprintf ( "computed_bal_%d.txt" , blockNum ), logger )
2122 }
2223 return bal
2324}
2425
25- // writeBALToFile writes the Block Access List to a text file for debugging/analysis
26- func writeBALToFile (bal types.BlockAccessList , blockNum uint64 , dataDir string ) {
26+ // writeBALToFile dumps the Block Access List to <dataDir>/bal/<name> for debugging/analysis.
27+ func writeBALToFile (bal types.BlockAccessList , dataDir string , name string , logger log. Logger ) {
2728 if dataDir == "" {
2829 return
2930 }
30-
3131 balDir := filepath .Join (dataDir , "bal" )
32- if err := os .MkdirAll (balDir , 0755 ); err != nil {
33- log .Warn ("Failed to create BAL directory" , "dir" , balDir , "error " , err )
32+ if err := os .MkdirAll (balDir , 0o755 ); err != nil {
33+ logger .Warn ("failed to create BAL debug directory" , "dir" , balDir , "err " , err )
3434 return
3535 }
36-
37- filename := filepath .Join (balDir , fmt .Sprintf ("bal_block_%d.txt" , blockNum ))
38-
39- file , err := os .Create (filename )
40- if err != nil {
41- log .Warn ("Failed to create BAL file" , "blockNum" , blockNum , "error" , err )
42- return
36+ path := filepath .Join (balDir , name )
37+ if err := os .WriteFile (path , []byte (bal .DebugString ()), 0o644 ); err != nil {
38+ logger .Warn ("failed to write BAL debug file" , "path" , path , "err" , err )
4339 }
44- defer file .Close ()
45-
46- fmt .Fprintf (file , "Block Access List for Block %d\n " , blockNum )
47- bal .DebugPrint (file )
4840}
4941
50- func ProcessBAL (tx kv.TemporalRwTx , h * types.Header , vio * state.VersionedIO , isEIP7928 bool , experimental bool , dataDir string ) error {
42+ func ProcessBAL (tx kv.TemporalRwTx , h * types.Header , vio * state.VersionedIO , isEIP7928 bool , experimental bool , dataDir string , logger log. Logger ) error {
5143 if ! isEIP7928 && ! experimental {
5244 return nil
5345 }
@@ -56,97 +48,63 @@ func ProcessBAL(tx kv.TemporalRwTx, h *types.Header, vio *state.VersionedIO, isE
5648 }
5749 blockNum := h .Number .Uint64 ()
5850 blockHash := h .Hash ()
59- bal := CreateBAL (blockNum , vio , dataDir )
60- err := bal .Validate ()
51+ computedBlockBal := CreateBAL (blockNum , vio , dataDir , logger )
52+ err := computedBlockBal .Validate ()
6153 if err != nil {
6254 return fmt .Errorf ("block %d: invalid computed block access list: %w" , blockNum , err )
6355 }
64- log .Debug ("bal" , "blockNum" , blockNum , "hash" , bal .Hash ())
6556 if ! isEIP7928 {
6657 return nil
6758 }
6859 // EIP-7928 size bound is only consensus-binding once Amsterdam activates.
69- if err := bal .ValidateMaxItems (h .GasLimit ); err != nil {
60+ if err := computedBlockBal .ValidateMaxItems (h .GasLimit ); err != nil {
7061 return fmt .Errorf ("%w, block %d: %w" , rules .ErrInvalidBlock , blockNum , err )
7162 }
7263 if h .BlockAccessListHash == nil {
7364 return fmt .Errorf ("block %d: EIP-7928 active but BlockAccessListHash is nil in header" , blockNum )
7465 }
75- headerBALHash := * h .BlockAccessListHash
76- dbBALBytes , err := rawdb .ReadBlockAccessListBytes (tx , blockHash , blockNum )
66+ blockBalHash := * h .BlockAccessListHash
67+ blockBalBytes , err := rawdb .ReadBlockAccessListBytes (tx , blockHash , blockNum )
7768 if err != nil {
7869 return fmt .Errorf ("block %d: read stored block access list: %w" , blockNum , err )
7970 }
8071 // A stored BAL sidecar may be absent — eth/71 backfill is best-effort and
8172 // never blocks stage progress — so cross-check it only when present.
82- if dbBALBytes != nil {
83- dbBAL , err := types .DecodeBlockAccessListBytes (dbBALBytes )
73+ var blockBal types.BlockAccessList
74+ if blockBalBytes != nil {
75+ blockBal , err = types .DecodeBlockAccessListBytes (blockBalBytes )
8476 if err != nil {
8577 return fmt .Errorf ("block %d: read stored block access list: %w" , blockNum , err )
8678 }
87- if err = dbBAL .Validate (); err != nil {
79+ if err = blockBal .Validate (); err != nil {
8880 return fmt .Errorf ("block %d: db block access list is invalid: %w" , blockNum , err )
8981 }
90- if err = dbBAL .ValidateMaxItems (h .GasLimit ); err != nil {
82+ if err = blockBal .ValidateMaxItems (h .GasLimit ); err != nil {
9183 return fmt .Errorf ("block %d: stored block access list exceeds max items: %w" , blockNum , err )
9284 }
93-
94- if headerBALHash != dbBAL .Hash () {
95- log .Info (fmt .Sprintf ("bal from block: %s" , dbBAL .DebugString ()))
96- return fmt .Errorf ("block %d: invalid block access list, hash mismatch: got %s expected %s" , blockNum , dbBAL .Hash (), headerBALHash )
85+ if blockBalHash != blockBal .Hash () {
86+ reportBALMismatch (blockNum , blockHash , blockBal , blockBalHash , computedBlockBal , dataDir , logger )
87+ return fmt .Errorf ("block %d: invalid block access list, hash mismatch: got %s expected %s" , blockNum , blockBal .Hash (), blockBalHash )
9788 }
9889 }
9990 // Always validate computed BAL against header. The BalancePath cross-check
10091 // in VersionMap.validateRead ensures deterministic parallel execution even
10192 // without a stored BAL body (HasBAL=false), so the computed BAL is accurate.
102- if headerBALHash != bal .Hash () {
103- // Dump full BAL DebugStrings to stderr via the logger so container
104- // stdout captures (kurtosis/docker log collection) preserve the diff
105- // even when the on-disk dumpDir below is unreachable (e.g. ephemeral
106- // container, CI runner without artifact capture of /data).
107- // Each DebugString is one multi-line value tagged with block metadata
108- // so operators can diff computed vs stored without hunting for files.
109- computedDebug := bal .DebugString ()
110- var storedDebug string
111- if dbBALBytes != nil {
112- dbBAL2 , decErr := types .DecodeBlockAccessListBytes (dbBALBytes )
113- if decErr != nil {
114- log .Warn ("failed to decode stored BAL for debug dump" , "err" , decErr )
115- } else if dbBAL2 != nil {
116- storedDebug = dbBAL2 .DebugString ()
117- }
118- }
119- log .Error ("BAL mismatch: computed" , "block" , blockNum , "hash" , bal .Hash (), "headerHash" , headerBALHash , "bal" , computedDebug )
120- if storedDebug != "" {
121- log .Error ("BAL mismatch: stored (from sidecar)" , "block" , blockNum , "hash" , headerBALHash , "bal" , storedDebug )
122- }
123-
124- dumpDir := ""
125- if dataDir != "" {
126- balDir := filepath .Join (dataDir , "bal" )
127- if err := os .MkdirAll (balDir , 0o755 ); err != nil {
128- log .Warn ("failed to create BAL debug directory" , "dir" , balDir , "err" , err )
129- } else {
130- computedPath := filepath .Join (balDir , fmt .Sprintf ("computed_bal_%d.txt" , blockNum ))
131- if err := os .WriteFile (computedPath , []byte (computedDebug ), 0o644 ); err != nil {
132- log .Warn ("failed to write computed BAL debug file" , "path" , computedPath , "err" , err )
133- } else {
134- dumpDir = balDir
135- }
136- if storedDebug != "" {
137- storedPath := filepath .Join (balDir , fmt .Sprintf ("stored_bal_%d.txt" , blockNum ))
138- if err := os .WriteFile (storedPath , []byte (storedDebug ), 0o644 ); err != nil {
139- log .Warn ("failed to write stored BAL debug file" , "path" , storedPath , "err" , err )
140- }
141- }
142- }
143- }
144- if dumpDir != "" {
145- return fmt .Errorf ("%w, block=%d (hash=%s): block access list mismatch: got %s expected %s; debug dumps in %s" ,
146- rules .ErrInvalidBlock , blockNum , blockHash , bal .Hash (), headerBALHash , dumpDir )
147- }
93+ computedBlockBalHash := computedBlockBal .Hash ()
94+ if blockBalHash != computedBlockBalHash {
95+ reportBALMismatch (blockNum , blockHash , blockBal , blockBalHash , computedBlockBal , dataDir , logger )
14896 return fmt .Errorf ("%w, block=%d (hash=%s): block access list mismatch: got %s expected %s" ,
149- rules .ErrInvalidBlock , blockNum , blockHash , bal . Hash (), headerBALHash )
97+ rules .ErrInvalidBlock , blockNum , blockHash , computedBlockBalHash , blockBalHash )
15098 }
15199 return nil
152100}
101+
102+ // reportBALMismatch logs a BAL hash mismatch and dumps the block's BAL and the
103+ // computed one under <dataDir>/bal for offline diffing.
104+ func reportBALMismatch (blockNum uint64 , blockHash common.Hash , blockBal types.BlockAccessList , blockBalHash common.Hash , computedBlockBal types.BlockAccessList , dataDir string , logger log.Logger ) {
105+ logger .Error ("BAL mismatch" , "blockNum" , blockNum , "blockHash" , blockHash , "blockBalHash" , blockBalHash , "computedBlockBalHash" , computedBlockBal .Hash ())
106+ writeBALToFile (computedBlockBal , dataDir , fmt .Sprintf ("computed_bal_%d.txt" , blockNum ), logger )
107+ if blockBal != nil {
108+ writeBALToFile (blockBal , dataDir , fmt .Sprintf ("block_bal_%d.txt" , blockNum ), logger )
109+ }
110+ }
0 commit comments