Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### 2025-10-28

- Batch BlobsBundle::validate [#4993](https://github.com/lambdaclass/ethrex/pull/4993)
- Remove latest_block_header lock [#5050](https://github.com/lambdaclass/ethrex/pull/5050)

### 2025-10-27

Expand Down
1 change: 1 addition & 0 deletions crates/l2/networking/rpc/l2/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl RpcHandler for SponsoredTx {
.await
.map_err(RpcErr::from)?;
let chain_config = context.l1_ctx.storage.get_chain_config();

let chain_id = chain_config.chain_id;
let nonce = context
.l1_ctx
Expand Down
95 changes: 42 additions & 53 deletions crates/storage/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use sha3::{Digest as _, Keccak256};
use std::{collections::hash_map::Entry, sync::Arc};
use std::{
collections::{BTreeMap, HashMap},
sync::RwLock,
sync::Mutex,
};
use std::{fmt::Debug, path::Path};
use tracing::{debug, error, info, instrument};
Expand All @@ -34,7 +34,13 @@ pub const MAX_SNAPSHOT_READS: usize = 100;
pub struct Store {
pub engine: Arc<dyn StoreEngine>,
pub chain_config: ChainConfig,
pub latest_block_header: Arc<RwLock<BlockHeader>>,
/// Keeps the latest canonical block hash
/// It's wrapped in an ArcSwap to allow for cheap lock-free reads with infrequent writes
/// Reading an out-of-date value is acceptable, since it's only used as:
/// - a cache of the (frequently requested) header
/// - a Latest tag for RPC, where a small extra delay before the newest block is expected
/// - sync-related operations, which must be idempotent in order to handle reorgs
latest_block_header: LatestBlockHeaderCache,
}

pub type StorageTrieNodes = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
Expand Down Expand Up @@ -81,12 +87,12 @@ impl Store {
EngineType::RocksDB => Self {
engine: Arc::new(RocksDBStore::new(path)?),
chain_config: Default::default(),
latest_block_header: Arc::new(RwLock::new(BlockHeader::default())),
latest_block_header: Default::default(),
},
EngineType::InMemory => Self {
engine: Arc::new(InMemoryStore::new()),
chain_config: Default::default(),
latest_block_header: Arc::new(RwLock::new(BlockHeader::default())),
latest_block_header: Default::default(),
},
};

Expand Down Expand Up @@ -175,13 +181,9 @@ impl Store {
&self,
block_number: BlockNumber,
) -> Result<Option<BlockHeader>, StoreError> {
let latest = self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?
.clone();
let latest = self.latest_block_header.get();
if block_number == latest.number {
return Ok(Some(latest));
return Ok(Some((*latest).clone()));
}
self.engine.get_block_header(block_number)
}
Expand All @@ -191,12 +193,9 @@ impl Store {
block_hash: BlockHash,
) -> Result<Option<BlockHeader>, StoreError> {
{
let latest = self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?;
let latest = self.latest_block_header.get();
if block_hash == latest.hash() {
return Ok(Some(latest.clone()));
return Ok(Some((*latest).clone()));
}
}

Expand All @@ -223,11 +222,7 @@ impl Store {
block_number: BlockNumber,
) -> Result<Option<BlockBody>, StoreError> {
// FIXME (#4353)
let latest = self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?
.clone();
let latest = self.latest_block_header.get();
if block_number == latest.number {
// The latest may not be marked as canonical yet
return self.engine.get_block_body_by_hash(latest.hash()).await;
Expand Down Expand Up @@ -634,14 +629,13 @@ impl Store {
// Set chain config
self.set_chain_config(&genesis.config).await?;

// The cache can't be empty
if let Some(number) = self.engine.get_latest_block_number().await? {
*self
.latest_block_header
.write()
.map_err(|_| StoreError::LockError)? = self
let latest_block_header = self
.engine
.get_block_header(number)?
.ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
self.latest_block_header.update(latest_block_header);
}

match self.engine.get_block_header(genesis_block_number)? {
Expand Down Expand Up @@ -686,10 +680,7 @@ impl Store {
.engine
.get_block_header(number)?
.ok_or_else(|| StoreError::Custom("latest block header is missing".to_string()))?;
*self
.latest_block_header
.write()
.map_err(|_| StoreError::LockError)? = latest_block_header;
self.latest_block_header.update(latest_block_header);
Ok(())
}

Expand Down Expand Up @@ -781,11 +772,7 @@ impl Store {
}

pub async fn get_latest_block_number(&self) -> Result<BlockNumber, StoreError> {
Ok(self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?
.number)
Ok(self.latest_block_header.get().number)
}

pub async fn update_pending_block_number(
Expand All @@ -804,10 +791,7 @@ impl Store {
block_number: BlockNumber,
) -> Result<Option<BlockHash>, StoreError> {
{
let last = self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?;
let last = self.latest_block_header.get();
if last.number == block_number {
return Ok(Some(last.hash()));
}
Expand All @@ -816,12 +800,7 @@ impl Store {
}

pub async fn get_latest_canonical_block_hash(&self) -> Result<Option<BlockHash>, StoreError> {
Ok(Some(
self.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?
.hash(),
))
Ok(Some(self.latest_block_header.get().hash()))
}

/// Updates the canonical chain.
Expand All @@ -836,15 +815,12 @@ impl Store {
safe: Option<BlockNumber>,
finalized: Option<BlockNumber>,
) -> Result<(), StoreError> {
// Updates first the latest_block_header
// to avoid nonce inconsistencies #3927.
*self
.latest_block_header
.write()
.map_err(|_| StoreError::LockError)? = self
// Updates first the latest_block_header to avoid nonce inconsistencies #3927.
let latest_block_header = self
.engine
.get_block_header_by_hash(head_hash)?
.ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
self.latest_block_header.update(latest_block_header);
self.engine
.forkchoice_update(
new_canonical_blocks,
Expand Down Expand Up @@ -1324,10 +1300,7 @@ impl Store {
block_number: BlockNumber,
) -> Result<Option<BlockHash>, StoreError> {
{
let last = self
.latest_block_header
.read()
.map_err(|_| StoreError::LockError)?;
let last = self.latest_block_header.get();
if last.number == block_number {
return Ok(Some(last.hash()));
}
Expand Down Expand Up @@ -1456,6 +1429,22 @@ pub fn hash_key(key: &H256) -> Vec<u8> {
.to_vec()
}

#[derive(Debug, Default, Clone)]
struct LatestBlockHeaderCache {
current: Arc<Mutex<Arc<BlockHeader>>>,
}

impl LatestBlockHeaderCache {
pub fn get(&self) -> Arc<BlockHeader> {
self.current.lock().expect("poisoned mutex").clone()
}

pub fn update(&self, header: BlockHeader) {
let new = Arc::new(header);
*self.current.lock().expect("poisoned mutex") = new;
}
}

#[cfg(test)]
mod tests {
use bytes::Bytes;
Expand Down
Loading