-
Notifications
You must be signed in to change notification settings - Fork 154
fix(l2): use checkpoints to persist previous batch state #5037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 22 commits
328932d
da7a646
9bd8506
6aaba68
2c7432d
004632f
2912e28
bb46d61
51582a8
cc50ea5
2c3a1e8
cbc8d53
76abb2f
cf1aac5
6ecc127
930b778
9c694fb
aba49f6
067b628
301daf3
6ae9d1b
3866ca1
1663345
b757259
f2ea803
5a5f7d8
7098fb0
bfcadbe
14197f1
8001fae
934683b
8a29d11
a954b36
1948a88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,8 @@ use ethrex_metrics::metrics_blocks::METRICS_BLOCKS; | |
| #[cfg(feature = "c-kzg")] | ||
| use ethrex_common::types::BlobsBundle; | ||
|
|
||
| use crate::fork_choice::apply_fork_choice; | ||
|
|
||
| const MAX_PAYLOADS: usize = 10; | ||
| const MAX_MEMPOOL_SIZE_DEFAULT: usize = 10_000; | ||
|
|
||
|
|
@@ -186,13 +188,12 @@ impl Blockchain { | |
| &self, | ||
| blocks: &[Block], | ||
| ) -> Result<ExecutionWitness, ChainError> { | ||
| let first_block_header = blocks | ||
| let first_block_header = &blocks | ||
| .first() | ||
| .ok_or(ChainError::WitnessGeneration( | ||
| "Empty block batch".to_string(), | ||
| ))? | ||
| .header | ||
| .clone(); | ||
| .header; | ||
|
|
||
| // Get state at previous block | ||
| let trie = self | ||
|
|
@@ -201,7 +202,17 @@ impl Blockchain { | |
| .map_err(|_| ChainError::ParentStateNotFound)? | ||
| .ok_or(ChainError::ParentStateNotFound)?; | ||
|
|
||
| let (state_trie_witness, mut trie) = TrieLogger::open_trie(trie); | ||
| let (mut current_trie_witness, mut trie) = TrieLogger::open_trie(trie); | ||
|
|
||
| // For each block, a new TrieLogger will be opened, each containing the | ||
| // witness accessed during the block execution. We need to accumulate | ||
| // all the nodes accessed during the entire batch execution. | ||
| let mut accumulated_state_trie_witness = current_trie_witness | ||
| .lock() | ||
| .map_err(|_| { | ||
| ChainError::WitnessGeneration("Failed to lock state trie witness".to_string()) | ||
| })? | ||
| .clone(); | ||
|
|
||
| let mut touched_account_storage_slots = BTreeMap::new(); | ||
| // This will become the state trie + storage trie | ||
|
|
@@ -214,12 +225,20 @@ impl Blockchain { | |
|
|
||
| let mut block_hashes = HashMap::new(); | ||
| let mut codes = Vec::new(); | ||
|
|
||
| for block in blocks { | ||
| let parent_hash = block.header.parent_hash; | ||
|
|
||
| // This assumes that the user has the necessary state stored already, | ||
| // so if the user only has the state previous to the first block, it | ||
| // will fail in the second iteration of this for loop. To ensure this, | ||
| // doesn't fail, later in this function we store the new state after | ||
| // re-execution. | ||
| let vm_db: DynVmDatabase = | ||
| Box::new(StoreVmDatabase::new(self.storage.clone(), parent_hash)); | ||
|
|
||
| let logger = Arc::new(DatabaseLogger::new(Arc::new(Mutex::new(Box::new(vm_db))))); | ||
|
|
||
| let mut vm = match self.options.r#type { | ||
| BlockchainType::L1 => Evm::new_from_db_for_l1(logger.clone()), | ||
| BlockchainType::L2(fee_config) => { | ||
|
|
@@ -228,7 +247,8 @@ impl Blockchain { | |
| }; | ||
|
|
||
| // Re-execute block with logger | ||
| vm.execute_block(block)?; | ||
| let execution_result = vm.execute_block(block)?; | ||
|
|
||
| // Gather account updates | ||
| let account_updates = vm.get_state_transitions()?; | ||
|
|
||
|
|
@@ -251,7 +271,9 @@ impl Blockchain { | |
| ChainError::WitnessGeneration("Failed to get block hashes".to_string()) | ||
| })? | ||
| .clone(); | ||
|
|
||
| block_hashes.extend(logger_block_hashes); | ||
|
|
||
| // Access all the accounts needed for withdrawals | ||
| if let Some(withdrawals) = block.body.withdrawals.as_ref() { | ||
| for withdrawal in withdrawals { | ||
|
|
@@ -295,6 +317,7 @@ impl Blockchain { | |
| used_storage_tries.insert(*account, (storage_trie_witness, storage_trie)); | ||
| } | ||
| } | ||
|
|
||
| // Store all the accessed evm bytecodes | ||
| for code_hash in logger | ||
| .code_accessed | ||
|
|
@@ -317,14 +340,39 @@ impl Blockchain { | |
| } | ||
|
|
||
| // Apply account updates to the trie recording all the necessary nodes to do so | ||
| let (updated_trie, storage_tries_after_update) = self | ||
| let (storage_tries_after_update, account_updates_list) = self | ||
ilitteri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .storage | ||
| .apply_account_updates_from_trie_with_witness( | ||
| trie, | ||
| &account_updates, | ||
| used_storage_tries, | ||
| ) | ||
| .await?; | ||
|
|
||
| // We cannot ensure that the users of this function have the necessary | ||
| // state stored, so in order for it to not assume anything, we update | ||
| // the storage with the new state after re-execution | ||
| self.store_block(block.clone(), account_updates_list, execution_result) | ||
| .await?; | ||
|
|
||
| // Later in this function we query the batch block headers by number, | ||
| // to do so, they need to be marked as canonical. | ||
| // Only users that have the state previous to the first batch block | ||
| // need this step. | ||
| if self | ||
| .storage | ||
| .get_block_header(block.header.number)? | ||
| .is_none() | ||
| { | ||
| apply_fork_choice(&self.storage, block.hash(), block.hash(), block.hash()) | ||
|
||
| .await | ||
| .map_err(|e| { | ||
| ChainError::WitnessGeneration(format!( | ||
| "Failed to apply fork choice after witness generation: {e}" | ||
| )) | ||
| })?; | ||
| } | ||
|
|
||
| for (address, (witness, _storage_trie)) in storage_tries_after_update { | ||
| let mut witness = witness.lock().map_err(|_| { | ||
| ChainError::WitnessGeneration("Failed to lock storage trie witness".to_string()) | ||
|
|
@@ -334,15 +382,33 @@ impl Blockchain { | |
| used_trie_nodes.extend_from_slice(&witness); | ||
| touched_account_storage_slots.entry(address).or_default(); | ||
| } | ||
|
|
||
| let (new_state_trie_witness, updated_trie) = TrieLogger::open_trie( | ||
| self.storage | ||
| .state_trie(block.hash()) | ||
| .map_err(|_| ChainError::ParentStateNotFound)? | ||
| .ok_or(ChainError::ParentStateNotFound)?, | ||
| ); | ||
|
|
||
| // Use the updated state trie for the next block | ||
| trie = updated_trie; | ||
|
|
||
| for state_trie_witness in current_trie_witness | ||
| .lock() | ||
| .map_err(|_| { | ||
| ChainError::WitnessGeneration("Failed to lock state trie witness".to_string()) | ||
| })? | ||
| .iter() | ||
| { | ||
| accumulated_state_trie_witness.insert(state_trie_witness.clone()); | ||
| } | ||
|
|
||
| current_trie_witness = new_state_trie_witness; | ||
| } | ||
|
|
||
| // Get the witness for the state trie | ||
| let mut state_trie_witness = state_trie_witness.lock().map_err(|_| { | ||
| ChainError::WitnessGeneration("Failed to lock state trie witness".to_string()) | ||
| })?; | ||
| let state_trie_witness = std::mem::take(&mut *state_trie_witness); | ||
| used_trie_nodes.extend_from_slice(&Vec::from_iter(state_trie_witness.into_iter())); | ||
| used_trie_nodes | ||
| .extend_from_slice(&Vec::from_iter(accumulated_state_trie_witness.into_iter())); | ||
|
|
||
| // If the witness is empty at least try to store the root | ||
| if used_trie_nodes.is_empty() | ||
| && let Some(root) = root_node | ||
|
|
@@ -351,6 +417,7 @@ impl Blockchain { | |
| } | ||
|
|
||
| let mut needed_block_numbers = block_hashes.keys().collect::<Vec<_>>(); | ||
|
|
||
| needed_block_numbers.sort(); | ||
|
|
||
| // Last needed block header for the witness is the parent of the last block we need to execute | ||
|
|
@@ -360,17 +427,21 @@ impl Blockchain { | |
| .header | ||
| .number | ||
| .saturating_sub(1); | ||
|
|
||
| // The first block number we need is either the parent of the first block number or the earliest block number used by BLOCKHASH | ||
| let mut first_needed_block_number = first_block_header.number.saturating_sub(1); | ||
|
|
||
| if let Some(block_number_from_logger) = needed_block_numbers.first() | ||
| && **block_number_from_logger < first_needed_block_number | ||
| { | ||
| first_needed_block_number = **block_number_from_logger; | ||
| } | ||
|
|
||
| let mut block_headers_bytes = Vec::new(); | ||
|
|
||
| for block_number in first_needed_block_number..=last_needed_block_number { | ||
| let header = self.storage.get_block_header(block_number)?.ok_or( | ||
| ChainError::WitnessGeneration("Failed to get block header".to_string()), | ||
| ChainError::WitnessGeneration(format!("Failed to get block {block_number} header")), | ||
| )?; | ||
| block_headers_bytes.push(header.encode_to_vec()); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.