From d60303d437cbad0e545b2f6d5f279739d54caddb Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 16 May 2018 19:27:11 +0200 Subject: [PATCH 1/2] less eager deletion of DB values --- polkadot/consensus/src/service.rs | 1 - substrate/client/db/src/lib.rs | 104 +++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index ce2b562a475cd..97384040e9c86 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -198,7 +198,6 @@ fn start_bft( } }; - let input = Messages { network_stream: network.bft_messages(parent_hash), local_id: bft_service.local_id(), diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 9d2b875701404..a9e2068abfe45 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -491,7 +491,7 @@ impl client::backend::Backend for Backend { for (key, (val, rc)) in operation.pending_state.updates.drain() { if rc > 0 { transaction.put(columns::STATE, &key.0[..], &val); - } else { + } else if rc < 0 { transaction.delete(columns::STATE, &key.0[..]); } } @@ -654,4 +654,106 @@ mod tests { assert_eq!(state.storage(&[5, 5, 5]).unwrap(), Some(vec![4, 5, 6])); } } + + #[test] + fn delete_only_when_negative_rc() { + let key; + let db = Backend::new_test(); + + { + let mut op = db.begin_operation(BlockId::Hash(Default::default())).unwrap(); + let mut header = block::Header { + number: 0, + parent_hash: Default::default(), + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage: Vec<(_, _)> = vec![]; + + header.state_root = op.pending_state.storage_root(storage + .iter() + .cloned() + .map(|(x, y)| (x, Some(y))) + ).0.into(); + + op.reset_storage(storage.iter().cloned()).unwrap(); + + key = op.pending_state.updates.insert(b"hello"); + op.set_block_data( + header, + Some(vec![]), + None, + true + ).unwrap(); + + db.commit_operation(op).unwrap(); + + assert_eq!(db.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]); + } + + { + let mut op = db.begin_operation(BlockId::Number(0)).unwrap(); + let mut header = block::Header { + number: 1, + parent_hash: Default::default(), + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage: Vec<(_, _)> = vec![]; + + header.state_root = op.pending_state.storage_root(storage + .iter() + .cloned() + .map(|(x, y)| (x, Some(y))) + ).0.into(); + + op.pending_state.updates.insert(b"hello"); + op.pending_state.updates.remove(&key); + op.set_block_data( + header, + Some(vec![]), + None, + true + ).unwrap(); + + db.commit_operation(op).unwrap(); + + assert_eq!(db.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]); + } + + { + let mut op = db.begin_operation(BlockId::Number(1)).unwrap(); + let mut header = block::Header { + number: 1, + parent_hash: Default::default(), + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let storage: Vec<(_, _)> = vec![]; + + header.state_root = op.pending_state.storage_root(storage + .iter() + .cloned() + .map(|(x, y)| (x, Some(y))) + ).0.into(); + + op.pending_state.updates.remove(&key); + op.set_block_data( + header, + Some(vec![]), + None, + true + ).unwrap(); + + db.commit_operation(op).unwrap(); + + assert!(db.db.get(::columns::STATE, &key.0[..]).unwrap().is_none()); + } + } } From c987290610cb6efec949887b93608eec20995167 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 17 May 2018 12:17:56 +0200 Subject: [PATCH 2/2] archive mode --- substrate/client/db/src/lib.rs | 65 +++++++++++++------------- substrate/client/src/backend.rs | 9 +++- substrate/client/src/in_mem.rs | 36 +++++++------- substrate/state-machine/src/backend.rs | 59 ++++++++++++++++------- substrate/state-machine/src/lib.rs | 9 ++-- 5 files changed, 107 insertions(+), 71 deletions(-) diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index a9e2068abfe45..5a21021862944 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -96,12 +96,6 @@ struct PendingBlock { is_best: bool, } -/// Database transaction -pub struct BlockImportOperation { - pending_state: DbState, - pending_block: Option, -} - #[derive(Clone)] struct Meta { best_hash: HeaderHash, @@ -261,11 +255,18 @@ impl client::blockchain::Backend for BlockchainDb { } } +/// Database transaction +pub struct BlockImportOperation { + old_state: DbState, + updates: MemoryDB, + pending_block: Option, +} + impl client::backend::BlockImportOperation for BlockImportOperation { type State = DbState; fn state(&self) -> Result<&Self::State, client::error::Error> { - Ok(&self.pending_state) + Ok(&self.old_state) } fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_best: bool) -> Result<(), client::error::Error> { @@ -280,14 +281,14 @@ impl client::backend::BlockImportOperation for BlockImportOperation { } fn update_storage(&mut self, update: MemoryDB) -> Result<(), client::error::Error> { - self.pending_state.commit(update); + self.updates = update; Ok(()) } fn reset_storage, Vec)>>(&mut self, iter: I) -> Result<(), client::error::Error> { // TODO: wipe out existing trie. - let (_, update) = self.pending_state.storage_root(iter.into_iter().map(|(k, v)| (k, Some(v)))); - self.pending_state.commit(update); + let (_, update) = self.old_state.storage_root(iter.into_iter().map(|(k, v)| (k, Some(v)))); + self.updates = update; Ok(()) } } @@ -341,10 +342,10 @@ impl<'a> HashDB for Ephemeral<'a> { } /// DB-backed patricia trie state, transaction type is an overlay of changes to commit. +#[derive(Clone)] pub struct DbState { db: Arc, root: TrieH256, - updates: MemoryDB, } impl state_machine::Backend for DbState { @@ -364,10 +365,6 @@ impl state_machine::Backend for DbState { .get(key).map(|x| x.map(|val| val.to_vec())).map_err(map_e) } - fn commit(&mut self, transaction: MemoryDB) { - self.updates = transaction; - } - fn pairs(&self) -> Vec<(Vec, Vec)> { let mut read_overlay = MemoryDB::default(); let eph = Ephemeral { @@ -423,10 +420,12 @@ impl state_machine::Backend for DbState { } } -/// In-memory backend. Keeps all states and blocks in memory. Useful for testing. +/// Disk backend. Keeps data in a key-value store. In archive mode, trie nodes are kept from all blocks. +/// Otherwise, trie nodes are kept only from the most recent block. pub struct Backend { db: Arc, blockchain: BlockchainDb, + archive: bool, } impl Backend { @@ -438,22 +437,23 @@ impl Backend { let path = config.path.to_str().ok_or_else(|| client::error::ErrorKind::Backend("Invalid database path".into()))?; let db = Arc::new(Database::open(&db_config, &path).map_err(db_err)?); - Backend::from_kvdb(db as Arc<_>) + Backend::from_kvdb(db as Arc<_>, true) } #[cfg(test)] fn new_test() -> Backend { let db = Arc::new(::kvdb_memorydb::create(columns::NUM_COLUMNS)); - Backend::from_kvdb(db as Arc<_>).expect("failed to create test-db") + Backend::from_kvdb(db as Arc<_>, false).expect("failed to create test-db") } - fn from_kvdb(db: Arc) -> Result { + fn from_kvdb(db: Arc, archive: bool) -> Result { let blockchain = BlockchainDb::new(db.clone())?; Ok(Backend { db, blockchain, + archive }) } } @@ -467,7 +467,8 @@ impl client::backend::Backend for Backend { let state = self.state_at(block)?; Ok(BlockImportOperation { pending_block: None, - pending_state: state, + old_state: state, + updates: MemoryDB::default(), }) } @@ -488,10 +489,10 @@ impl client::backend::Backend for Backend { if pending_block.is_best { transaction.put(columns::META, meta::BEST_BLOCK, &key); } - for (key, (val, rc)) in operation.pending_state.updates.drain() { + for (key, (val, rc)) in operation.updates.drain() { if rc > 0 { transaction.put(columns::STATE, &key.0[..], &val); - } else if rc < 0 { + } else if rc < 0 && !self.archive { transaction.delete(columns::STATE, &key.0[..]); } } @@ -518,7 +519,6 @@ impl client::backend::Backend for Backend { return Ok(DbState { db: self.db.clone(), - updates: Default::default(), root, }) } @@ -528,7 +528,6 @@ impl client::backend::Backend for Backend { self.blockchain.header(block).and_then(|maybe_hdr| maybe_hdr.map(|hdr| { DbState { db: self.db.clone(), - updates: Default::default(), root: hdr.state_root.0.into(), } }).ok_or_else(|| client::error::ErrorKind::UnknownBlock(block).into())) @@ -595,7 +594,7 @@ mod tests { (vec![1, 2, 3], vec![9, 9, 9]), ]; - header.state_root = op.pending_state.storage_root(storage + header.state_root = op.old_state.storage_root(storage .iter() .cloned() .map(|(x, y)| (x, Some(y))) @@ -634,7 +633,7 @@ mod tests { (vec![5, 5, 5], Some(vec![4, 5, 6])), ]; - let (root, overlay) = op.pending_state.storage_root(storage.iter().cloned()); + let (root, overlay) = op.old_state.storage_root(storage.iter().cloned()); op.update_storage(overlay).unwrap(); header.state_root = root.into(); @@ -672,7 +671,7 @@ mod tests { let storage: Vec<(_, _)> = vec![]; - header.state_root = op.pending_state.storage_root(storage + header.state_root = op.old_state.storage_root(storage .iter() .cloned() .map(|(x, y)| (x, Some(y))) @@ -680,7 +679,7 @@ mod tests { op.reset_storage(storage.iter().cloned()).unwrap(); - key = op.pending_state.updates.insert(b"hello"); + key = op.updates.insert(b"hello"); op.set_block_data( header, Some(vec![]), @@ -705,14 +704,14 @@ mod tests { let storage: Vec<(_, _)> = vec![]; - header.state_root = op.pending_state.storage_root(storage + header.state_root = op.old_state.storage_root(storage .iter() .cloned() .map(|(x, y)| (x, Some(y))) ).0.into(); - op.pending_state.updates.insert(b"hello"); - op.pending_state.updates.remove(&key); + op.updates.insert(b"hello"); + op.updates.remove(&key); op.set_block_data( header, Some(vec![]), @@ -737,13 +736,13 @@ mod tests { let storage: Vec<(_, _)> = vec![]; - header.state_root = op.pending_state.storage_root(storage + header.state_root = op.old_state.storage_root(storage .iter() .cloned() .map(|(x, y)| (x, Some(y))) ).0.into(); - op.pending_state.updates.remove(&key); + op.updates.remove(&key); op.set_block_data( header, Some(vec![]), diff --git a/substrate/client/src/backend.rs b/substrate/client/src/backend.rs index 1890668c26bae..dad95dc0fdbee 100644 --- a/substrate/client/src/backend.rs +++ b/substrate/client/src/backend.rs @@ -37,6 +37,13 @@ pub trait BlockImportOperation { } /// Client backend. Manages the data layer. +/// +/// Note on state pruning: while an object from `state_at` is alive, the state +/// should not be pruned. The backend should internally reference-count +/// its state objects. +/// +/// The same applies for live `BlockImportOperation`s: while an import operation building on a parent `P` +/// is alive, the state for `P` should not be pruned. pub trait Backend { /// Associated block insertion operation type. type BlockImportOperation: BlockImportOperation; @@ -52,6 +59,6 @@ pub trait Backend { fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>; /// Returns reference to blockchain backend. fn blockchain(&self) -> &Self::Blockchain; - /// Returns state backend for specified block. + /// Returns state backend with post-state of given block. fn state_at(&self, block: BlockId) -> error::Result; } diff --git a/substrate/client/src/in_mem.rs b/substrate/client/src/in_mem.rs index e3989b8ac4d8e..138b6f27a24f8 100644 --- a/substrate/client/src/in_mem.rs +++ b/substrate/client/src/in_mem.rs @@ -18,14 +18,13 @@ use std::collections::HashMap; use parking_lot::RwLock; -use state_machine; use error; use backend; use runtime_support::Hashable; use primitives; use primitives::block::{self, Id as BlockId, HeaderHash}; use blockchain::{self, BlockStatus}; -use state_machine::backend::Backend as StateBackend; +use state_machine::backend::{Backend as StateBackend, InMemory}; fn header_hash(header: &block::Header) -> block::HeaderHash { header.blake2_256().into() @@ -43,12 +42,6 @@ struct Block { body: Option, } -/// In-memory operation. -pub struct BlockImportOperation { - pending_block: Option, - pending_state: state_machine::backend::InMemory, -} - #[derive(Clone)] struct BlockchainStorage { blocks: HashMap, @@ -160,11 +153,18 @@ impl blockchain::Backend for Blockchain { } } +/// In-memory operation. +pub struct BlockImportOperation { + pending_block: Option, + old_state: InMemory, + new_state: Option, +} + impl backend::BlockImportOperation for BlockImportOperation { - type State = state_machine::backend::InMemory; + type State = InMemory; fn state(&self) -> error::Result<&Self::State> { - Ok(&self.pending_state) + Ok(&self.old_state) } fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_new_best: bool) -> error::Result<()> { @@ -180,20 +180,20 @@ impl backend::BlockImportOperation for BlockImportOperation { Ok(()) } - fn update_storage(&mut self, update: ::Transaction) -> error::Result<()> { - self.pending_state.commit(update); + fn update_storage(&mut self, update: ::Transaction) -> error::Result<()> { + self.new_state = Some(self.old_state.update(update)); Ok(()) } fn reset_storage, Vec)>>(&mut self, iter: I) -> error::Result<()> { - self.pending_state = state_machine::backend::InMemory::from(iter.collect()); + self.new_state = Some(InMemory::from(iter.collect::>())); Ok(()) } } /// In-memory backend. Keeps all states and blocks in memory. Useful for testing. pub struct Backend { - states: RwLock>, + states: RwLock>, blockchain: Blockchain, } @@ -210,7 +210,7 @@ impl Backend { impl backend::Backend for Backend { type BlockImportOperation = BlockImportOperation; type Blockchain = Blockchain; - type State = state_machine::backend::InMemory; + type State = InMemory; fn begin_operation(&self, block: BlockId) -> error::Result { let state = match block { @@ -220,14 +220,16 @@ impl backend::Backend for Backend { Ok(BlockImportOperation { pending_block: None, - pending_state: state, + old_state: state, + new_state: None, }) } fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> { if let Some(pending_block) = operation.pending_block { let hash = header_hash(&pending_block.block.header); - self.states.write().insert(hash, operation.pending_state); + let old_state = &operation.old_state; + self.states.write().insert(hash, operation.new_state.unwrap_or_else(|| old_state.clone())); self.blockchain.insert(hash, pending_block.block.header, pending_block.block.justification, pending_block.block.body, pending_block.is_best); } Ok(()) diff --git a/substrate/state-machine/src/backend.rs b/substrate/state-machine/src/backend.rs index 9c90a47be161a..da68b932feff3 100644 --- a/substrate/state-machine/src/backend.rs +++ b/substrate/state-machine/src/backend.rs @@ -18,10 +18,13 @@ use std::{error, fmt}; use std::collections::HashMap; +use std::sync::Arc; /// A state backend is used to read state data and can have changes committed /// to it. -pub trait Backend { +/// +/// The clone operation should be cheap. +pub trait Backend: Clone { /// An error type when fetching data is not possible. type Error: super::Error; @@ -36,9 +39,6 @@ pub trait Backend { fn storage_root(&self, delta: I) -> ([u8; 32], Self::Transaction) where I: IntoIterator, Option>)>; - /// Commit updates to the backend and get new state. - fn commit(&mut self, tx: Self::Transaction); - /// Get all key/value pairs into a Vec. fn pairs(&self) -> Vec<(Vec, Vec)>; } @@ -60,20 +60,54 @@ impl error::Error for Void { /// In-memory backend. Fully recomputes tries on each commit but useful for /// tests. -pub type InMemory = HashMap, Vec>; +#[derive(Clone, PartialEq, Eq)] +pub struct InMemory { + inner: Arc, Vec>>, +} + +impl Default for InMemory { + fn default() -> Self { + InMemory { + inner: Arc::new(Default::default()), + } + } +} + +impl InMemory { + /// Copy the state, with applied updates + pub fn update(&self, changes: ::Transaction) -> Self { + let mut inner: HashMap<_, _> = (&*self.inner).clone(); + for (key, val) in changes { + match val { + Some(v) => { inner.insert(key, v); }, + None => { inner.remove(&key); }, + } + } + + inner.into() + } +} + +impl From, Vec>> for InMemory { + fn from(inner: HashMap, Vec>) -> Self { + InMemory { + inner: Arc::new(inner), + } + } +} impl Backend for InMemory { type Error = Void; type Transaction = Vec<(Vec, Option>)>; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { - Ok(self.get(key).map(Clone::clone)) + Ok(self.inner.get(key).map(Clone::clone)) } fn storage_root(&self, delta: I) -> ([u8; 32], Self::Transaction) where I: IntoIterator, Option>)> { - let existing_pairs = self.iter().map(|(k, v)| (k.clone(), Some(v.clone()))); + let existing_pairs = self.inner.iter().map(|(k, v)| (k.clone(), Some(v.clone()))); let transaction: Vec<_> = delta.into_iter().collect(); let root = ::triehash::trie_root(existing_pairs.chain(transaction.iter().cloned()) @@ -85,17 +119,8 @@ impl Backend for InMemory { (root, transaction) } - fn commit(&mut self, changes: Self::Transaction) { - for (key, val) in changes { - match val { - Some(v) => { self.insert(key, v); }, - None => { self.remove(&key); }, - } - } - } - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.iter().map(|(k, v)| (k.clone(), v.clone())).collect() + self.inner.iter().map(|(k, v)| (k.clone(), v.clone())).collect() } } diff --git a/substrate/state-machine/src/lib.rs b/substrate/state-machine/src/lib.rs index 72fa063dbb554..c8aa4900980ed 100644 --- a/substrate/state-machine/src/lib.rs +++ b/substrate/state-machine/src/lib.rs @@ -145,11 +145,13 @@ pub trait CodeExecutor: Sized + Send + Sync { } /// Execute a call using the given state backend, overlayed changes, and call executor. +/// Produces a state-backend-specific "transaction" which can be used to apply the changes +/// to the backing store, such as the disk. /// /// On an error, no prospective changes are written to the overlay. /// /// Note: changes to code will be in place if this call is made again. For running partial -/// blocks (e.g. a transaction at a time), ensure a differrent method is used. +/// blocks (e.g. a transaction at a time), ensure a different method is used. pub fn execute( backend: &B, overlay: &mut OverlayedChanges, @@ -228,12 +230,13 @@ mod tests { #[test] fn overlayed_storage_root_works() { - let backend = InMemory::from(map![ + let initial: HashMap<_, _> = map![ b"doe".to_vec() => b"reindeer".to_vec(), b"dog".to_vec() => b"puppyXXX".to_vec(), b"dogglesworth".to_vec() => b"catXXX".to_vec(), b"doug".to_vec() => b"notadog".to_vec() - ]); + ]; + let backend = InMemory::from(initial); let mut overlay = OverlayedChanges { committed: map![ b"dog".to_vec() => Some(b"puppy".to_vec()),