Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 2ab715f

Browse files
arkparbkchr
andauthored
Fixed restoring state-db journals on startup (#8494)
* Fixed restoring state-db journals on startup * Improved documentation a bit * Update client/state-db/src/lib.rs Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Bastian Köcher <[email protected]>
1 parent d19c0e1 commit 2ab715f

File tree

2 files changed

+83
-28
lines changed

2 files changed

+83
-28
lines changed

client/state-db/src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,24 @@
1616
// You should have received a copy of the GNU General Public License
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

19-
//! State database maintenance. Handles canonicalization and pruning in the database. The input to
20-
//! this module is a `ChangeSet` which is basically a list of key-value pairs (trie nodes) that
21-
//! were added or deleted during block execution.
19+
//! State database maintenance. Handles canonicalization and pruning in the database.
2220
//!
2321
//! # Canonicalization.
2422
//! Canonicalization window tracks a tree of blocks identified by header hash. The in-memory
25-
//! overlay allows to get any node that was inserted in any of the blocks within the window.
26-
//! The tree is journaled to the backing database and rebuilt on startup.
23+
//! overlay allows to get any trie node that was inserted in any of the blocks within the window.
24+
//! The overlay is journaled to the backing database and rebuilt on startup.
25+
//! There's a limit of 32 blocks that may have the same block number in the canonicalization window.
26+
//!
2727
//! Canonicalization function selects one root from the top of the tree and discards all other roots
28-
//! and their subtrees.
28+
//! and their subtrees. Upon canonicalization all trie nodes that were inserted in the block are added to
29+
//! the backing DB and block tracking is moved to the pruning window, where no forks are allowed.
30+
//!
31+
//! # Canonicalization vs Finality
32+
//! Database engine uses a notion of canonicality, rather then finality. A canonical block may not be yet finalized
33+
//! from the perspective of the consensus engine, but it still can't be reverted in the database. Most of the time
34+
//! during normal operation last canonical block is the same as last finalized. However if finality stall for a
35+
//! long duration for some reason, there's only a certain number of blocks that can fit in the non-canonical overlay,
36+
//! so canonicalization of an unfinalized block may be forced.
2937
//!
3038
//! # Pruning.
3139
//! See `RefWindow` for pruning algorithm details. `StateDb` prunes on each canonicalization until
@@ -89,6 +97,8 @@ pub enum Error<E: fmt::Debug> {
8997
InvalidParent,
9098
/// Invalid pruning mode specified. Contains expected mode.
9199
InvalidPruningMode(String),
100+
/// Too many unfinalized sibling blocks inserted.
101+
TooManySiblingBlocks,
92102
}
93103

94104
/// Pinning error type.
@@ -112,6 +122,7 @@ impl<E: fmt::Debug> fmt::Debug for Error<E> {
112122
Error::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"),
113123
Error::InvalidParent => write!(f, "Trying to insert block with unknown parent"),
114124
Error::InvalidPruningMode(e) => write!(f, "Expected pruning mode: {}", e),
125+
Error::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"),
115126
}
116127
}
117128
}

client/state-db/src/noncanonical.rs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use log::trace;
3030

3131
const NON_CANONICAL_JOURNAL: &[u8] = b"noncanonical_journal";
3232
const LAST_CANONICAL: &[u8] = b"last_canonical";
33+
const MAX_BLOCKS_PER_LEVEL: u64 = 32;
3334

3435
/// See module documentation.
3536
#[derive(parity_util_mem_derive::MallocSizeOf)]
@@ -162,28 +163,30 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
162163
let mut total: u64 = 0;
163164
block += 1;
164165
loop {
165-
let mut index: u64 = 0;
166166
let mut level = Vec::new();
167-
loop {
167+
for index in 0 .. MAX_BLOCKS_PER_LEVEL {
168168
let journal_key = to_journal_key(block, index);
169-
match db.get_meta(&journal_key).map_err(|e| Error::Db(e))? {
170-
Some(record) => {
171-
let record: JournalRecord<BlockHash, Key> = Decode::decode(&mut record.as_slice())?;
172-
let inserted = record.inserted.iter().map(|(k, _)| k.clone()).collect();
173-
let overlay = BlockOverlay {
174-
hash: record.hash.clone(),
175-
journal_key,
176-
inserted: inserted,
177-
deleted: record.deleted,
178-
};
179-
insert_values(&mut values, record.inserted);
180-
trace!(target: "state-db", "Uncanonicalized journal entry {}.{} ({} inserted, {} deleted)", block, index, overlay.inserted.len(), overlay.deleted.len());
181-
level.push(overlay);
182-
parents.insert(record.hash, record.parent_hash);
183-
index += 1;
184-
total += 1;
185-
},
186-
None => break,
169+
if let Some(record) = db.get_meta(&journal_key).map_err(|e| Error::Db(e))? {
170+
let record: JournalRecord<BlockHash, Key> = Decode::decode(&mut record.as_slice())?;
171+
let inserted = record.inserted.iter().map(|(k, _)| k.clone()).collect();
172+
let overlay = BlockOverlay {
173+
hash: record.hash.clone(),
174+
journal_key,
175+
inserted: inserted,
176+
deleted: record.deleted,
177+
};
178+
insert_values(&mut values, record.inserted);
179+
trace!(
180+
target: "state-db",
181+
"Uncanonicalized journal entry {}.{} ({} inserted, {} deleted)",
182+
block,
183+
index,
184+
overlay.inserted.len(),
185+
overlay.deleted.len()
186+
);
187+
level.push(overlay);
188+
parents.insert(record.hash, record.parent_hash);
189+
total += 1;
187190
}
188191
}
189192
if level.is_empty() {
@@ -241,6 +244,10 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
241244
.expect("number is [front_block_number .. front_block_number + levels.len()) is asserted in precondition; qed")
242245
};
243246

247+
if level.len() >= MAX_BLOCKS_PER_LEVEL as usize {
248+
return Err(Error::TooManySiblingBlocks);
249+
}
250+
244251
let index = level.len() as u64;
245252
let journal_key = to_journal_key(number, index);
246253

@@ -513,7 +520,7 @@ mod tests {
513520
use std::io;
514521
use sp_core::H256;
515522
use super::{NonCanonicalOverlay, to_journal_key};
516-
use crate::{ChangeSet, CommitSet};
523+
use crate::{ChangeSet, CommitSet, MetaDb};
517524
use crate::test::{make_db, make_changeset};
518525

519526
fn contains(overlay: &NonCanonicalOverlay<H256, H256>, key: u64) -> bool {
@@ -716,7 +723,6 @@ mod tests {
716723

717724
#[test]
718725
fn complex_tree() {
719-
use crate::MetaDb;
720726
let mut db = make_db(&[]);
721727

722728
// - 1 - 1_1 - 1_1_1
@@ -958,4 +964,42 @@ mod tests {
958964
assert!(!contains(&overlay, 1));
959965
assert!(overlay.pinned.is_empty());
960966
}
967+
968+
#[test]
969+
fn restore_from_journal_after_canonicalize_no_first() {
970+
// This test discards a branch that is journaled under a non-zero index on level 1,
971+
// making sure all journals are loaded for each level even if some of them are missing.
972+
let root = H256::random();
973+
let h1 = H256::random();
974+
let h2 = H256::random();
975+
let h11 = H256::random();
976+
let h21 = H256::random();
977+
let mut db = make_db(&[]);
978+
let mut overlay = NonCanonicalOverlay::<H256, H256>::new(&db).unwrap();
979+
db.commit(&overlay.insert::<io::Error>(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap());
980+
db.commit(&overlay.insert::<io::Error>(&h1, 11, &root, make_changeset(&[1], &[])).unwrap());
981+
db.commit(&overlay.insert::<io::Error>(&h2, 11, &root, make_changeset(&[2], &[])).unwrap());
982+
db.commit(&overlay.insert::<io::Error>(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap());
983+
db.commit(&overlay.insert::<io::Error>(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap());
984+
let mut commit = CommitSet::default();
985+
overlay.canonicalize::<io::Error>(&root, &mut commit).unwrap();
986+
overlay.canonicalize::<io::Error>(&h2, &mut commit).unwrap(); // h11 should stay in the DB
987+
db.commit(&commit);
988+
overlay.apply_pending();
989+
assert_eq!(overlay.levels.len(), 1);
990+
assert!(contains(&overlay, 21));
991+
assert!(!contains(&overlay, 11));
992+
assert!(db.get_meta(&to_journal_key(12, 1)).unwrap().is_some());
993+
assert!(db.get_meta(&to_journal_key(12, 0)).unwrap().is_none());
994+
995+
// Restore into a new overlay and check that journaled value exists.
996+
let mut overlay = NonCanonicalOverlay::<H256, H256>::new(&db).unwrap();
997+
assert!(contains(&overlay, 21));
998+
999+
let mut commit = CommitSet::default();
1000+
overlay.canonicalize::<io::Error>(&h21, &mut commit).unwrap(); // h11 should stay in the DB
1001+
db.commit(&commit);
1002+
overlay.apply_pending();
1003+
assert!(!contains(&overlay, 21));
1004+
}
9611005
}

0 commit comments

Comments
 (0)