Skip to content

Commit 5946f28

Browse files
authored
fix(l1): cache block hashes when adding blocks in batch (#3034)
**Motivation** When adding blocks in batches, transactions in blocks may need to read the hash of a previous block in the batch. This poses an issue, as we add all blocks after the full batch has been executed ad we don't have those hashes stored. Previously, this issue was tackled by storing the block headers before executing and storing the full block batch. This also required us to make the fixes on #2831 (as we might have the headers of blocks we have not yet executed during full sync). This stopped being a suitable solution once #3022 was merged, as block hashes are now searched using the block the state is based on's ancestors. As the state is based on the first block in the batch's parents, later blocks are not able to access the hashes of previous blocks in the batch. This PR aims to fix both issues by adding a `block_hash_cache` to the `StoreVmDatabase` were we would add the hashes of all blocks in the batch, and use it when fetching block hashes before looking at the Store. This way we can access block hashes for previously executed blocks in the batch without prematurely adding data from blocks yet to be executed to the state. Allowing us to rollback fixes from #2831 <!-- Why does this pull request exist? What are its goals? --> **Description** * Add `block_hash_cache` to `VmStoreDatabase` * Cache block hashes of the full batch before executing in `Blockchain::add_block_batch` * No longer store block headers before execution during full sync * Rollback fixes from #2831 (no longer needed due to the above change) <!-- A clear and concise general description of the changes this PR introduces --> <!-- Link to issues: Resolves #111, Resolves #222 --> Closes #issue_number
1 parent f27cfff commit 5946f28

File tree

4 files changed

+70
-49
lines changed

4 files changed

+70
-49
lines changed

crates/blockchain/blockchain.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,10 @@ impl Blockchain {
7474
block: &Block,
7575
) -> Result<(BlockExecutionResult, Vec<AccountUpdate>), ChainError> {
7676
// Validate if it can be the new head and find the parent
77-
// Feda (#2831): We search for the entire block because during full/batch sync
78-
// we can have the header without the body indicating it's still syncing.
79-
let parent = self
80-
.storage
81-
.get_block_by_hash(block.header.parent_hash)
82-
.await?;
83-
let parent_header = match parent {
84-
Some(parent_block) => parent_block.header,
85-
None => {
86-
// If the parent is not present, we store it as pending.
87-
self.storage.add_pending_block(block.clone()).await?;
88-
return Err(ChainError::ParentNotFound);
89-
}
77+
let Ok(parent_header) = find_parent_header(&block.header, &self.storage) else {
78+
// If the parent is not present, we store it as pending.
79+
self.storage.add_pending_block(block.clone()).await?;
80+
return Err(ChainError::ParentNotFound);
9081
};
9182

9283
let chain_config = self.storage.get_chain_config()?;
@@ -217,7 +208,14 @@ impl Blockchain {
217208
.get_chain_config()
218209
.map_err(|e| (e.into(), None))?;
219210

220-
let vm_db = StoreVmDatabase::new(self.storage.clone(), first_block_header.parent_hash);
211+
// Cache block hashes for the full batch so we can access them during execution without having to store the blocks beforehand
212+
let block_hash_cache = blocks.iter().map(|b| (b.header.number, b.hash())).collect();
213+
214+
let vm_db = StoreVmDatabase::new_with_block_hash_cache(
215+
self.storage.clone(),
216+
first_block_header.parent_hash,
217+
block_hash_cache,
218+
);
221219
let mut vm = Evm::new(self.evm_engine, vm_db);
222220

223221
let blocks_len = blocks.len();

crates/blockchain/fork_choice.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,19 @@ pub async fn apply_fork_choice(
4040
None
4141
};
4242

43-
// Feda (#2831): We search for the entire block because during full/batch sync
44-
// we can have the header without the body indicating it's still syncing.
45-
let head_block_res = store.get_block_by_hash(head_hash).await?;
46-
47-
let Some(head_block) = head_block_res else {
48-
return Err(InvalidForkChoice::Syncing);
49-
};
43+
let head_res = store.get_block_header_by_hash(head_hash)?;
5044

5145
if !safe_hash.is_zero() {
52-
check_order(safe_res.as_ref(), Some(&head_block.header))?;
46+
check_order(&safe_res, &head_res)?;
5347
}
5448

5549
if !finalized_hash.is_zero() && !safe_hash.is_zero() {
56-
check_order(finalized_res.as_ref(), safe_res.as_ref())?;
50+
check_order(&finalized_res, &safe_res)?;
5751
}
5852

59-
let head = head_block.header;
53+
let Some(head) = head_res else {
54+
return Err(InvalidForkChoice::Syncing);
55+
};
6056

6157
let latest = store.get_latest_block_number().await?;
6258

@@ -131,8 +127,8 @@ pub async fn apply_fork_choice(
131127

132128
// Checks that block 1 is prior to block 2 and that if the second is present, the first one is too.
133129
fn check_order(
134-
block_1: Option<&BlockHeader>,
135-
block_2: Option<&BlockHeader>,
130+
block_1: &Option<BlockHeader>,
131+
block_2: &Option<BlockHeader>,
136132
) -> Result<(), InvalidForkChoice> {
137133
// We don't need to perform the check if the hashes are null
138134
match (block_1, block_2) {

crates/blockchain/vm.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
use bytes::Bytes;
22
use ethrex_common::{
3-
types::{AccountInfo, BlockHash, ChainConfig, EMPTY_KECCACK_HASH},
3+
types::{AccountInfo, BlockHash, BlockNumber, ChainConfig, EMPTY_KECCACK_HASH},
44
Address, H256, U256,
55
};
66
use ethrex_storage::Store;
77
use ethrex_vm::{EvmError, VmDatabase};
8-
use std::cmp::Ordering;
8+
use std::{cmp::Ordering, collections::HashMap};
99

1010
#[derive(Clone)]
1111
pub struct StoreVmDatabase {
1212
pub store: Store,
1313
pub block_hash: BlockHash,
14+
// Used to store known block hashes
15+
// We use this when executing blocks in batches, as we will only add the blocks at the end
16+
// And may need to access hashes of blocks previously executed in the batch
17+
pub block_hash_cache: HashMap<BlockNumber, BlockHash>,
1418
}
1519

1620
impl StoreVmDatabase {
1721
pub fn new(store: Store, block_hash: BlockHash) -> Self {
18-
StoreVmDatabase { store, block_hash }
22+
StoreVmDatabase {
23+
store,
24+
block_hash,
25+
block_hash_cache: HashMap::new(),
26+
}
27+
}
28+
29+
pub fn new_with_block_hash_cache(
30+
store: Store,
31+
block_hash: BlockHash,
32+
block_hash_cache: HashMap<BlockNumber, BlockHash>,
33+
) -> Self {
34+
StoreVmDatabase {
35+
store,
36+
block_hash,
37+
block_hash_cache,
38+
}
1939
}
2040
}
2141

@@ -33,6 +53,10 @@ impl VmDatabase for StoreVmDatabase {
3353
}
3454

3555
fn get_block_hash(&self, block_number: u64) -> Result<H256, EvmError> {
56+
// Check if we have it cached
57+
if let Some(block_hash) = self.block_hash_cache.get(&block_number) {
58+
return Ok(*block_hash);
59+
}
3660
// First check if our block is canonical, if it is then it's ancestor will also be canonical and we can look it up directly
3761
if self
3862
.store

crates/networking/p2p/sync.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -274,26 +274,29 @@ impl Syncer {
274274
// Discard the first header as we already have it
275275
block_hashes.remove(0);
276276
block_headers.remove(0);
277-
// Store headers and save hashes for full block retrieval
278-
all_block_hashes.extend_from_slice(&block_hashes[..]);
279-
// This step is necessary for full sync because some opcodes depend on previous blocks during execution.
280-
store
281-
.add_block_headers(block_hashes.clone(), block_headers.clone())
282-
.await?;
283-
284-
if sync_mode == SyncMode::Full {
285-
let last_block_hash = self
286-
.download_and_run_blocks(
287-
&block_hashes,
288-
&block_headers,
289-
sync_head,
290-
sync_head_found,
291-
store.clone(),
292-
)
293-
.await?;
294-
if let Some(last_block_hash) = last_block_hash {
295-
current_head = last_block_hash;
296-
search_head = current_head;
277+
278+
match sync_mode {
279+
SyncMode::Snap => {
280+
// Store headers and save hashes for full block retrieval
281+
all_block_hashes.extend_from_slice(&block_hashes[..]);
282+
store
283+
.add_block_headers(block_hashes.clone(), block_headers.clone())
284+
.await?;
285+
}
286+
SyncMode::Full => {
287+
let last_block_hash = self
288+
.download_and_run_blocks(
289+
&block_hashes,
290+
&block_headers,
291+
sync_head,
292+
sync_head_found,
293+
store.clone(),
294+
)
295+
.await?;
296+
if let Some(last_block_hash) = last_block_hash {
297+
current_head = last_block_hash;
298+
search_head = current_head;
299+
}
297300
}
298301
}
299302

0 commit comments

Comments
 (0)