Skip to content

Commit 4d2fa0b

Browse files
Oppeniovoidjrchatruc
authored
perf(l1,l2): remove latest_block_header lock (#5050)
chain_config is a matter of initialization with mutable stores, while latest_block_header is replaced by an ArcSwap to allow lock-free swapping with new values. **Motivation** <!-- Why does this pull request exist? What are its goals? --> **Description** <!-- A clear and concise general description of the changes this PR introduces --> <!-- Link to issues: Resolves #111, Resolves #222 --> Closes #issue_number --------- Co-authored-by: Lucas Fiegl <[email protected]> Co-authored-by: Javier Rodríguez Chatruc <[email protected]> Co-authored-by: Javier Chatruc <[email protected]>
1 parent 378d60b commit 4d2fa0b

File tree

3 files changed

+44
-53
lines changed

3 files changed

+44
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### 2025-10-28
66

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

910
### 2025-10-27
1011

crates/l2/networking/rpc/l2/transaction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ impl RpcHandler for SponsoredTx {
131131
.await
132132
.map_err(RpcErr::from)?;
133133
let chain_config = context.l1_ctx.storage.get_chain_config();
134+
134135
let chain_id = chain_config.chain_id;
135136
let nonce = context
136137
.l1_ctx

crates/storage/store.rs

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use sha3::{Digest as _, Keccak256};
2020
use std::{collections::hash_map::Entry, sync::Arc};
2121
use std::{
2222
collections::{BTreeMap, HashMap},
23-
sync::RwLock,
23+
sync::Mutex,
2424
};
2525
use std::{fmt::Debug, path::Path};
2626
use tracing::{debug, error, info, instrument};
@@ -34,7 +34,13 @@ pub const MAX_SNAPSHOT_READS: usize = 100;
3434
pub struct Store {
3535
pub engine: Arc<dyn StoreEngine>,
3636
pub chain_config: ChainConfig,
37-
pub latest_block_header: Arc<RwLock<BlockHeader>>,
37+
/// Keeps the latest canonical block hash
38+
/// It's wrapped in an ArcSwap to allow for cheap lock-free reads with infrequent writes
39+
/// Reading an out-of-date value is acceptable, since it's only used as:
40+
/// - a cache of the (frequently requested) header
41+
/// - a Latest tag for RPC, where a small extra delay before the newest block is expected
42+
/// - sync-related operations, which must be idempotent in order to handle reorgs
43+
latest_block_header: LatestBlockHeaderCache,
3844
}
3945

4046
pub type StorageTrieNodes = Vec<(H256, Vec<(Nibbles, Vec<u8>)>)>;
@@ -81,12 +87,12 @@ impl Store {
8187
EngineType::RocksDB => Self {
8288
engine: Arc::new(RocksDBStore::new(path)?),
8389
chain_config: Default::default(),
84-
latest_block_header: Arc::new(RwLock::new(BlockHeader::default())),
90+
latest_block_header: Default::default(),
8591
},
8692
EngineType::InMemory => Self {
8793
engine: Arc::new(InMemoryStore::new()),
8894
chain_config: Default::default(),
89-
latest_block_header: Arc::new(RwLock::new(BlockHeader::default())),
95+
latest_block_header: Default::default(),
9096
},
9197
};
9298

@@ -175,13 +181,9 @@ impl Store {
175181
&self,
176182
block_number: BlockNumber,
177183
) -> Result<Option<BlockHeader>, StoreError> {
178-
let latest = self
179-
.latest_block_header
180-
.read()
181-
.map_err(|_| StoreError::LockError)?
182-
.clone();
184+
let latest = self.latest_block_header.get();
183185
if block_number == latest.number {
184-
return Ok(Some(latest));
186+
return Ok(Some((*latest).clone()));
185187
}
186188
self.engine.get_block_header(block_number)
187189
}
@@ -191,12 +193,9 @@ impl Store {
191193
block_hash: BlockHash,
192194
) -> Result<Option<BlockHeader>, StoreError> {
193195
{
194-
let latest = self
195-
.latest_block_header
196-
.read()
197-
.map_err(|_| StoreError::LockError)?;
196+
let latest = self.latest_block_header.get();
198197
if block_hash == latest.hash() {
199-
return Ok(Some(latest.clone()));
198+
return Ok(Some((*latest).clone()));
200199
}
201200
}
202201

@@ -223,11 +222,7 @@ impl Store {
223222
block_number: BlockNumber,
224223
) -> Result<Option<BlockBody>, StoreError> {
225224
// FIXME (#4353)
226-
let latest = self
227-
.latest_block_header
228-
.read()
229-
.map_err(|_| StoreError::LockError)?
230-
.clone();
225+
let latest = self.latest_block_header.get();
231226
if block_number == latest.number {
232227
// The latest may not be marked as canonical yet
233228
return self.engine.get_block_body_by_hash(latest.hash()).await;
@@ -634,14 +629,13 @@ impl Store {
634629
// Set chain config
635630
self.set_chain_config(&genesis.config).await?;
636631

632+
// The cache can't be empty
637633
if let Some(number) = self.engine.get_latest_block_number().await? {
638-
*self
639-
.latest_block_header
640-
.write()
641-
.map_err(|_| StoreError::LockError)? = self
634+
let latest_block_header = self
642635
.engine
643636
.get_block_header(number)?
644637
.ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
638+
self.latest_block_header.update(latest_block_header);
645639
}
646640

647641
match self.engine.get_block_header(genesis_block_number)? {
@@ -686,10 +680,7 @@ impl Store {
686680
.engine
687681
.get_block_header(number)?
688682
.ok_or_else(|| StoreError::Custom("latest block header is missing".to_string()))?;
689-
*self
690-
.latest_block_header
691-
.write()
692-
.map_err(|_| StoreError::LockError)? = latest_block_header;
683+
self.latest_block_header.update(latest_block_header);
693684
Ok(())
694685
}
695686

@@ -781,11 +772,7 @@ impl Store {
781772
}
782773

783774
pub async fn get_latest_block_number(&self) -> Result<BlockNumber, StoreError> {
784-
Ok(self
785-
.latest_block_header
786-
.read()
787-
.map_err(|_| StoreError::LockError)?
788-
.number)
775+
Ok(self.latest_block_header.get().number)
789776
}
790777

791778
pub async fn update_pending_block_number(
@@ -804,10 +791,7 @@ impl Store {
804791
block_number: BlockNumber,
805792
) -> Result<Option<BlockHash>, StoreError> {
806793
{
807-
let last = self
808-
.latest_block_header
809-
.read()
810-
.map_err(|_| StoreError::LockError)?;
794+
let last = self.latest_block_header.get();
811795
if last.number == block_number {
812796
return Ok(Some(last.hash()));
813797
}
@@ -816,12 +800,7 @@ impl Store {
816800
}
817801

818802
pub async fn get_latest_canonical_block_hash(&self) -> Result<Option<BlockHash>, StoreError> {
819-
Ok(Some(
820-
self.latest_block_header
821-
.read()
822-
.map_err(|_| StoreError::LockError)?
823-
.hash(),
824-
))
803+
Ok(Some(self.latest_block_header.get().hash()))
825804
}
826805

827806
/// Updates the canonical chain.
@@ -836,15 +815,12 @@ impl Store {
836815
safe: Option<BlockNumber>,
837816
finalized: Option<BlockNumber>,
838817
) -> Result<(), StoreError> {
839-
// Updates first the latest_block_header
840-
// to avoid nonce inconsistencies #3927.
841-
*self
842-
.latest_block_header
843-
.write()
844-
.map_err(|_| StoreError::LockError)? = self
818+
// Updates first the latest_block_header to avoid nonce inconsistencies #3927.
819+
let latest_block_header = self
845820
.engine
846821
.get_block_header_by_hash(head_hash)?
847822
.ok_or_else(|| StoreError::MissingLatestBlockNumber)?;
823+
self.latest_block_header.update(latest_block_header);
848824
self.engine
849825
.forkchoice_update(
850826
new_canonical_blocks,
@@ -1324,10 +1300,7 @@ impl Store {
13241300
block_number: BlockNumber,
13251301
) -> Result<Option<BlockHash>, StoreError> {
13261302
{
1327-
let last = self
1328-
.latest_block_header
1329-
.read()
1330-
.map_err(|_| StoreError::LockError)?;
1303+
let last = self.latest_block_header.get();
13311304
if last.number == block_number {
13321305
return Ok(Some(last.hash()));
13331306
}
@@ -1456,6 +1429,22 @@ pub fn hash_key(key: &H256) -> Vec<u8> {
14561429
.to_vec()
14571430
}
14581431

1432+
#[derive(Debug, Default, Clone)]
1433+
struct LatestBlockHeaderCache {
1434+
current: Arc<Mutex<Arc<BlockHeader>>>,
1435+
}
1436+
1437+
impl LatestBlockHeaderCache {
1438+
pub fn get(&self) -> Arc<BlockHeader> {
1439+
self.current.lock().expect("poisoned mutex").clone()
1440+
}
1441+
1442+
pub fn update(&self, header: BlockHeader) {
1443+
let new = Arc::new(header);
1444+
*self.current.lock().expect("poisoned mutex") = new;
1445+
}
1446+
}
1447+
14591448
#[cfg(test)]
14601449
mod tests {
14611450
use bytes::Bytes;

0 commit comments

Comments
 (0)