Skip to content

Commit 1ba631b

Browse files
authored
feat: store safe block num as well (#11648)
1 parent 250785f commit 1ba631b

File tree

14 files changed

+130
-44
lines changed

14 files changed

+130
-44
lines changed

crates/blockchain-tree/src/externals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use reth_db_api::{cursor::DbCursorRO, transaction::DbTx};
77
use reth_node_types::NodeTypesWithDB;
88
use reth_primitives::StaticFileSegment;
99
use reth_provider::{
10-
providers::ProviderNodeTypes, FinalizedBlockReader, FinalizedBlockWriter, ProviderFactory,
10+
providers::ProviderNodeTypes, ChainStateBlockReader, ChainStateBlockWriter, ProviderFactory,
1111
StaticFileProviderFactory, StatsReader,
1212
};
1313
use reth_storage_errors::provider::ProviderResult;

crates/chain-state/src/chain_info.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ pub struct ChainInfoTracker {
2121
impl ChainInfoTracker {
2222
/// Create a new chain info container for the given canonical head and finalized header if it
2323
/// exists.
24-
pub fn new(head: SealedHeader, finalized: Option<SealedHeader>) -> Self {
24+
pub fn new(
25+
head: SealedHeader,
26+
finalized: Option<SealedHeader>,
27+
safe: Option<SealedHeader>,
28+
) -> Self {
2529
let (finalized_block, _) = watch::channel(finalized);
26-
let (safe_block, _) = watch::channel(None);
30+
let (safe_block, _) = watch::channel(safe);
2731

2832
Self {
2933
inner: Arc::new(ChainInfoInner {

crates/chain-state/src/in_memory.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,13 @@ impl CanonicalInMemoryState {
173173
numbers: BTreeMap<u64, B256>,
174174
pending: Option<BlockState>,
175175
finalized: Option<SealedHeader>,
176+
safe: Option<SealedHeader>,
176177
) -> Self {
177178
let in_memory_state = InMemoryState::new(blocks, numbers, pending);
178179
let header = in_memory_state
179180
.head_state()
180181
.map_or_else(SealedHeader::default, |state| state.block_ref().block().header.clone());
181-
let chain_info_tracker = ChainInfoTracker::new(header, finalized);
182+
let chain_info_tracker = ChainInfoTracker::new(header, finalized, safe);
182183
let (canon_state_notification_sender, _) =
183184
broadcast::channel(CANON_STATE_NOTIFICATION_CHANNEL_SIZE);
184185

@@ -193,13 +194,17 @@ impl CanonicalInMemoryState {
193194

194195
/// Create an empty state.
195196
pub fn empty() -> Self {
196-
Self::new(HashMap::default(), BTreeMap::new(), None, None)
197+
Self::new(HashMap::default(), BTreeMap::new(), None, None, None)
197198
}
198199

199200
/// Create a new in memory state with the given local head and finalized header
200201
/// if it exists.
201-
pub fn with_head(head: SealedHeader, finalized: Option<SealedHeader>) -> Self {
202-
let chain_info_tracker = ChainInfoTracker::new(head, finalized);
202+
pub fn with_head(
203+
head: SealedHeader,
204+
finalized: Option<SealedHeader>,
205+
safe: Option<SealedHeader>,
206+
) -> Self {
207+
let chain_info_tracker = ChainInfoTracker::new(head, finalized, safe);
203208
let in_memory_state = InMemoryState::default();
204209
let (canon_state_notification_sender, _) =
205210
broadcast::channel(CANON_STATE_NOTIFICATION_CHANNEL_SIZE);
@@ -1255,7 +1260,7 @@ mod tests {
12551260
numbers.insert(2, block2.block().hash());
12561261
numbers.insert(3, block3.block().hash());
12571262

1258-
let canonical_state = CanonicalInMemoryState::new(blocks, numbers, None, None);
1263+
let canonical_state = CanonicalInMemoryState::new(blocks, numbers, None, None, None);
12591264

12601265
let historical: StateProviderBox = Box::new(MockStateProvider);
12611266

@@ -1297,7 +1302,7 @@ mod tests {
12971302
let mut numbers = BTreeMap::new();
12981303
numbers.insert(1, hash);
12991304

1300-
let state = CanonicalInMemoryState::new(blocks, numbers, None, None);
1305+
let state = CanonicalInMemoryState::new(blocks, numbers, None, None, None);
13011306
let chain: Vec<_> = state.canonical_chain().collect();
13021307

13031308
assert_eq!(chain.len(), 1);

crates/cli/commands/src/stage/unwind.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use reth_node_builder::{NodeTypesWithDB, NodeTypesWithEngine};
1717
use reth_node_core::args::NetworkArgs;
1818
use reth_provider::{
1919
providers::ProviderNodeTypes, BlockExecutionWriter, BlockNumReader, ChainSpecProvider,
20-
FinalizedBlockReader, FinalizedBlockWriter, ProviderFactory, StaticFileProviderFactory,
20+
ChainStateBlockReader, ChainStateBlockWriter, ProviderFactory, StaticFileProviderFactory,
2121
};
2222
use reth_prune::PruneModes;
2323
use reth_stages::{

crates/consensus/beacon/src/engine/test_utils.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,13 @@ where
398398
let (header, seal) = sealed.into_parts();
399399
let genesis_block = SealedHeader::new(header, seal);
400400

401-
let blockchain_provider =
402-
BlockchainProvider::with_blocks(provider_factory.clone(), tree, genesis_block, None);
401+
let blockchain_provider = BlockchainProvider::with_blocks(
402+
provider_factory.clone(),
403+
tree,
404+
genesis_block,
405+
None,
406+
None,
407+
);
403408

404409
let pruner = Pruner::new_with_factory(
405410
provider_factory.clone(),

crates/engine/tree/src/persistence.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use reth_chain_state::ExecutedBlock;
44
use reth_errors::ProviderError;
55
use reth_provider::{
66
providers::ProviderNodeTypes, writer::UnifiedStorageWriter, BlockHashReader,
7-
DatabaseProviderFactory, FinalizedBlockWriter, ProviderFactory, StaticFileProviderFactory,
7+
ChainStateBlockWriter, DatabaseProviderFactory, ProviderFactory, StaticFileProviderFactory,
88
};
99
use reth_prune::{PrunerError, PrunerOutput, PrunerWithFactory};
1010
use reth_stages_api::{MetricEvent, MetricEventsSender};
@@ -97,6 +97,11 @@ impl<N: ProviderNodeTypes> PersistenceService<N> {
9797
provider.save_finalized_block_number(finalized_block)?;
9898
provider.commit()?;
9999
}
100+
PersistenceAction::SaveSafeBlock(safe_block) => {
101+
let provider = self.provider.database_provider_rw()?;
102+
provider.save_safe_block_number(safe_block)?;
103+
provider.commit()?;
104+
}
100105
}
101106
}
102107
Ok(())
@@ -176,6 +181,9 @@ pub enum PersistenceAction {
176181

177182
/// Update the persisted finalized block on disk
178183
SaveFinalizedBlock(u64),
184+
185+
/// Update the persisted safe block on disk
186+
SaveSafeBlock(u64),
179187
}
180188

181189
/// A handle to the persistence service
@@ -251,6 +259,14 @@ impl PersistenceHandle {
251259
self.send_action(PersistenceAction::SaveFinalizedBlock(finalized_block))
252260
}
253261

262+
/// Persists the finalized block number on disk.
263+
pub fn save_safe_block_number(
264+
&self,
265+
safe_block: u64,
266+
) -> Result<(), SendError<PersistenceAction>> {
267+
self.send_action(PersistenceAction::SaveSafeBlock(safe_block))
268+
}
269+
254270
/// Tells the persistence service to remove blocks above a certain block number. The removed
255271
/// blocks are returned by the service.
256272
///

crates/engine/tree/src/tree/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,8 +2401,13 @@ where
24012401
// if the safe block is not known, we can't update the safe block
24022402
return Err(OnForkChoiceUpdated::invalid_state())
24032403
}
2404-
Ok(Some(finalized)) => {
2405-
self.canonical_in_memory_state.set_safe(finalized);
2404+
Ok(Some(safe)) => {
2405+
if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
2406+
// we're also persisting the safe block on disk so we can reload it on
2407+
// restart this is required by optimism which queries the safe block: <https://github.com/ethereum-optimism/optimism/blob/c383eb880f307caa3ca41010ec10f30f08396b2e/op-node/rollup/sync/start.go#L65-L65>
2408+
let _ = self.persistence.save_safe_block_number(safe.number);
2409+
self.canonical_in_memory_state.set_safe(safe);
2410+
}
24062411
}
24072412
Err(err) => {
24082413
error!(target: "engine::tree", %err, "Failed to fetch safe block header");
@@ -2680,7 +2685,7 @@ mod tests {
26802685
let (header, seal) = sealed.into_parts();
26812686
let header = SealedHeader::new(header, seal);
26822687
let engine_api_tree_state = EngineApiTreeState::new(10, 10, header.num_hash());
2683-
let canonical_in_memory_state = CanonicalInMemoryState::with_head(header, None);
2688+
let canonical_in_memory_state = CanonicalInMemoryState::with_head(header, None, None);
26842689

26852690
let (to_payload_service, _payload_command_rx) = unbounded_channel();
26862691
let payload_builder = PayloadBuilderHandle::new(to_payload_service);
@@ -2744,7 +2749,7 @@ mod tests {
27442749
let last_executed_block = blocks.last().unwrap().clone();
27452750
let pending = Some(BlockState::new(last_executed_block));
27462751
self.tree.canonical_in_memory_state =
2747-
CanonicalInMemoryState::new(state_by_hash, hash_by_number, pending, None);
2752+
CanonicalInMemoryState::new(state_by_hash, hash_by_number, pending, None, None);
27482753

27492754
self.blocks = blocks.clone();
27502755
self.persist_blocks(

crates/stages/api/src/pipeline/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ pub use event::*;
77
use futures_util::Future;
88
use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH;
99
use reth_provider::{
10-
providers::ProviderNodeTypes, writer::UnifiedStorageWriter, DatabaseProviderFactory,
11-
FinalizedBlockReader, FinalizedBlockWriter, ProviderFactory, StageCheckpointReader,
10+
providers::ProviderNodeTypes, writer::UnifiedStorageWriter, ChainStateBlockReader,
11+
ChainStateBlockWriter, DatabaseProviderFactory, ProviderFactory, StageCheckpointReader,
1212
StageCheckpointWriter, StaticFileProviderFactory,
1313
};
1414
use reth_prune::PrunerBuilder;

crates/storage/db/src/tables/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ tables! {
416416
pub enum ChainStateKey {
417417
/// Last finalized block key
418418
LastFinalizedBlock,
419+
/// Last finalized block key
420+
LastSafeBlockBlock,
419421
}
420422

421423
impl Encode for ChainStateKey {
@@ -424,16 +426,17 @@ impl Encode for ChainStateKey {
424426
fn encode(self) -> Self::Encoded {
425427
match self {
426428
Self::LastFinalizedBlock => [0],
429+
Self::LastSafeBlockBlock => [1],
427430
}
428431
}
429432
}
430433

431434
impl Decode for ChainStateKey {
432435
fn decode(value: &[u8]) -> Result<Self, reth_db_api::DatabaseError> {
433-
if value == [0] {
434-
Ok(Self::LastFinalizedBlock)
435-
} else {
436-
Err(reth_db_api::DatabaseError::Decode)
436+
match value {
437+
[0] => Ok(Self::LastFinalizedBlock),
438+
[1] => Ok(Self::LastSafeBlockBlock),
439+
_ => Err(reth_db_api::DatabaseError::Decode),
437440
}
438441
}
439442
}

crates/storage/provider/src/providers/blockchain_provider.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{
22
providers::StaticFileProvider, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader,
33
BlockReader, BlockReaderIdExt, BlockSource, CanonChainTracker, CanonStateNotifications,
4-
CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory,
5-
DatabaseProviderRO, EvmEnvProvider, FinalizedBlockReader, HeaderProvider, ProviderError,
4+
CanonStateSubscriptions, ChainSpecProvider, ChainStateBlockReader, ChangeSetReader,
5+
DatabaseProviderFactory, DatabaseProviderRO, EvmEnvProvider, HeaderProvider, ProviderError,
66
ProviderFactory, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt,
77
RequestsProvider, StageCheckpointReader, StateProviderBox, StateProviderFactory, StateReader,
88
StaticFileProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider,
@@ -93,9 +93,23 @@ impl<N: ProviderNodeTypes> BlockchainProvider2<N> {
9393
.map(|num| provider.sealed_header(num))
9494
.transpose()?
9595
.flatten();
96+
let safe_header = provider
97+
.last_safe_block_number()?
98+
.or_else(|| {
99+
// for the purpose of this we can also use the finalized block if we don't have the
100+
// safe block
101+
provider.last_finalized_block_number().ok().flatten()
102+
})
103+
.map(|num| provider.sealed_header(num))
104+
.transpose()?
105+
.flatten();
96106
Ok(Self {
97107
database,
98-
canonical_in_memory_state: CanonicalInMemoryState::with_head(latest, finalized_header),
108+
canonical_in_memory_state: CanonicalInMemoryState::with_head(
109+
latest,
110+
finalized_header,
111+
safe_header,
112+
),
99113
})
100114
}
101115

crates/storage/provider/src/providers/database/provider.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use crate::{
77
},
88
writer::UnifiedStorageWriter,
99
AccountReader, BlockExecutionReader, BlockExecutionWriter, BlockHashReader, BlockNumReader,
10-
BlockReader, BlockWriter, BundleStateInit, DBProvider, EvmEnvProvider, FinalizedBlockReader,
11-
FinalizedBlockWriter, HashingWriter, HeaderProvider, HeaderSyncGap, HeaderSyncGapProvider,
12-
HistoricalStateProvider, HistoryWriter, LatestStateProvider, OriginalValuesKnown,
13-
ProviderError, PruneCheckpointReader, PruneCheckpointWriter, RequestsProvider, RevertsInit,
14-
StageCheckpointReader, StateChangeWriter, StateProviderBox, StateReader, StateWriter,
15-
StaticFileProviderFactory, StatsReader, StorageReader, StorageTrieWriter, TransactionVariant,
16-
TransactionsProvider, TransactionsProviderExt, TrieWriter, WithdrawalsProvider,
10+
BlockReader, BlockWriter, BundleStateInit, ChainStateBlockReader, ChainStateBlockWriter,
11+
DBProvider, EvmEnvProvider, HashingWriter, HeaderProvider, HeaderSyncGap,
12+
HeaderSyncGapProvider, HistoricalStateProvider, HistoryWriter, LatestStateProvider,
13+
OriginalValuesKnown, ProviderError, PruneCheckpointReader, PruneCheckpointWriter,
14+
RequestsProvider, RevertsInit, StageCheckpointReader, StateChangeWriter, StateProviderBox,
15+
StateReader, StateWriter, StaticFileProviderFactory, StatsReader, StorageReader,
16+
StorageTrieWriter, TransactionVariant, TransactionsProvider, TransactionsProviderExt,
17+
TrieWriter, WithdrawalsProvider,
1718
};
1819
use alloy_eips::BlockHashOrNumber;
1920
use alloy_primitives::{keccak256, Address, BlockHash, BlockNumber, TxHash, TxNumber, B256, U256};
@@ -3596,7 +3597,7 @@ impl<TX: DbTx, Spec: Send + Sync> StatsReader for DatabaseProvider<TX, Spec> {
35963597
}
35973598
}
35983599

3599-
impl<TX: DbTx, Spec: Send + Sync> FinalizedBlockReader for DatabaseProvider<TX, Spec> {
3600+
impl<TX: DbTx, Spec: Send + Sync> ChainStateBlockReader for DatabaseProvider<TX, Spec> {
36003601
fn last_finalized_block_number(&self) -> ProviderResult<Option<BlockNumber>> {
36013602
let mut finalized_blocks = self
36023603
.tx
@@ -3608,14 +3609,32 @@ impl<TX: DbTx, Spec: Send + Sync> FinalizedBlockReader for DatabaseProvider<TX,
36083609
let last_finalized_block_number = finalized_blocks.pop_first().map(|pair| pair.1);
36093610
Ok(last_finalized_block_number)
36103611
}
3612+
3613+
fn last_safe_block_number(&self) -> ProviderResult<Option<BlockNumber>> {
3614+
let mut finalized_blocks = self
3615+
.tx
3616+
.cursor_read::<tables::ChainState>()?
3617+
.walk(Some(tables::ChainStateKey::LastSafeBlockBlock))?
3618+
.take(1)
3619+
.collect::<Result<BTreeMap<tables::ChainStateKey, BlockNumber>, _>>()?;
3620+
3621+
let last_finalized_block_number = finalized_blocks.pop_first().map(|pair| pair.1);
3622+
Ok(last_finalized_block_number)
3623+
}
36113624
}
36123625

3613-
impl<TX: DbTxMut, Spec: Send + Sync> FinalizedBlockWriter for DatabaseProvider<TX, Spec> {
3626+
impl<TX: DbTxMut, Spec: Send + Sync> ChainStateBlockWriter for DatabaseProvider<TX, Spec> {
36143627
fn save_finalized_block_number(&self, block_number: BlockNumber) -> ProviderResult<()> {
36153628
Ok(self
36163629
.tx
36173630
.put::<tables::ChainState>(tables::ChainStateKey::LastFinalizedBlock, block_number)?)
36183631
}
3632+
3633+
fn save_safe_block_number(&self, block_number: BlockNumber) -> ProviderResult<()> {
3634+
Ok(self
3635+
.tx
3636+
.put::<tables::ChainState>(tables::ChainStateKey::LastSafeBlockBlock, block_number)?)
3637+
}
36193638
}
36203639

36213640
impl<TX: DbTx + 'static, Spec: Send + Sync + 'static> DBProvider for DatabaseProvider<TX, Spec> {

crates/storage/provider/src/providers/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::{
22
AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt,
33
BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications,
4-
CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory,
5-
EvmEnvProvider, FinalizedBlockReader, FullExecutionDataProvider, HeaderProvider, ProviderError,
6-
PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, RequestsProvider,
4+
CanonStateSubscriptions, ChainSpecProvider, ChainStateBlockReader, ChangeSetReader,
5+
DatabaseProviderFactory, EvmEnvProvider, FullExecutionDataProvider, HeaderProvider,
6+
ProviderError, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, RequestsProvider,
77
StageCheckpointReader, StateProviderBox, StateProviderFactory, StaticFileProviderFactory,
88
TransactionVariant, TransactionsProvider, TreeViewer, WithdrawalsProvider,
99
};
@@ -109,8 +109,9 @@ impl<N: ProviderNodeTypes> BlockchainProvider<N> {
109109
tree: Arc<dyn TreeViewer>,
110110
latest: SealedHeader,
111111
finalized: Option<SealedHeader>,
112+
safe: Option<SealedHeader>,
112113
) -> Self {
113-
Self { database, tree, chain_info: ChainInfoTracker::new(latest, finalized) }
114+
Self { database, tree, chain_info: ChainInfoTracker::new(latest, finalized, safe) }
114115
}
115116

116117
/// Create a new provider using only the database and the tree, fetching the latest header from
@@ -128,11 +129,18 @@ impl<N: ProviderNodeTypes> BlockchainProvider<N> {
128129
.transpose()?
129130
.flatten();
130131

132+
let safe_header = provider
133+
.last_safe_block_number()?
134+
.map(|num| provider.sealed_header(num))
135+
.transpose()?
136+
.flatten();
137+
131138
Ok(Self::with_blocks(
132139
database,
133140
tree,
134141
SealedHeader::new(latest_header, best.best_hash),
135142
finalized_header,
143+
safe_header,
136144
))
137145
}
138146

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
use alloy_primitives::BlockNumber;
22
use reth_errors::ProviderResult;
33

4-
/// Functionality to read the last known finalized block from the database.
5-
pub trait FinalizedBlockReader: Send + Sync {
4+
/// Functionality to read the last known chain blocks from the database.
5+
pub trait ChainStateBlockReader: Send + Sync {
66
/// Returns the last finalized block number.
77
///
88
/// If no finalized block has been written yet, this returns `None`.
99
fn last_finalized_block_number(&self) -> ProviderResult<Option<BlockNumber>>;
10+
/// Returns the last safe block number.
11+
///
12+
/// If no safe block has been written yet, this returns `None`.
13+
fn last_safe_block_number(&self) -> ProviderResult<Option<BlockNumber>>;
1014
}
1115

12-
/// Functionality to write the last known finalized block to the database.
13-
pub trait FinalizedBlockWriter: Send + Sync {
16+
/// Functionality to write the last known chain blocks to the database.
17+
pub trait ChainStateBlockWriter: Send + Sync {
1418
/// Saves the given finalized block number in the DB.
1519
fn save_finalized_block_number(&self, block_number: BlockNumber) -> ProviderResult<()>;
20+
21+
/// Saves the given safe block number in the DB.
22+
fn save_safe_block_number(&self, block_number: BlockNumber) -> ProviderResult<()>;
1623
}

0 commit comments

Comments
 (0)