Support reference-counting state backend. (#5769)

* Optimize pinning

* Ref counting state backend

* Style

Co-Authored-By: Wei Tang <hi@that.world>

* Update Cargo.lock

* Handle empty node

Co-authored-by: Wei Tang <hi@that.world>
This commit is contained in:
Arkadiy Paronyan
2020-04-27 12:24:50 +02:00
committed by GitHub
parent 636ddd95d2
commit 64ed36d093
11 changed files with 281 additions and 189 deletions
+3 -2
View File
@@ -4701,11 +4701,12 @@ dependencies = [
[[package]]
name = "parity-db"
version = "0.1.0"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4174d70be686b0d7cdee964b2d723e1461e28390c8804f01efc907d81043be9"
checksum = "00d595e372d119261593297debbe4193811a4dc811d2a1ccbb8caaa6666ad7ab"
dependencies = [
"blake2-rfc",
"crc32fast",
"libc",
"log",
"memmap",
+1 -1
View File
@@ -34,7 +34,7 @@ sp-trie = { version = "2.0.0-dev", path = "../../primitives/trie" }
sp-consensus = { version = "0.8.0-dev", path = "../../primitives/consensus/common" }
sp-blockchain = { version = "2.0.0-dev", path = "../../primitives/blockchain" }
sp-database = { version = "2.0.0-dev", path = "../../primitives/database" }
parity-db = { version = "0.1", optional = true }
parity-db = { version = "0.1.2", optional = true }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-dev", path = "../../utils/prometheus" }
[dev-dependencies]
+43 -11
View File
@@ -109,8 +109,9 @@ pub type DbState<B> = sp_state_machine::TrieBackend<
Arc<dyn sp_state_machine::Storage<HashFor<B>>>, HashFor<B>
>;
const DB_HASH_LEN: usize = 32;
/// Hash type that this backend uses for the database.
pub type DbHash = [u8; 32];
pub type DbHash = [u8; DB_HASH_LEN];
/// A reference tracking state.
///
@@ -314,6 +315,13 @@ impl DatabaseSettingsSrc {
DatabaseSettingsSrc::Custom(_) => None,
}
}
/// Check if database supports internal ref counting for state data.
pub fn supports_ref_counting(&self) -> bool {
match self {
DatabaseSettingsSrc::ParityDb { .. } => true,
_ => false,
}
}
}
/// Create an instance of db-backed client.
@@ -716,13 +724,18 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block> for Bloc
struct StorageDb<Block: BlockT> {
pub db: Arc<dyn Database<DbHash>>,
pub state_db: StateDb<Block::Hash, Vec<u8>>,
prefix_keys: bool,
}
impl<Block: BlockT> sp_state_machine::Storage<HashFor<Block>> for StorageDb<Block> {
fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result<Option<DBValue>, String> {
let key = prefixed_key::<HashFor<Block>>(key, prefix);
self.state_db.get(&key, self)
.map_err(|e| format!("Database backend error: {:?}", e))
if self.prefix_keys {
let key = prefixed_key::<HashFor<Block>>(key, prefix);
self.state_db.get(&key, self)
} else {
self.state_db.get(key.as_ref(), self)
}
.map_err(|e| format!("Database backend error: {:?}", e))
}
}
@@ -843,11 +856,15 @@ impl<Block: BlockT> Backend<Block> {
let map_e = |e: sc_state_db::Error<io::Error>| sp_blockchain::Error::from(
format!("State database error: {:?}", e)
);
let state_db: StateDb<_, _> = StateDb::new(config.pruning.clone(), &StateMetaDb(&*db))
.map_err(map_e)?;
let state_db: StateDb<_, _> = StateDb::new(
config.pruning.clone(),
!config.source.supports_ref_counting(),
&StateMetaDb(&*db),
).map_err(map_e)?;
let storage_db = StorageDb {
db: db.clone(),
state_db,
prefix_keys: !config.source.supports_ref_counting(),
};
let offchain_storage = offchain::LocalStorage::new(db.clone());
let changes_tries_storage = DbChangesTrieStorage::new(
@@ -1112,17 +1129,32 @@ impl<Block: BlockT> Backend<Block> {
let mut bytes: u64 = 0;
let mut removal: u64 = 0;
let mut bytes_removal: u64 = 0;
for (key, (val, rc)) in operation.db_updates.drain() {
for (mut key, (val, rc)) in operation.db_updates.drain() {
if !self.storage.prefix_keys {
// Strip prefix
key.drain(0 .. key.len() - DB_HASH_LEN);
};
if rc > 0 {
ops += 1;
bytes += key.len() as u64 + val.len() as u64;
changeset.inserted.push((key, val.to_vec()));
if rc == 1 {
changeset.inserted.push((key, val.to_vec()));
} else {
changeset.inserted.push((key.clone(), val.to_vec()));
for _ in 0 .. rc - 1 {
changeset.inserted.push((key.clone(), Default::default()));
}
}
} else if rc < 0 {
removal += 1;
bytes_removal += key.len() as u64;
changeset.deleted.push(key);
if rc == -1 {
changeset.deleted.push(key);
} else {
for _ in 0 .. -rc {
changeset.deleted.push(key.clone());
}
}
}
}
self.state_usage.tally_writes_nodes(ops, bytes);
+9 -2
View File
@@ -17,6 +17,8 @@
/// A `Database` adapter for parity-db.
use sp_database::{Database, Change, Transaction, ColumnId};
use crate::utils::NUM_COLUMNS;
use crate::columns;
struct DbAdapter(parity_db::Db);
@@ -30,8 +32,13 @@ fn handle_err<T>(result: parity_db::Result<T>) -> T {
}
/// Wrap RocksDb database into a trait object that implements `sp_database::Database`
pub fn open<H: Clone>(path: &std::path::Path, num_columns: u32) -> parity_db::Result<std::sync::Arc<dyn Database<H>>> {
let db = parity_db::Db::with_columns(path, num_columns as u8)?;
pub fn open<H: Clone>(path: &std::path::Path) -> parity_db::Result<std::sync::Arc<dyn Database<H>>> {
let mut config = parity_db::Options::with_columns(path, NUM_COLUMNS as u8);
let mut state_col = &mut config.columns[columns::STATE as usize];
state_col.ref_counted = true;
state_col.preimage = true;
state_col.uniform = true;
let db = parity_db::Db::open(&config)?;
Ok(std::sync::Arc::new(DbAdapter(db)))
}
+1 -1
View File
@@ -254,7 +254,7 @@ pub fn open_database<Block: BlockT>(
},
#[cfg(feature = "parity-db")]
DatabaseSettingsSrc::ParityDb { path } => {
crate::parity_db::open(&path, NUM_COLUMNS)
crate::parity_db::open(&path)
.map_err(|e| sp_blockchain::Error::Backend(format!("{:?}", e)))?
},
DatabaseSettingsSrc::Custom(db) => db.clone(),
+18 -10
View File
@@ -201,9 +201,10 @@ struct StateDbSync<BlockHash: Hash, Key: Hash> {
impl<BlockHash: Hash + MallocSizeOf, Key: Hash + MallocSizeOf> StateDbSync<BlockHash, Key> {
fn new<D: MetaDb>(
mode: PruningMode,
ref_counting: bool,
db: &D,
) -> Result<StateDbSync<BlockHash, Key>, Error<D::Error>> {
trace!(target: "state-db", "StateDb settings: {:?}", mode);
trace!(target: "state-db", "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting);
// Check that settings match
Self::check_meta(&mode, db)?;
@@ -214,7 +215,7 @@ impl<BlockHash: Hash + MallocSizeOf, Key: Hash + MallocSizeOf> StateDbSync<Block
max_mem: Some(_),
..
}) => unimplemented!(),
PruningMode::Constrained(_) => Some(RefWindow::new(db)?),
PruningMode::Constrained(_) => Some(RefWindow::new(db, ref_counting)?),
PruningMode::ArchiveAll | PruningMode::ArchiveCanonical => None,
};
@@ -387,8 +388,11 @@ impl<BlockHash: Hash + MallocSizeOf, Key: Hash + MallocSizeOf> StateDbSync<Block
}
}
pub fn get<D: NodeDb>(&self, key: &Key, db: &D) -> Result<Option<DBValue>, Error<D::Error>>
where Key: AsRef<D::Key>
pub fn get<D: NodeDb, Q: ?Sized>(&self, key: &Q, db: &D) -> Result<Option<DBValue>, Error<D::Error>>
where
Q: AsRef<D::Key>,
Key: std::borrow::Borrow<Q>,
Q: std::hash::Hash + Eq,
{
if let Some(value) = self.non_canonical.get(key) {
return Ok(Some(value));
@@ -438,10 +442,11 @@ impl<BlockHash: Hash + MallocSizeOf, Key: Hash + MallocSizeOf> StateDb<BlockHash
/// Creates a new instance. Does not expect any metadata in the database.
pub fn new<D: MetaDb>(
mode: PruningMode,
ref_counting: bool,
db: &D,
) -> Result<StateDb<BlockHash, Key>, Error<D::Error>> {
Ok(StateDb {
db: RwLock::new(StateDbSync::new(mode, db)?)
db: RwLock::new(StateDbSync::new(mode, ref_counting, db)?)
})
}
@@ -475,8 +480,11 @@ impl<BlockHash: Hash + MallocSizeOf, Key: Hash + MallocSizeOf> StateDb<BlockHash
}
/// Get a value from non-canonical/pruning overlay or the backing DB.
pub fn get<D: NodeDb>(&self, key: &Key, db: &D) -> Result<Option<DBValue>, Error<D::Error>>
where Key: AsRef<D::Key>
pub fn get<D: NodeDb, Q: ?Sized>(&self, key: &Q, db: &D) -> Result<Option<DBValue>, Error<D::Error>>
where
Q: AsRef<D::Key>,
Key: std::borrow::Borrow<Q>,
Q: std::hash::Hash + Eq,
{
self.db.read().get(key, db)
}
@@ -523,7 +531,7 @@ mod tests {
fn make_test_db(settings: PruningMode) -> (TestDb, StateDb<H256, H256>) {
let mut db = make_db(&[91, 921, 922, 93, 94]);
let state_db = StateDb::new(settings, &db).unwrap();
let state_db = StateDb::new(settings, false, &db).unwrap();
db.commit(
&state_db
@@ -638,7 +646,7 @@ mod tests {
#[test]
fn detects_incompatible_mode() {
let mut db = make_db(&[]);
let state_db = StateDb::new(PruningMode::ArchiveAll, &db).unwrap();
let state_db = StateDb::new(PruningMode::ArchiveAll, false, &db).unwrap();
db.commit(
&state_db
.insert_block::<io::Error>(
@@ -650,7 +658,7 @@ mod tests {
.unwrap(),
);
let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2), max_mem: None });
let state_db: Result<StateDb<H256, H256>, _> = StateDb::new(new_mode, &db);
let state_db: Result<StateDb<H256, H256>, _> = StateDb::new(new_mode, false, &db);
assert!(state_db.is_err());
}
}
+76 -41
View File
@@ -40,7 +40,7 @@ pub struct NonCanonicalOverlay<BlockHash: Hash, Key: Hash> {
values: HashMap<Key, (u32, DBValue)>, //ref counted
//would be deleted but kept around because block is pinned, ref counted.
pinned: HashMap<BlockHash, u32>,
pinned_insertions: HashMap<BlockHash, Vec<Key>>,
pinned_insertions: HashMap<BlockHash, (Vec<Key>, u32)>,
}
#[derive(Encode, Decode)]
@@ -90,25 +90,44 @@ fn discard_values<Key: Hash>(values: &mut HashMap<Key, (u32, DBValue)>, inserted
}
fn discard_descendants<BlockHash: Hash, Key: Hash>(
levels: &mut VecDeque<Vec<BlockOverlay<BlockHash, Key>>>,
levels: &mut (&mut [Vec<BlockOverlay<BlockHash, Key>>], &mut [Vec<BlockOverlay<BlockHash, Key>>]),
mut values: &mut HashMap<Key, (u32, DBValue)>,
index: usize,
parents: &mut HashMap<BlockHash, BlockHash>,
pinned: &HashMap<BlockHash, u32>,
pinned_insertions: &mut HashMap<BlockHash, Vec<Key>>,
pinned_insertions: &mut HashMap<BlockHash, (Vec<Key>, u32)>,
hash: &BlockHash,
) {
let mut discarded = Vec::new();
if let Some(level) = levels.get_mut(index) {
) -> u32 {
let (first, mut remainder) = if let Some((first, rest)) = levels.0.split_first_mut() {
(Some(first), (rest, &mut levels.1[..]))
} else {
if let Some((first, rest)) = levels.1.split_first_mut() {
(Some(first), (&mut levels.0[..], rest))
} else {
(None, (&mut levels.0[..], &mut levels.1[..]))
}
};
let mut pinned_children = 0;
if let Some(level) = first {
*level = level.drain(..).filter_map(|overlay| {
let parent = parents.get(&overlay.hash)
.expect("there is a parent entry for each entry in levels; qed");
if parent == hash {
discarded.push(overlay.hash.clone());
let mut num_pinned = discard_descendants(
&mut remainder,
values,
parents,
pinned,
pinned_insertions,
&overlay.hash
);
if pinned.contains_key(&overlay.hash) {
num_pinned += 1;
}
if num_pinned != 0 {
// save to be discarded later.
pinned_insertions.insert(overlay.hash.clone(), overlay.inserted);
pinned_insertions.insert(overlay.hash.clone(), (overlay.inserted, num_pinned));
pinned_children += num_pinned;
} else {
// discard immediately.
parents.remove(&overlay.hash);
@@ -120,9 +139,7 @@ fn discard_descendants<BlockHash: Hash, Key: Hash>(
}
}).collect();
}
for hash in discarded {
discard_descendants(levels, values, index + 1, parents, pinned, pinned_insertions, &hash);
}
pinned_children
}
impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
@@ -346,19 +363,23 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
// discard unfinalized overlays and values
for (i, overlay) in level.into_iter().enumerate() {
if i != index {
let mut pinned_children = if i != index {
discard_descendants(
&mut self.levels,
&mut self.levels.as_mut_slices(),
&mut self.values,
0,
&mut self.parents,
&self.pinned,
&mut self.pinned_insertions,
&overlay.hash,
);
}
)
} else {
0
};
if self.pinned.contains_key(&overlay.hash) {
self.pinned_insertions.insert(overlay.hash.clone(), overlay.inserted);
pinned_children += 1;
}
if pinned_children != 0 {
self.pinned_insertions.insert(overlay.hash.clone(), (overlay.inserted, pinned_children));
} else {
self.parents.remove(&overlay.hash);
discard_values(&mut self.values, overlay.inserted);
@@ -372,7 +393,11 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
}
/// Get a value from the node overlay. This searches in every existing changeset.
pub fn get(&self, key: &Key) -> Option<DBValue> {
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<DBValue>
where
Key: std::borrow::Borrow<Q>,
Q: std::hash::Hash + Eq,
{
if let Some((_, value)) = self.values.get(&key) {
return Some(value.clone());
}
@@ -435,37 +460,47 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
debug_assert!(false, "Trying to pin pending state");
return;
}
// Also pin all parents
let mut parent = Some(hash);
while let Some(hash) = parent {
let refs = self.pinned.entry(hash.clone()).or_default();
if *refs == 0 {
trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash);
}
*refs += 1;
parent = self.parents.get(hash);
let refs = self.pinned.entry(hash.clone()).or_default();
if *refs == 0 {
trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash);
}
*refs += 1;
}
/// Discard pinned state
pub fn unpin(&mut self, hash: &BlockHash) {
// Also unpin all parents
let mut parent = Some(hash.clone());
while let Some(hash) = parent {
parent = self.parents.get(&hash).cloned();
match self.pinned.entry(hash.clone()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() -= 1;
if *entry.get() == 0 {
entry.remove();
if let Some(inserted) = self.pinned_insertions.remove(&hash) {
let removed = match self.pinned.entry(hash.clone()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() -= 1;
if *entry.get() == 0 {
entry.remove();
true
} else {
false
}
},
Entry::Vacant(_) => false,
};
if removed {
let mut parent = Some(hash.clone());
while let Some(hash) = parent {
parent = self.parents.get(&hash).cloned();
match self.pinned_insertions.entry(hash.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().1 -= 1;
if entry.get().1 == 0 {
let (inserted, _) = entry.remove();
trace!(target: "state-db-pin", "Discarding unpinned non-canon block: {:?}", hash);
discard_values(&mut self.values, inserted);
self.parents.remove(&hash);
true
} else {
false
}
}
},
Entry::Vacant(_) => {},
},
Entry::Vacant(_) => break,
};
}
}
}
+64 -23
View File
@@ -45,6 +45,10 @@ pub struct RefWindow<BlockHash: Hash, Key: Hash> {
/// Number of calls of `prune_one` after
/// last call `apply_pending` or `revert_pending`
pending_prunings: usize,
/// Keep track of re-inserted keys and do not delete them when pruning.
/// Setting this to false requires backend that supports reference
/// counting.
count_insertions: bool,
}
#[derive(Debug, PartialEq, Eq, parity_util_mem_derive::MallocSizeOf)]
@@ -66,7 +70,7 @@ fn to_journal_key(block: u64) -> Vec<u8> {
}
impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
pub fn new<D: MetaDb>(db: &D) -> Result<RefWindow<BlockHash, Key>, Error<D::Error>> {
pub fn new<D: MetaDb>(db: &D, count_insertions: bool) -> Result<RefWindow<BlockHash, Key>, Error<D::Error>> {
let last_pruned = db.get_meta(&to_meta_key(LAST_PRUNED, &()))
.map_err(|e| Error::Db(e))?;
let pending_number: u64 = match last_pruned {
@@ -80,6 +84,7 @@ impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
pending_number: pending_number,
pending_canonicalizations: 0,
pending_prunings: 0,
count_insertions,
};
// read the journal
trace!(target: "state-db", "Reading pruning journal. Pending #{}", pending_number);
@@ -99,17 +104,19 @@ impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
}
fn import<I: IntoIterator<Item=Key>>(&mut self, hash: &BlockHash, journal_key: Vec<u8>, inserted: I, deleted: Vec<Key>) {
// remove all re-inserted keys from death rows
for k in inserted {
if let Some(block) = self.death_index.remove(&k) {
self.death_rows[(block - self.pending_number) as usize].deleted.remove(&k);
if self.count_insertions {
// remove all re-inserted keys from death rows
for k in inserted {
if let Some(block) = self.death_index.remove(&k) {
self.death_rows[(block - self.pending_number) as usize].deleted.remove(&k);
}
}
}
// add new keys
let imported_block = self.pending_number + self.death_rows.len() as u64;
for k in deleted.iter() {
self.death_index.insert(k.clone(), imported_block);
// add new keys
let imported_block = self.pending_number + self.death_rows.len() as u64;
for k in deleted.iter() {
self.death_index.insert(k.clone(), imported_block);
}
}
self.death_rows.push_back(
DeathRow {
@@ -157,7 +164,11 @@ impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
/// Add a change set to the window. Creates a journal record and pushes it to `commit`
pub fn note_canonical(&mut self, hash: &BlockHash, commit: &mut CommitSet<Key>) {
trace!(target: "state-db", "Adding to pruning window: {:?} ({} inserted, {} deleted)", hash, commit.data.inserted.len(), commit.data.deleted.len());
let inserted = commit.data.inserted.iter().map(|(k, _)| k.clone()).collect();
let inserted = if self.count_insertions {
commit.data.inserted.iter().map(|(k, _)| k.clone()).collect()
} else {
Default::default()
};
let deleted = ::std::mem::replace(&mut commit.data.deleted, Vec::new());
let journal_record = JournalRecord {
hash: hash.clone(),
@@ -177,8 +188,10 @@ impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
for _ in 0 .. self.pending_prunings {
let pruned = self.death_rows.pop_front().expect("pending_prunings is always < death_rows.len()");
trace!(target: "state-db", "Applying pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len());
for k in pruned.deleted.iter() {
self.death_index.remove(&k);
if self.count_insertions {
for k in pruned.deleted.iter() {
self.death_index.remove(&k);
}
}
self.pending_number += 1;
}
@@ -192,8 +205,10 @@ impl<BlockHash: Hash, Key: Hash> RefWindow<BlockHash, Key> {
// We don't bother to track and revert that for now. This means that a few nodes might end up no being
// deleted in case transaction fails and `revert_pending` is called.
self.death_rows.truncate(self.death_rows.len() - self.pending_canonicalizations);
let new_max_block = self.death_rows.len() as u64 + self.pending_number;
self.death_index.retain(|_, block| *block < new_max_block);
if self.count_insertions {
let new_max_block = self.death_rows.len() as u64 + self.pending_number;
self.death_index.retain(|_, block| *block < new_max_block);
}
self.pending_canonicalizations = 0;
self.pending_prunings = 0;
}
@@ -207,7 +222,7 @@ mod tests {
use crate::test::{make_db, make_commit, TestDb};
fn check_journal(pruning: &RefWindow<H256, H256>, db: &TestDb) {
let restored: RefWindow<H256, H256> = RefWindow::new(db).unwrap();
let restored: RefWindow<H256, H256> = RefWindow::new(db, pruning.count_insertions).unwrap();
assert_eq!(pruning.pending_number, restored.pending_number);
assert_eq!(pruning.death_rows, restored.death_rows);
assert_eq!(pruning.death_index, restored.death_index);
@@ -216,7 +231,7 @@ mod tests {
#[test]
fn created_from_empty_db() {
let db = make_db(&[]);
let pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
assert_eq!(pruning.pending_number, 0);
assert!(pruning.death_rows.is_empty());
assert!(pruning.death_index.is_empty());
@@ -225,7 +240,7 @@ mod tests {
#[test]
fn prune_empty() {
let db = make_db(&[]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = CommitSet::default();
pruning.prune_one(&mut commit);
assert_eq!(pruning.pending_number, 0);
@@ -238,7 +253,7 @@ mod tests {
#[test]
fn prune_one() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = make_commit(&[4, 5], &[1, 3]);
let h = H256::random();
pruning.note_canonical(&h, &mut commit);
@@ -267,7 +282,7 @@ mod tests {
#[test]
fn prune_two() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = make_commit(&[4], &[1]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
@@ -295,7 +310,7 @@ mod tests {
#[test]
fn prune_two_pending() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = make_commit(&[4], &[1]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
@@ -318,7 +333,7 @@ mod tests {
#[test]
fn reinserted_survives() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = make_commit(&[], &[2]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
@@ -351,7 +366,7 @@ mod tests {
#[test]
fn reinserted_survive_pending() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db).unwrap();
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, true).unwrap();
let mut commit = make_commit(&[], &[2]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
@@ -377,4 +392,30 @@ mod tests {
pruning.apply_pending();
assert_eq!(pruning.pending_number, 3);
}
#[test]
fn reinserted_ignores() {
let mut db = make_db(&[1, 2, 3]);
let mut pruning: RefWindow<H256, H256> = RefWindow::new(&db, false).unwrap();
let mut commit = make_commit(&[], &[2]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
let mut commit = make_commit(&[2], &[]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
let mut commit = make_commit(&[], &[2]);
pruning.note_canonical(&H256::random(), &mut commit);
db.commit(&commit);
assert!(db.data_eq(&make_db(&[1, 2, 3])));
pruning.apply_pending();
check_journal(&pruning, &db);
let mut commit = CommitSet::default();
pruning.prune_one(&mut commit);
db.commit(&commit);
assert!(db.data_eq(&make_db(&[1, 3])));
assert!(pruning.death_index.is_empty());
}
}
@@ -129,11 +129,8 @@ impl<S: TrieBackendStorage<H>, H: Hasher> Backend<H> for TrieBackend<S, H> where
}
fn pairs(&self) -> Vec<(StorageKey, StorageValue)> {
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral::new(self.essence.backend_storage(), &mut read_overlay);
let collect_all = || -> Result<_, Box<TrieError<H::Out>>> {
let trie = TrieDB::<H>::new(&eph, self.essence.root())?;
let trie = TrieDB::<H>::new(self.essence(), self.essence.root())?;
let mut v = Vec::new();
for x in trie.iter()? {
let (key, value) = x?;
@@ -153,11 +150,8 @@ impl<S: TrieBackendStorage<H>, H: Hasher> Backend<H> for TrieBackend<S, H> where
}
fn keys(&self, prefix: &[u8]) -> Vec<StorageKey> {
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral::new(self.essence.backend_storage(), &mut read_overlay);
let collect_all = || -> Result<_, Box<TrieError<H::Out>>> {
let trie = TrieDB::<H>::new(&eph, self.essence.root())?;
let trie = TrieDB::<H>::new(self.essence(), self.essence.root())?;
let mut v = Vec::new();
for x in trie.iter()? {
let (key, _) = x?;
@@ -20,7 +20,7 @@
use std::ops::Deref;
use std::sync::Arc;
use log::{debug, warn};
use hash_db::{self, Hasher, EMPTY_PREFIX, Prefix};
use hash_db::{self, Hasher, Prefix};
use sp_trie::{Trie, MemoryDB, PrefixedMemoryDB, DBValue,
empty_child_trie_root, read_trie_value, read_child_trie_value,
for_keys_in_child_trie, KeySpacedDB, TrieDBIterator};
@@ -39,6 +39,7 @@ pub trait Storage<H: Hasher>: Send + Sync {
pub struct TrieBackendEssence<S: TrieBackendStorage<H>, H: Hasher> {
storage: S,
root: H::Out,
empty: H::Out,
}
impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out: Encode {
@@ -47,6 +48,7 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
TrieBackendEssence {
storage,
root,
empty: H::hash(&[0u8]),
}
}
@@ -116,18 +118,13 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
child_info: Option<&ChildInfo>,
key: &[u8],
) -> Result<Option<StorageKey>, String> {
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let dyn_eph: &dyn hash_db::HashDBRef<_, _>;
let keyspace_eph;
if let Some(child_info) = child_info.as_ref() {
keyspace_eph = KeySpacedDB::new(&eph, child_info.keyspace());
keyspace_eph = KeySpacedDB::new(self, child_info.keyspace());
dyn_eph = &keyspace_eph;
} else {
dyn_eph = &eph;
dyn_eph = self;
}
let trie = TrieDB::<H>::new(dyn_eph, root)
@@ -161,15 +158,9 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
/// Get the value of storage at given key.
pub fn storage(&self, key: &[u8]) -> Result<Option<StorageValue>, String> {
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let map_e = |e| format!("Trie lookup error: {}", e);
read_trie_value::<Layout<H>, _>(&eph, &self.root, key).map_err(map_e)
read_trie_value::<Layout<H>, _>(self, &self.root, key).map_err(map_e)
}
/// Get the value of child storage at given key.
@@ -181,15 +172,9 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
let root = self.child_root(child_info)?
.unwrap_or(empty_child_trie_root::<Layout<H>>().encode());
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let map_e = |e| format!("Trie lookup error: {}", e);
read_child_trie_value::<Layout<H>, _>(child_info.keyspace(), &eph, &root, key)
read_child_trie_value::<Layout<H>, _>(child_info.keyspace(), self, &root, key)
.map_err(map_e)
}
@@ -207,15 +192,9 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
}
};
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
if let Err(e) = for_keys_in_child_trie::<Layout<H>, _, Ephemeral<S, H>>(
if let Err(e) = for_keys_in_child_trie::<Layout<H>, _, _>(
child_info.keyspace(),
&eph,
self,
&root,
f,
) {
@@ -254,12 +233,6 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
mut f: F,
child_info: Option<&ChildInfo>,
) {
let mut read_overlay = S::Overlay::default();
let eph = Ephemeral {
storage: &self.storage,
overlay: &mut read_overlay,
};
let mut iter = move |db| -> Result<(), Box<TrieError<H::Out>>> {
let trie = TrieDB::<H>::new(db, root)?;
@@ -275,10 +248,10 @@ impl<S: TrieBackendStorage<H>, H: Hasher> TrieBackendEssence<S, H> where H::Out:
};
let result = if let Some(child_info) = child_info {
let db = KeySpacedDB::new(&eph, child_info.keyspace());
let db = KeySpacedDB::new(self, child_info.keyspace());
iter(&db)
} else {
iter(&eph)
iter(self)
};
if let Err(e) = result {
debug!(target: "trie", "Error while iterating by prefix: {}", e);
@@ -296,15 +269,6 @@ pub(crate) struct Ephemeral<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> {
overlay: &'a mut S::Overlay,
}
impl<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> hash_db::AsPlainDB<H::Out, DBValue>
for Ephemeral<'a, S, H>
{
fn as_plain_db<'b>(&'b self) -> &'b (dyn hash_db::PlainDB<H::Out, DBValue> + 'b) { self }
fn as_plain_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::PlainDB<H::Out, DBValue> + 'b) {
self
}
}
impl<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> hash_db::AsHashDB<H, DBValue>
for Ephemeral<'a, S, H>
{
@@ -321,43 +285,6 @@ impl<'a, S: TrieBackendStorage<H>, H: Hasher> Ephemeral<'a, S, H> {
}
}
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> hash_db::PlainDB<H::Out, DBValue>
for Ephemeral<'a, S, H>
{
fn get(&self, key: &H::Out) -> Option<DBValue> {
if let Some(val) = hash_db::HashDB::get(self.overlay, key, EMPTY_PREFIX) {
Some(val)
} else {
match self.storage.get(&key, EMPTY_PREFIX) {
Ok(x) => x,
Err(e) => {
warn!(target: "trie", "Failed to read from DB: {}", e);
None
},
}
}
}
fn contains(&self, key: &H::Out) -> bool {
hash_db::HashDB::get(self, key, EMPTY_PREFIX).is_some()
}
fn emplace(&mut self, key: H::Out, value: DBValue) {
hash_db::HashDB::emplace(self.overlay, key, EMPTY_PREFIX, value)
}
fn remove(&mut self, key: &H::Out) {
hash_db::HashDB::remove(self.overlay, key, EMPTY_PREFIX)
}
}
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> hash_db::PlainDBRef<H::Out, DBValue>
for Ephemeral<'a, S, H>
{
fn get(&self, key: &H::Out) -> Option<DBValue> { hash_db::PlainDB::get(self, key) }
fn contains(&self, key: &H::Out) -> bool { hash_db::PlainDB::contains(self, key) }
}
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> hash_db::HashDB<H, DBValue>
for Ephemeral<'a, S, H>
{
@@ -438,6 +365,59 @@ impl<H: Hasher> TrieBackendStorage<H> for MemoryDB<H> {
}
}
impl<S: TrieBackendStorage<H>, H: Hasher> hash_db::AsHashDB<H, DBValue>
for TrieBackendEssence<S, H>
{
fn as_hash_db<'b>(&'b self) -> &'b (dyn hash_db::HashDB<H, DBValue> + 'b) { self }
fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::HashDB<H, DBValue> + 'b) { self }
}
impl<S: TrieBackendStorage<H>, H: Hasher> hash_db::HashDB<H, DBValue>
for TrieBackendEssence<S, H>
{
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
if *key == self.empty {
return Some([0u8].to_vec())
}
match self.storage.get(&key, prefix) {
Ok(x) => x,
Err(e) => {
warn!(target: "trie", "Failed to read from DB: {}", e);
None
},
}
}
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
hash_db::HashDB::get(self, key, prefix).is_some()
}
fn insert(&mut self, _prefix: Prefix, _value: &[u8]) -> H::Out {
unimplemented!();
}
fn emplace(&mut self, _key: H::Out, _prefix: Prefix, _value: DBValue) {
unimplemented!();
}
fn remove(&mut self, _key: &H::Out, _prefix: Prefix) {
unimplemented!();
}
}
impl<S: TrieBackendStorage<H>, H: Hasher> hash_db::HashDBRef<H, DBValue>
for TrieBackendEssence<S, H>
{
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
hash_db::HashDB::get(self, key, prefix)
}
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
hash_db::HashDB::contains(self, key, prefix)
}
}
#[cfg(test)]
mod test {
use sp_core::{Blake2Hasher, H256};
-6
View File
@@ -86,8 +86,6 @@ pub trait AsHashDB<H: Hasher>: hash_db::AsHashDB<H, trie_db::DBValue> {}
impl<H: Hasher, T: hash_db::AsHashDB<H, trie_db::DBValue>> AsHashDB<H> for T {}
/// Reexport from `hash_db`, with genericity set for `Hasher` trait.
pub type HashDB<'a, H> = dyn hash_db::HashDB<H, trie_db::DBValue> + 'a;
/// Reexport from `hash_db`, with genericity set for key only.
pub type PlainDB<'a, K> = dyn hash_db::PlainDB<K, trie_db::DBValue> + 'a;
/// Reexport from `hash_db`, with genericity set for `Hasher` trait.
/// This uses a `KeyFunction` for prefixing keys internally (avoiding
/// key conflict for non random keys).
@@ -244,7 +242,6 @@ pub fn child_delta_trie_root<L: TrieConfiguration, I, A, B, DB, RD>(
B: AsRef<[u8]>,
RD: AsRef<[u8]>,
DB: hash_db::HashDB<L::Hash, trie_db::DBValue>
+ hash_db::PlainDB<TrieHash<L>, trie_db::DBValue>,
{
let mut root = TrieHash::<L>::default();
// root is fetched from DB, not writable by runtime, so it's always valid.
@@ -274,7 +271,6 @@ pub fn for_keys_in_child_trie<L: TrieConfiguration, F: FnMut(&[u8]), DB>(
) -> Result<(), Box<TrieError<L>>>
where
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>
+ hash_db::PlainDBRef<TrieHash<L>, trie_db::DBValue>,
{
let mut root = TrieHash::<L>::default();
// root is fetched from DB, not writable by runtime, so it's always valid.
@@ -324,7 +320,6 @@ pub fn read_child_trie_value<L: TrieConfiguration, DB>(
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>>
where
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>
+ hash_db::PlainDBRef<TrieHash<L>, trie_db::DBValue>,
{
let mut root = TrieHash::<L>::default();
// root is fetched from DB, not writable by runtime, so it's always valid.
@@ -344,7 +339,6 @@ pub fn read_child_trie_value_with<L: TrieConfiguration, Q: Query<L::Hash, Item=D
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>>
where
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>
+ hash_db::PlainDBRef<TrieHash<L>, trie_db::DBValue>,
{
let mut root = TrieHash::<L>::default();
// root is fetched from DB, not writable by runtime, so it's always valid.