@@ -41,6 +41,7 @@ use reth_rpc_types::{
41
41
use reth_stages_api:: ControlFlow ;
42
42
use reth_trie:: { updates:: TrieUpdates , HashedPostState } ;
43
43
use std:: {
44
+ cmp:: Ordering ,
44
45
collections:: { btree_map, hash_map, BTreeMap , HashMap , HashSet , VecDeque } ,
45
46
fmt:: Debug ,
46
47
ops:: Bound ,
@@ -530,6 +531,7 @@ where
530
531
last_persisted_block_hash : header. hash ( ) ,
531
532
last_persisted_block_number : best_block_number,
532
533
rx : None ,
534
+ remove_above_state : None ,
533
535
} ;
534
536
535
537
let ( tx, outgoing) = tokio:: sync:: mpsc:: unbounded_channel ( ) ;
@@ -1050,14 +1052,21 @@ where
1050
1052
/// If we're currently awaiting a response this will try to receive the response (non-blocking)
1051
1053
/// or send a new persistence action if necessary.
1052
1054
fn advance_persistence ( & mut self ) -> Result < ( ) , TryRecvError > {
1053
- if self . should_persist ( ) && !self . persistence_state . in_progress ( ) {
1054
- let blocks_to_persist = self . get_canonical_blocks_to_persist ( ) ;
1055
- if blocks_to_persist. is_empty ( ) {
1056
- debug ! ( target: "engine" , "Returned empty set of blocks to persist" ) ;
1057
- } else {
1055
+ if !self . persistence_state . in_progress ( ) {
1056
+ if let Some ( new_tip_num) = self . persistence_state . remove_above_state . take ( ) {
1057
+ debug ! ( target: "engine" , ?new_tip_num, "Removing blocks using persistence task" ) ;
1058
1058
let ( tx, rx) = oneshot:: channel ( ) ;
1059
- let _ = self . persistence . save_blocks ( blocks_to_persist , tx) ;
1059
+ let _ = self . persistence . remove_blocks_above ( new_tip_num , tx) ;
1060
1060
self . persistence_state . start ( rx) ;
1061
+ } else if self . should_persist ( ) {
1062
+ let blocks_to_persist = self . get_canonical_blocks_to_persist ( ) ;
1063
+ if blocks_to_persist. is_empty ( ) {
1064
+ debug ! ( target: "engine" , "Returned empty set of blocks to persist" ) ;
1065
+ } else {
1066
+ let ( tx, rx) = oneshot:: channel ( ) ;
1067
+ let _ = self . persistence . save_blocks ( blocks_to_persist, tx) ;
1068
+ self . persistence_state . start ( rx) ;
1069
+ }
1061
1070
}
1062
1071
}
1063
1072
@@ -1794,13 +1803,55 @@ where
1794
1803
None
1795
1804
}
1796
1805
1806
+ /// This determines whether or not we should remove blocks from the chain, based on a canonical
1807
+ /// chain update.
1808
+ ///
1809
+ /// If the chain update is a reorg:
1810
+ /// * is the new chain behind the last persisted block, or
1811
+ /// * if the root of the new chain is at the same height as the last persisted block, is it a
1812
+ /// different block
1813
+ ///
1814
+ /// If either of these are true, then this returns the height of the first block. Otherwise,
1815
+ /// this returns [`None`]. This should be used to check whether or not we should be sending a
1816
+ /// remove command to the persistence task.
1817
+ fn find_disk_reorg ( & self , chain_update : & NewCanonicalChain ) -> Option < u64 > {
1818
+ let NewCanonicalChain :: Reorg { new, old : _ } = chain_update else { return None } ;
1819
+
1820
+ let BlockNumHash { number : new_num, hash : new_hash } =
1821
+ new. first ( ) . map ( |block| block. block . num_hash ( ) ) ?;
1822
+
1823
+ match new_num. cmp ( & self . persistence_state . last_persisted_block_number ) {
1824
+ Ordering :: Greater => {
1825
+ // new number is above the last persisted block so the reorg can be performed
1826
+ // entirely in memory
1827
+ None
1828
+ }
1829
+ Ordering :: Equal => {
1830
+ // new number is the same, if the hash is the same then we should not need to remove
1831
+ // any blocks
1832
+ ( self . persistence_state . last_persisted_block_hash != new_hash) . then_some ( new_num)
1833
+ }
1834
+ Ordering :: Less => {
1835
+ // this means we are below the last persisted block and must remove on disk blocks
1836
+ Some ( new_num)
1837
+ }
1838
+ }
1839
+ }
1840
+
1797
1841
/// Invoked when we the canonical chain has been updated.
1798
1842
///
1799
1843
/// This is invoked on a valid forkchoice update, or if we can make the target block canonical.
1800
1844
fn on_canonical_chain_update ( & mut self , chain_update : NewCanonicalChain ) {
1801
1845
trace ! ( target: "engine" , new_blocks = %chain_update. new_block_count( ) , reorged_blocks = %chain_update. reorged_block_count( ) , "applying new chain update" ) ;
1802
1846
let start = Instant :: now ( ) ;
1803
1847
1848
+ // schedule a remove_above call if we have an on-disk reorg
1849
+ if let Some ( height) = self . find_disk_reorg ( & chain_update) {
1850
+ // calculate the new tip by subtracting one from the lowest part of the chain
1851
+ let new_tip_num = height. saturating_sub ( 1 ) ;
1852
+ self . persistence_state . schedule_removal ( new_tip_num) ;
1853
+ }
1854
+
1804
1855
// update the tracked canonical head
1805
1856
self . state . tree_state . set_canonical_head ( chain_update. tip ( ) . num_hash ( ) ) ;
1806
1857
@@ -2308,6 +2359,9 @@ pub struct PersistenceState {
2308
2359
///
2309
2360
/// This tracks the chain height that is persisted on disk
2310
2361
last_persisted_block_number : u64 ,
2362
+ /// The block above which blocks should be removed from disk, because there has been an on disk
2363
+ /// reorg.
2364
+ remove_above_state : Option < u64 > ,
2311
2365
}
2312
2366
2313
2367
impl PersistenceState {
@@ -2322,6 +2376,12 @@ impl PersistenceState {
2322
2376
self . rx = Some ( ( rx, Instant :: now ( ) ) ) ;
2323
2377
}
2324
2378
2379
+ /// Sets the `remove_above_state`, to the new tip number specified.
2380
+ fn schedule_removal ( & mut self , new_tip_num : u64 ) {
2381
+ // TODO: what about multiple on-disk reorgs in a row?
2382
+ self . remove_above_state = Some ( new_tip_num) ;
2383
+ }
2384
+
2325
2385
/// Sets state for a finished persistence task.
2326
2386
fn finish ( & mut self , last_persisted_block_hash : B256 , last_persisted_block_number : u64 ) {
2327
2387
trace ! ( target: "engine" , block= %last_persisted_block_number, hash=%last_persisted_block_hash, "updating persistence state" ) ;
0 commit comments