@@ -51,11 +51,10 @@ pub struct ExecutionWitnessResult {
51
51
#[ serde( skip) ]
52
52
#[ rkyv( with = rkyv:: with:: Skip ) ]
53
53
pub state_trie : Option < Trie > ,
54
- // Indexed by account
55
- // Pruned storage MPT
54
+ // Storage tries accessed by account address
56
55
#[ serde( skip) ]
57
56
#[ rkyv( with = rkyv:: with:: Skip ) ]
58
- pub storage_tries : Option < HashMap < Address , Trie > > ,
57
+ pub storage_tries : HashMap < Address , Trie > ,
59
58
// Block headers needed for BLOCKHASH opcode
60
59
pub block_headers : HashMap < u64 , BlockHeader > ,
61
60
// Parent block header to get the initial state root
@@ -85,36 +84,17 @@ pub enum ExecutionWitnessError {
85
84
}
86
85
87
86
impl ExecutionWitnessResult {
88
- pub fn rebuild_tries ( & mut self ) -> Result < ( ) , ExecutionWitnessError > {
87
+ /// Use the state nodes to build the state trie and store them in `self.state_trie`
88
+ /// This function will fail if the state trie cannot be rebuilt.
89
+ pub fn rebuild_state_trie ( & mut self ) -> Result < ( ) , ExecutionWitnessError > {
89
90
let state_trie = rebuild_trie ( self . parent_block_header . state_root , & self . state_trie_nodes ) ?;
90
-
91
- // Keys can either be account addresses or storage slots. They have different sizes,
92
- // so we filter them by size. The from_slice method panics if the input has the wrong size.
93
- let addresses: Vec < Address > = self
94
- . keys
95
- . iter ( )
96
- . filter ( |k| k. len ( ) == Address :: len_bytes ( ) )
97
- . map ( |k| Address :: from_slice ( k) )
98
- . collect ( ) ;
99
-
100
- let storage_tries: HashMap < Address , Trie > = HashMap :: from_iter (
101
- addresses
102
- . iter ( )
103
- . filter_map ( |addr| {
104
- Some ( (
105
- * addr,
106
- Self :: rebuild_storage_trie ( addr, & state_trie, & self . state_trie_nodes ) ?,
107
- ) )
108
- } )
109
- . collect :: < Vec < ( Address , Trie ) > > ( ) ,
110
- ) ;
111
-
112
91
self . state_trie = Some ( state_trie) ;
113
- self . storage_tries = Some ( storage_tries) ;
114
92
115
93
Ok ( ( ) )
116
94
}
117
95
96
+ /// Helper function to rebuild the storage trie for a given account address
97
+ /// Returns if root is not empty, an Option with the rebuilt trie
118
98
// This function is an option because we expect it to fail sometimes, and we just want to filter it
119
99
pub fn rebuild_storage_trie ( address : & H160 , trie : & Trie , state : & [ Bytes ] ) -> Option < Trie > {
120
100
let account_state_rlp = trie. get ( & hash_address ( address) ) . ok ( ) ??;
@@ -128,12 +108,14 @@ impl ExecutionWitnessResult {
128
108
rebuild_trie ( account_state. storage_root , state) . ok ( )
129
109
}
130
110
111
+ /// Helper function to apply account updates to the execution witness
112
+ /// It updates the state trie and storage tries with the given account updates
113
+ /// Returns an error if the updates cannot be applied
131
114
pub fn apply_account_updates (
132
115
& mut self ,
133
116
account_updates : & [ AccountUpdate ] ,
134
117
) -> Result < ( ) , ExecutionWitnessError > {
135
- let ( Some ( state_trie) , Some ( storage_tries_map) ) =
136
- ( self . state_trie . as_mut ( ) , self . storage_tries . as_mut ( ) )
118
+ let ( Some ( state_trie) , storage_tries) = ( self . state_trie . as_mut ( ) , & mut self . storage_tries )
137
119
else {
138
120
return Err ( ExecutionWitnessError :: ApplyAccountUpdates (
139
121
"Tried to apply account updates before rebuilding the tries" . to_string ( ) ,
@@ -169,10 +151,9 @@ impl ExecutionWitnessResult {
169
151
}
170
152
// Store the added storage in the account's storage trie and compute its new root
171
153
if !update. added_storage . is_empty ( ) {
172
- let storage_trie =
173
- storage_tries_map. entry ( update. address ) . or_insert_with ( || {
174
- Trie :: from_nodes ( None , & [ ] ) . expect ( "failed to create empty trie" )
175
- } ) ;
154
+ let storage_trie = storage_tries. entry ( update. address ) . or_insert_with ( || {
155
+ Trie :: from_nodes ( None , & [ ] ) . expect ( "failed to create empty trie" )
156
+ } ) ;
176
157
177
158
// Inserts must come before deletes, otherwise deletes might require extra nodes
178
159
// Example:
@@ -207,6 +188,8 @@ impl ExecutionWitnessResult {
207
188
Ok ( ( ) )
208
189
}
209
190
191
+ /// Returns the root hash of the state trie
192
+ /// Returns an error if the state trie is not built yet
210
193
pub fn state_trie_root ( & self ) -> Result < H256 , ExecutionWitnessError > {
211
194
let state_trie = self
212
195
. state_trie
@@ -254,6 +237,8 @@ impl ExecutionWitnessResult {
254
237
Ok ( None )
255
238
}
256
239
240
+ /// Retrieves the parent block header for the specified block number
241
+ /// Searches within `self.block_headers`
257
242
pub fn get_block_parent_header (
258
243
& self ,
259
244
block_number : u64 ,
@@ -263,6 +248,8 @@ impl ExecutionWitnessResult {
263
248
. ok_or ( ExecutionWitnessError :: MissingParentHeaderOf ( block_number) )
264
249
}
265
250
251
+ /// Retrieves the account info based on what is stored in the state trie.
252
+ /// Returns an error if the state trie is not rebuilt or if decoding the account state fails.
266
253
pub fn get_account_info (
267
254
& self ,
268
255
address : Address ,
@@ -289,6 +276,8 @@ impl ExecutionWitnessResult {
289
276
} ) )
290
277
}
291
278
279
+ /// Fetches the block hash for a specific block number.
280
+ /// Looks up `self.block_headers` and computes the hash if it is not already computed.
292
281
pub fn get_block_hash ( & self , block_number : u64 ) -> Result < H256 , ExecutionWitnessError > {
293
282
self . block_headers
294
283
. get ( & block_number)
@@ -300,21 +289,31 @@ impl ExecutionWitnessResult {
300
289
} )
301
290
}
302
291
292
+ /// Retrieves a storage slot value for an account in its storage trie.
293
+ ///
294
+ /// Lazily builds the storage trie for the address if not already available.
295
+ /// This lazy loading approach minimizes memory usage by only building tries when needed.
303
296
pub fn get_storage_slot (
304
- & self ,
297
+ & mut self ,
305
298
address : Address ,
306
299
key : H256 ,
307
300
) -> Result < Option < U256 > , ExecutionWitnessError > {
308
- let storage_tries_map =
309
- self . storage_tries
310
- . as_ref ( )
311
- . ok_or ( ExecutionWitnessError :: Database (
312
- "ExecutionWitness: Tried to get storage slot before rebuilding tries"
301
+ let storage_trie = if let Some ( storage_trie) = self . storage_tries . get ( & address) {
302
+ storage_trie
303
+ } else {
304
+ let Some ( state_trie) = self . state_trie . as_ref ( ) else {
305
+ return Err ( ExecutionWitnessError :: Database (
306
+ "ExecutionWitness: Tried to get storage slot before rebuilding state trie."
313
307
. to_string ( ) ,
314
- ) ) ?;
308
+ ) ) ;
309
+ } ;
310
+ let Some ( storage_trie) =
311
+ Self :: rebuild_storage_trie ( & address, state_trie, & self . state_trie_nodes )
312
+ else {
313
+ return Ok ( None ) ;
314
+ } ;
315
315
316
- let Some ( storage_trie) = storage_tries_map. get ( & address) else {
317
- return Ok ( None ) ;
316
+ self . storage_tries . entry ( address) . or_insert ( storage_trie)
318
317
} ;
319
318
let hashed_key = hash_key ( & key) ;
320
319
if let Some ( encoded_key) = storage_trie
@@ -331,10 +330,13 @@ impl ExecutionWitnessResult {
331
330
}
332
331
}
333
332
333
+ /// Retrieves the chain configuration for the execution witness.
334
334
pub fn get_chain_config ( & self ) -> Result < ChainConfig , ExecutionWitnessError > {
335
335
Ok ( self . chain_config )
336
336
}
337
337
338
+ /// Retrieves the account code for a specific account.
339
+ /// Returns an Err if the code is not found.
338
340
pub fn get_account_code ( & self , code_hash : H256 ) -> Result < bytes:: Bytes , ExecutionWitnessError > {
339
341
if code_hash == * EMPTY_KECCACK_HASH {
340
342
return Ok ( Bytes :: new ( ) ) ;
@@ -447,16 +449,3 @@ pub fn rebuild_trie(initial_state: H256, state: &[Bytes]) -> Result<Trie, Execut
447
449
)
448
450
. map_err ( |e| ExecutionWitnessError :: RebuildTrie ( format ! ( "Failed to build state trie {e}" ) ) )
449
451
}
450
-
451
- // This function is an option because we expect it to fail sometimes, and we just want to filter it
452
- pub fn rebuild_storage_trie ( address : & H160 , trie : & Trie , state : & [ Bytes ] ) -> Option < Trie > {
453
- let account_state_rlp = trie. get ( & hash_address ( address) ) . ok ( ) ??;
454
-
455
- let account_state = AccountState :: decode ( & account_state_rlp) . ok ( ) ?;
456
-
457
- if account_state. storage_root == * EMPTY_TRIE_HASH {
458
- return None ;
459
- }
460
-
461
- rebuild_trie ( account_state. storage_root , state) . ok ( )
462
- }
0 commit comments