Skip to content
This repository was archived by the owner on Feb 1, 2024. It is now read-only.

Commit f6d005b

Browse files
committed
Keep block height with state roots to be pruned
Keep the block height with the state root to be pruned: This prevents roots from being pruned if there is a lot of fork swapping, and also allows the abandoned chains' roots to be pruned at the prune depth as well. This required the state_root_pruning_queue to be made a binary heap for efficient access. Additionally: - renamed `decimate` to `execute`, which now takes an `at_depth` parameter Signed-off-by: Peter Schwarz <pschwarz@bitwise.io>
1 parent 930df3c commit f6d005b

2 files changed

Lines changed: 93 additions & 41 deletions

File tree

validator/src/journal/chain.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ impl<BC: BlockCache + 'static, BV: BlockValidator + 'static, CW: ChainWriter + '
283283
&result
284284
.current_chain
285285
.iter()
286-
.map(|block| block.state_root_hash())
286+
.map(|block| (block.block_num(), block.state_root_hash()))
287287
.collect::<Vec<_>>(),
288288
);
289289

@@ -359,16 +359,17 @@ impl<BC: BlockCache + 'static, BV: BlockValidator + 'static, CW: ChainWriter + '
359359

360360
let chain_head_block_num = state.chain_head.as_ref().unwrap().block_num();
361361
if chain_head_block_num + 1 > self.state_pruning_block_depth as u64 {
362-
let prune_at = chain_head_block_num - (self.state_pruning_block_depth as u64);
362+
let prune_at = chain_head_block_num - (self.state_pruning_block_depth as u64);
363363
match state.chain_reader.get_block_by_block_num(prune_at) {
364-
Ok(Some(block)) => state.state_pruning_manager.add_to_queue(block.state_root_hash()),
364+
Ok(Some(block)) => state.state_pruning_manager.add_to_queue(block.block_num(),block.state_root_hash()),
365365
Ok(None) => warn!("No block at block height {}; ignoring...", prune_at),
366366
Err(err) => error!("Unable to fetch block at height {}: {:?}", prune_at, err)
367367
}
368+
369+
// Execute pruning:
370+
state.state_pruning_manager.execute(prune_at)
368371
}
369372

370-
// Execute pruning:
371-
state.state_pruning_manager.decimate()
372373
} else {
373374
info!("Rejected new chain head: {}", new_block);
374375
}

validator/src/state/state_pruning_manager.rs

Lines changed: 87 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* limitations under the License.
1515
* ------------------------------------------------------------------------------
1616
*/
17-
use std::collections::VecDeque;
17+
use std::cmp::Ordering;
18+
use std::collections::BinaryHeap;
1819

1920
use database::lmdb::LmdbDatabase;
2021
use metrics;
@@ -33,76 +34,105 @@ lazy_static! {
3334
/// abandoned and then re-chosen as the primary chain.
3435
pub struct StatePruningManager {
3536
// Contains the state root hashes slated for pruning
36-
state_root_prune_queue: VecDeque<String>,
37+
state_root_prune_queue: BinaryHeap<PruneCandidate>,
3738
state_database: LmdbDatabase,
3839
}
3940

41+
#[derive(Eq, PartialEq, Debug, Ord)]
42+
struct PruneCandidate(u64, String);
43+
44+
impl PartialOrd for PruneCandidate {
45+
fn partial_cmp(&self, other: &PruneCandidate) -> Option<Ordering> {
46+
Some(Ordering::reverse(self.0.cmp(&other.0)))
47+
}
48+
}
49+
4050
impl StatePruningManager {
4151
pub fn new(state_database: LmdbDatabase) -> Self {
4252
StatePruningManager {
43-
state_root_prune_queue: VecDeque::new(),
53+
state_root_prune_queue: BinaryHeap::new(),
4454
state_database,
4555
}
4656
}
4757

4858
/// Updates the pruning queue. Abandoned roots will be added to the queue.
4959
/// Added roots will be removed from the queue. This ensures that the state
5060
/// roots won't be removed, regardless of the chain state.
51-
pub fn update_queue(&mut self, added_roots: &[&str], _abandoned_roots: &[&str]) {
61+
pub fn update_queue(&mut self, added_roots: &[&str], abandoned_roots: &[(u64, &str)]) {
62+
// add the roots that have been abandoned.
63+
for (height, state_root_hash) in abandoned_roots {
64+
self.add_to_queue(*height, state_root_hash);
65+
}
5266
// Remove any state root hashes from the pruning queue that we may have switched
5367
// back too from an alternate chain
54-
added_roots.iter().for_each(|state_root_hash| {
55-
self.state_root_prune_queue.retain(|hash| {
56-
if hash != state_root_hash {
68+
let mut new_queue = BinaryHeap::with_capacity(0);
69+
::std::mem::swap(&mut self.state_root_prune_queue, &mut new_queue);
70+
self.state_root_prune_queue = new_queue
71+
.into_iter()
72+
.filter(|candidate| {
73+
if !added_roots.contains(&candidate.1.as_str()) {
5774
true
5875
} else {
59-
debug!("Removing {} from pruning queue", state_root_hash);
76+
debug!("Removing {} from pruning queue", candidate.1);
6077
false
6178
}
6279
})
63-
});
80+
.collect();
6481
}
6582

6683
/// Add a single state root to the pruning queue.
67-
pub fn add_to_queue(&mut self, state_root_hash: &str) {
84+
pub fn add_to_queue(&mut self, height: u64, state_root_hash: &str) {
6885
let state_root_hash = state_root_hash.into();
69-
if !self.state_root_prune_queue.contains(&state_root_hash) {
86+
if !self.state_root_prune_queue
87+
.iter()
88+
.any(|candidate| candidate.1 == state_root_hash)
89+
{
7090
debug!("Adding {} to pruning queue", state_root_hash);
71-
self.state_root_prune_queue.push_back(state_root_hash);
91+
self.state_root_prune_queue
92+
.push(PruneCandidate(height, state_root_hash));
7293
}
7394
}
7495

75-
/// Remove the first ten percent (floored) of the prune queue.
76-
pub fn decimate(&mut self) {
77-
let prune_count = (self.state_root_prune_queue.len() as f64 / 10.0).floor() as usize;
96+
/// Executes prune on any state root hash at or below the given depth.
97+
pub fn execute(&mut self, at_depth: u64) {
98+
let mut prune_candidates = vec![];
7899

79-
let mut total_pruned_entries: usize = 0;
80-
for _ in 0..prune_count {
81-
if let Some(state_root) = self.state_root_prune_queue.pop_front() {
82-
match MerkleDatabase::prune(&self.state_database, &state_root) {
83-
Ok(removed_keys) => {
84-
total_pruned_entries += removed_keys.len();
85-
86-
let mut state_roots_pruned_count =
87-
COLLECTOR.counter("StatePruneManager.state_roots_pruned", None, None);
88-
state_roots_pruned_count.inc();
89-
90-
// the state root was not pruned (it is likely the root of a
91-
// fork), so push it back into the queue.
92-
if removed_keys.is_empty() {
93-
self.state_root_prune_queue.push_back(state_root);
94-
}
95-
}
96-
Err(err) => {
97-
error!("Unable to prune state root {}: {:?}", state_root, err);
98-
self.state_root_prune_queue.push_back(state_root);
99-
}
100+
loop {
101+
if let Some(candidate) = self.state_root_prune_queue.pop() {
102+
if candidate.0 <= at_depth {
103+
prune_candidates.push(candidate);
104+
} else {
105+
self.state_root_prune_queue.push(candidate);
106+
break;
100107
}
101108
} else {
102109
break;
103110
}
104111
}
105112

113+
let mut total_pruned_entries: usize = 0;
114+
for candidate in prune_candidates {
115+
match MerkleDatabase::prune(&self.state_database, &candidate.1) {
116+
Ok(removed_keys) => {
117+
total_pruned_entries += removed_keys.len();
118+
119+
let mut state_roots_pruned_count =
120+
COLLECTOR.counter("StatePruneManager.state_roots_pruned", None, None);
121+
state_roots_pruned_count.inc();
122+
123+
// the state root was not pruned (it is likely the root of a
124+
// fork), so push it back into the queue.
125+
if removed_keys.is_empty() {
126+
self.state_root_prune_queue.push(candidate);
127+
}
128+
}
129+
Err(err) => {
130+
error!("Unable to prune state root {}: {:?}", candidate.1, err);
131+
self.state_root_prune_queue.push(candidate);
132+
}
133+
}
134+
}
135+
106136
if total_pruned_entries > 0 {
107137
info!(
108138
"Pruned {} keys from the Global state Database",
@@ -111,3 +141,24 @@ impl StatePruningManager {
111141
}
112142
}
113143
}
144+
145+
#[cfg(test)]
146+
mod tests {
147+
use super::*;
148+
149+
#[test]
150+
fn ordering_candidates() {
151+
let mut heap = ::std::collections::BinaryHeap::new();
152+
153+
heap.push(PruneCandidate(2, "two".into()));
154+
heap.push(PruneCandidate(3, "three".into()));
155+
heap.push(PruneCandidate(3, "another_three".into()));
156+
heap.push(PruneCandidate(4, "four".into()));
157+
158+
assert_eq!(heap.pop(), Some(PruneCandidate(2, "two".into())));
159+
assert_eq!(heap.pop(), Some(PruneCandidate(3, "another_three".into())));
160+
assert_eq!(heap.pop(), Some(PruneCandidate(3, "three".into())));
161+
assert_eq!(heap.pop(), Some(PruneCandidate(4, "four".into())));
162+
assert_eq!(heap.pop(), None);
163+
}
164+
}

0 commit comments

Comments
 (0)