Phase 1 of repo reorg (#719)

* Remove unneeded script

* Rename Substrate Demo -> Substrate

* Rename demo -> node

* Build wasm from last rename.

* Merge ed25519 into substrate-primitives

* Minor tweak

* Rename substrate -> core

* Move substrate-runtime-support to core/runtime/support

* Rename/move substrate-runtime-version

* Move codec up a level

* Rename substrate-codec -> parity-codec

* Move environmental up a level

* Move pwasm-* up to top, ready for removal

* Remove requirement of s-r-support from s-r-primitives

* Move core/runtime/primitives into core/runtime-primitives

* Remove s-r-support dep from s-r-version

* Remove dep of s-r-support from bft

* Remove dep of s-r-support from node/consensus

* Sever all other core deps from s-r-support

* Forgot the no_std directive

* Rename non-SRML modules to sr-* to avoid match clashes

* Move runtime/* to srml/*

* Rename substrate-runtime-* -> srml-*

* Move srml to top-level
This commit is contained in:
Gav Wood
2018-09-12 11:13:31 +02:00
committed by Arkadiy Paronyan
parent 8fe5aa4c81
commit 1e01162505
374 changed files with 2845 additions and 2902 deletions
+432
View File
@@ -0,0 +1,432 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! DB-backed cache of blockchain data.
use std::sync::Arc;
use parking_lot::RwLock;
use kvdb::{KeyValueDB, DBTransaction};
use client::blockchain::Cache as BlockchainCache;
use client::error::Result as ClientResult;
use codec::{Codec, Encode, Decode};
use primitives::AuthorityId;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, As, NumberFor};
use utils::{COLUMN_META, BlockKey, db_err, meta_keys, read_id, db_key_to_number, number_to_db_key};
/// Database-backed cache of blockchain data.
pub struct DbCache<Block: BlockT> {
db: Arc<KeyValueDB>,
block_index_column: Option<u32>,
authorities_at: DbCacheList<Block, Vec<AuthorityId>>,
}
impl<Block> DbCache<Block>
where
Block: BlockT,
NumberFor<Block>: As<u64>,
{
/// Create new cache.
pub fn new(
db: Arc<KeyValueDB>,
block_index_column: Option<u32>,
authorities_column: Option<u32>
) -> ClientResult<Self> {
Ok(DbCache {
db: db.clone(),
block_index_column,
authorities_at: DbCacheList::new(db, meta_keys::BEST_AUTHORITIES, authorities_column)?,
})
}
/// Get authorities_cache.
pub fn authorities_at_cache(&self) -> &DbCacheList<Block, Vec<AuthorityId>> {
&self.authorities_at
}
}
impl<Block> BlockchainCache<Block> for DbCache<Block>
where
Block: BlockT,
NumberFor<Block>: As<u64>,
{
fn authorities_at(&self, at: BlockId<Block>) -> Option<Vec<AuthorityId>> {
let authorities_at = read_id(&*self.db, self.block_index_column, at).and_then(|at| match at {
Some(at) => self.authorities_at.value_at_key(at),
None => Ok(None),
});
match authorities_at {
Ok(authorities) => authorities,
Err(error) => {
warn!("Trying to read authorities from db cache has failed with: {}", error);
None
},
}
}
}
/// Database-backed blockchain cache which holds its entries as a list.
/// The meta column holds the pointer to the best known cache entry and
/// every entry points to the previous entry.
/// New entry appears when the set of authorities changes in block, so the
/// best entry here means the entry that is valid for the best block (and
/// probably for its ascendants).
pub struct DbCacheList<Block: BlockT, T: Clone> {
db: Arc<KeyValueDB>,
meta_key: &'static [u8],
column: Option<u32>,
/// Best entry at the moment. None means that cache has no entries at all.
best_entry: RwLock<Option<Entry<NumberFor<Block>, T>>>,
}
/// Single cache entry.
#[derive(Clone)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct Entry<N, T: Clone> {
/// first block, when this value became actual
valid_from: N,
/// None means that we do not know the value starting from `valid_from` block
value: Option<T>,
}
/// Internal representation of the single cache entry. The entry points to the
/// previous entry in the cache, allowing us to traverse back in time in list-style.
#[derive(Encode, Decode)]
#[cfg_attr(test, derive(Debug, PartialEq))]
struct StorageEntry<N, T> {
/// None if valid from the beginning
prev_valid_from: Option<N>,
/// None means that we do not know the value starting from `valid_from` block
value: Option<T>,
}
impl<Block, T> DbCacheList<Block, T>
where
Block: BlockT,
NumberFor<Block>: As<u64>,
T: Clone + PartialEq + Codec,
{
/// Creates new cache list.
fn new(db: Arc<KeyValueDB>, meta_key: &'static [u8], column: Option<u32>) -> ClientResult<Self> {
let best_entry = RwLock::new(db.get(COLUMN_META, meta_key)
.map_err(db_err)
.and_then(|block| match block {
Some(block) => {
let valid_from = db_key_to_number(&block)?;
read_storage_entry::<Block, T>(&*db, column, valid_from)
.map(|entry| Some(Entry {
valid_from,
value: entry
.expect("meta entry references the entry at the block; storage entry at block exists when referenced; qed")
.value,
}))
},
None => Ok(None),
})?);
Ok(DbCacheList {
db,
column,
meta_key,
best_entry,
})
}
/// Gets the best known entry.
pub fn best_entry(&self) -> Option<Entry<NumberFor<Block>, T>> {
self.best_entry.read().clone()
}
/// Commits the new best pending value to the database. Returns Some if best entry must
/// be updated after transaction is committed.
pub fn commit_best_entry(
&self,
transaction: &mut DBTransaction,
valid_from: NumberFor<Block>,
pending_value: Option<T>
) -> Option<Entry<NumberFor<Block>, T>> {
let best_entry = self.best_entry();
let update_best_entry = match (
best_entry.as_ref().and_then(|a| a.value.as_ref()),
pending_value.as_ref()
) {
(Some(best_value), Some(pending_value)) => best_value != pending_value,
(None, Some(_)) | (Some(_), None) => true,
(None, None) => false,
};
if !update_best_entry {
return None;
}
let valid_from_key = number_to_db_key(valid_from);
transaction.put(COLUMN_META, self.meta_key, &valid_from_key);
transaction.put(self.column, &valid_from_key, &StorageEntry {
prev_valid_from: best_entry.map(|b| b.valid_from),
value: pending_value.clone(),
}.encode());
Some(Entry {
valid_from,
value: pending_value,
})
}
/// Updates the best in-memory cache entry. Must be called after transaction with changes
/// from commit_best_entry has been committed.
pub fn update_best_entry(&self, best_entry: Option<Entry<NumberFor<Block>, T>>) {
*self.best_entry.write() = best_entry;
}
/// Prune all entries from the beginning up to the block (including entry at the number). Returns
/// the number of pruned entries. Pruning never deletes the latest entry in the cache.
pub fn prune_entries(
&self,
transaction: &mut DBTransaction,
last_to_prune: NumberFor<Block>
) -> ClientResult<usize> {
// find the last entry we want to keep
let mut last_entry_to_keep = match self.best_entry() {
Some(best_entry) => best_entry.valid_from,
None => return Ok(0),
};
let mut first_entry_to_remove = last_entry_to_keep;
while first_entry_to_remove > last_to_prune {
last_entry_to_keep = first_entry_to_remove;
let entry = read_storage_entry::<Block, T>(&*self.db, self.column, first_entry_to_remove)?
.expect("entry referenced from the next entry; entry exists when referenced; qed");
// if we have reached the first list entry
// AND all list entries are for blocks that are later than last_to_prune
// => nothing to prune
first_entry_to_remove = match entry.prev_valid_from {
Some(prev_valid_from) => prev_valid_from,
None => return Ok(0),
}
}
// remove all entries, starting from entry_to_remove
let mut pruned = 0;
let mut entry_to_remove = Some(first_entry_to_remove);
while let Some(current_entry) = entry_to_remove {
let entry = read_storage_entry::<Block, T>(&*self.db, self.column, current_entry)?
.expect("referenced entry exists; entry_to_remove is a reference to the entry; qed");
if current_entry != last_entry_to_keep {
transaction.delete(self.column, &number_to_db_key(current_entry));
pruned += 1;
}
entry_to_remove = entry.prev_valid_from;
}
let mut entry = read_storage_entry::<Block, T>(&*self.db, self.column, last_entry_to_keep)?
.expect("last_entry_to_keep >= first_entry_to_remove; that means that we're leaving this entry in the db; qed");
entry.prev_valid_from = None;
transaction.put(self.column, &number_to_db_key(last_entry_to_keep), &entry.encode());
Ok(pruned)
}
/// Reads the cached value, actual at given block. Returns None if the value was not cached
/// or if it has been pruned.
fn value_at_key(&self, key: BlockKey) -> ClientResult<Option<T>> {
let at = db_key_to_number::<NumberFor<Block>>(&key)?;
let best_valid_from = match self.best_entry() {
// there are entries in cache
Some(best_entry) => {
// we're looking for the best value
if at >= best_entry.valid_from {
return Ok(best_entry.value);
}
// we're looking for the value of older blocks
best_entry.valid_from
},
// there are no entries in the cache
None => return Ok(None),
};
let mut entry = read_storage_entry::<Block, T>(&*self.db, self.column, best_valid_from)?
.expect("self.best_entry().is_some() if there's entry for best_valid_from; qed");
loop {
let prev_valid_from = match entry.prev_valid_from {
Some(prev_valid_from) => prev_valid_from,
None => return Ok(None),
};
let prev_entry = read_storage_entry::<Block, T>(&*self.db, self.column, prev_valid_from)?
.expect("entry referenced from the next entry; entry exists when referenced; qed");
if at >= prev_valid_from {
return Ok(prev_entry.value);
}
entry = prev_entry;
}
}
}
/// Reads the entry at the block with given number.
fn read_storage_entry<Block, T>(
db: &KeyValueDB,
column: Option<u32>,
number: NumberFor<Block>
) -> ClientResult<Option<StorageEntry<NumberFor<Block>, T>>>
where
Block: BlockT,
NumberFor<Block>: As<u64>,
T: Codec,
{
db.get(column, &number_to_db_key(number))
.and_then(|entry| match entry {
Some(entry) => Ok(StorageEntry::<NumberFor<Block>, T>::decode(&mut &entry[..])),
None => Ok(None),
})
.map_err(db_err)
}
#[cfg(test)]
mod tests {
use runtime_primitives::testing::Block as RawBlock;
use light::{AUTHORITIES_ENTRIES_TO_KEEP, columns, LightStorage};
use light::tests::insert_block;
use super::*;
type Block = RawBlock<u64>;
#[test]
fn authorities_storage_entry_serialized() {
let test_cases: Vec<StorageEntry<u64, Vec<AuthorityId>>> = vec![
StorageEntry { prev_valid_from: Some(42), value: Some(vec![[1u8; 32].into()]) },
StorageEntry { prev_valid_from: None, value: Some(vec![[1u8; 32].into(), [2u8; 32].into()]) },
StorageEntry { prev_valid_from: None, value: None },
];
for expected in test_cases {
let serialized = expected.encode();
let deserialized = StorageEntry::decode(&mut &serialized[..]).unwrap();
assert_eq!(expected, deserialized);
}
}
#[test]
fn best_authorities_are_updated() {
let db = LightStorage::new_test();
let authorities_at: Vec<(usize, Option<Entry<u64, Vec<AuthorityId>>>)> = vec![
(0, None),
(0, None),
(1, Some(Entry { valid_from: 1, value: Some(vec![[2u8; 32].into()]) })),
(1, Some(Entry { valid_from: 1, value: Some(vec![[2u8; 32].into()]) })),
(2, Some(Entry { valid_from: 3, value: Some(vec![[4u8; 32].into()]) })),
(2, Some(Entry { valid_from: 3, value: Some(vec![[4u8; 32].into()]) })),
(3, Some(Entry { valid_from: 5, value: None })),
(3, Some(Entry { valid_from: 5, value: None })),
];
// before any block, there are no entries in cache
assert!(db.cache().authorities_at_cache().best_entry().is_none());
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), 0);
// insert blocks and check that best_authorities() returns correct result
let mut prev_hash = Default::default();
for number in 0..authorities_at.len() {
let authorities_at_number = authorities_at[number].1.clone().and_then(|e| e.value);
prev_hash = insert_block(&db, &prev_hash, number as u64, authorities_at_number);
assert_eq!(db.cache().authorities_at_cache().best_entry(), authorities_at[number].1);
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), authorities_at[number].0);
}
// check that authorities_at() returns correct results for all retrospective blocks
for number in 1..authorities_at.len() + 1 {
assert_eq!(db.cache().authorities_at(BlockId::Number(number as u64)),
authorities_at.get(number + 1)
.or_else(|| authorities_at.last())
.unwrap().1.clone().and_then(|e| e.value));
}
// now check that cache entries are pruned when new blocks are inserted
let mut current_entries_count = authorities_at.last().unwrap().0;
let pruning_starts_at = AUTHORITIES_ENTRIES_TO_KEEP as usize;
for number in authorities_at.len()..authorities_at.len() + pruning_starts_at {
prev_hash = insert_block(&db, &prev_hash, number as u64, None);
if number > pruning_starts_at {
let prev_entries_count = authorities_at[number - pruning_starts_at].0;
let entries_count = authorities_at.get(number - pruning_starts_at + 1).map(|e| e.0)
.unwrap_or_else(|| authorities_at.last().unwrap().0);
current_entries_count -= entries_count - prev_entries_count;
}
// there's always at least 1 entry in the cache (after first insertion)
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), ::std::cmp::max(current_entries_count, 1));
}
}
#[test]
fn best_authorities_are_pruned() {
let db = LightStorage::<Block>::new_test();
let mut transaction = DBTransaction::new();
// insert first entry at block#100
db.cache().authorities_at_cache().update_best_entry(
db.cache().authorities_at_cache().commit_best_entry(&mut transaction, 100, Some(vec![[1u8; 32].into()])));
db.db().write(transaction).unwrap();
// no entries are pruned, since there's only one entry in the cache
let mut transaction = DBTransaction::new();
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 50).unwrap(), 0);
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 100).unwrap(), 0);
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 0);
// insert second entry at block#200
let mut transaction = DBTransaction::new();
db.cache().authorities_at_cache().update_best_entry(
db.cache().authorities_at_cache().commit_best_entry(&mut transaction, 200, Some(vec![[2u8; 32].into()])));
db.db().write(transaction).unwrap();
let mut transaction = DBTransaction::new();
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 50).unwrap(), 0);
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 100).unwrap(), 1);
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 1);
// still only 1 entry is removed since pruning never deletes the last entry
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 200).unwrap(), 1);
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 250).unwrap(), 1);
// physically remove entry for block#100 from db
let mut transaction = DBTransaction::new();
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 1);
db.db().write(transaction).unwrap();
assert_eq!(db.cache().authorities_at_cache().best_entry().unwrap().value, Some(vec![[2u8; 32].into()]));
assert_eq!(db.cache().authorities_at(BlockId::Number(50)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(100)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(150)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(200)), Some(vec![[2u8; 32].into()]));
assert_eq!(db.cache().authorities_at(BlockId::Number(250)), Some(vec![[2u8; 32].into()]));
// try to delete last entry => failure (no entries are removed)
let mut transaction = DBTransaction::new();
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 300).unwrap(), 0);
db.db().write(transaction).unwrap();
assert_eq!(db.cache().authorities_at_cache().best_entry().unwrap().value, Some(vec![[2u8; 32].into()]));
assert_eq!(db.cache().authorities_at(BlockId::Number(50)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(100)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(150)), None);
assert_eq!(db.cache().authorities_at(BlockId::Number(200)), Some(vec![[2u8; 32].into()]));
assert_eq!(db.cache().authorities_at(BlockId::Number(250)), Some(vec![[2u8; 32].into()]));
}
}
+714
View File
@@ -0,0 +1,714 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
// tag::description[]
//! Client backend that uses RocksDB database as storage.
// end::description[]
extern crate substrate_client as client;
extern crate kvdb_rocksdb;
extern crate kvdb;
extern crate hashdb;
extern crate memorydb;
extern crate parking_lot;
extern crate substrate_state_machine as state_machine;
extern crate substrate_primitives as primitives;
extern crate sr_primitives as runtime_primitives;
extern crate parity_codec as codec;
extern crate substrate_executor as executor;
extern crate substrate_state_db as state_db;
#[macro_use]
extern crate log;
#[macro_use]
extern crate parity_codec_derive;
#[cfg(test)]
extern crate kvdb_memorydb;
pub mod light;
mod cache;
mod utils;
use std::sync::Arc;
use std::path::PathBuf;
use std::io;
use codec::{Decode, Encode};
use hashdb::Hasher;
use kvdb::{KeyValueDB, DBTransaction};
use memorydb::MemoryDB;
use parking_lot::RwLock;
use primitives::{H256, AuthorityId, Blake2Hasher, RlpCodec};
use runtime_primitives::generic::BlockId;
use runtime_primitives::bft::Justification;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hash, HashFor, NumberFor, Zero};
use runtime_primitives::BuildStorage;
use state_machine::backend::Backend as StateBackend;
use executor::RuntimeInfo;
use state_machine::{CodeExecutor, DBValue, ExecutionStrategy};
use utils::{Meta, db_err, meta_keys, number_to_db_key, db_key_to_number, open_database,
read_db, read_id, read_meta};
use state_db::StateDb;
pub use state_db::PruningMode;
const FINALIZATION_WINDOW: u64 = 32;
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
pub type DbState = state_machine::TrieBackend<Blake2Hasher, RlpCodec>;
/// Database settings.
pub struct DatabaseSettings {
/// Cache size in bytes. If `None` default is used.
pub cache_size: Option<usize>,
/// Path to the database.
pub path: PathBuf,
/// Pruning mode.
pub pruning: PruningMode,
}
/// Create an instance of db-backed client.
pub fn new_client<E, S, Block>(
settings: DatabaseSettings,
executor: E,
genesis_storage: S,
execution_strategy: ExecutionStrategy,
) -> Result<client::Client<Backend<Block>, client::LocalCallExecutor<Backend<Block>, E>, Block>, client::error::Error>
where
Block: BlockT,
E: CodeExecutor<Blake2Hasher> + RuntimeInfo,
S: BuildStorage,
{
let backend = Arc::new(Backend::new(settings, FINALIZATION_WINDOW)?);
let executor = client::LocalCallExecutor::new(backend.clone(), executor);
Ok(client::Client::new(backend, executor, genesis_storage, execution_strategy)?)
}
mod columns {
pub const META: Option<u32> = Some(0);
pub const STATE: Option<u32> = Some(1);
pub const STATE_META: Option<u32> = Some(2);
pub const BLOCK_INDEX: Option<u32> = Some(3);
pub const HEADER: Option<u32> = Some(4);
pub const BODY: Option<u32> = Some(5);
pub const JUSTIFICATION: Option<u32> = Some(6);
}
struct PendingBlock<Block: BlockT> {
header: Block::Header,
justification: Option<Justification<Block::Hash>>,
body: Option<Vec<Block::Extrinsic>>,
is_best: bool,
}
// wrapper that implements trait required for state_db
struct StateMetaDb<'a>(&'a KeyValueDB);
impl<'a> state_db::MetaDb for StateMetaDb<'a> {
type Error = io::Error;
fn get_meta(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
self.0.get(columns::STATE_META, key).map(|r| r.map(|v| v.to_vec()))
}
}
/// Block database
pub struct BlockchainDb<Block: BlockT> {
db: Arc<KeyValueDB>,
meta: RwLock<Meta<<Block::Header as HeaderT>::Number, Block::Hash>>,
}
impl<Block: BlockT> BlockchainDb<Block> {
fn new(db: Arc<KeyValueDB>) -> Result<Self, client::error::Error> {
let meta = read_meta::<Block>(&*db, columns::HEADER)?;
Ok(BlockchainDb {
db,
meta: RwLock::new(meta)
})
}
fn update_meta(&self, hash: Block::Hash, number: <Block::Header as HeaderT>::Number, is_best: bool) {
if is_best {
let mut meta = self.meta.write();
if number == Zero::zero() {
meta.genesis_hash = hash;
}
meta.best_number = number;
meta.best_hash = hash;
}
}
}
impl<Block: BlockT> client::blockchain::HeaderBackend<Block> for BlockchainDb<Block> {
fn header(&self, id: BlockId<Block>) -> Result<Option<Block::Header>, client::error::Error> {
match read_db(&*self.db, columns::BLOCK_INDEX, columns::HEADER, id)? {
Some(header) => match Block::Header::decode(&mut &header[..]) {
Some(header) => Ok(Some(header)),
None => return Err(client::error::ErrorKind::Backend("Error decoding header".into()).into()),
}
None => Ok(None),
}
}
fn info(&self) -> Result<client::blockchain::Info<Block>, client::error::Error> {
let meta = self.meta.read();
Ok(client::blockchain::Info {
best_hash: meta.best_hash,
best_number: meta.best_number,
genesis_hash: meta.genesis_hash,
})
}
fn status(&self, id: BlockId<Block>) -> Result<client::blockchain::BlockStatus, client::error::Error> {
let exists = match id {
BlockId::Hash(_) => read_id(&*self.db, columns::BLOCK_INDEX, id)?.is_some(),
BlockId::Number(n) => n <= self.meta.read().best_number,
};
match exists {
true => Ok(client::blockchain::BlockStatus::InChain),
false => Ok(client::blockchain::BlockStatus::Unknown),
}
}
fn number(&self, hash: Block::Hash) -> Result<Option<<Block::Header as HeaderT>::Number>, client::error::Error> {
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
.and_then(|key| match key {
Some(key) => Ok(Some(db_key_to_number(&key)?)),
None => Ok(None),
})
}
fn hash(&self, number: <Block::Header as HeaderT>::Number) -> Result<Option<Block::Hash>, client::error::Error> {
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
)
}
}
impl<Block: BlockT> client::blockchain::Backend<Block> for BlockchainDb<Block> {
fn body(&self, id: BlockId<Block>) -> Result<Option<Vec<Block::Extrinsic>>, client::error::Error> {
match read_db(&*self.db, columns::BLOCK_INDEX, columns::BODY, id)? {
Some(body) => match Decode::decode(&mut &body[..]) {
Some(body) => Ok(Some(body)),
None => return Err(client::error::ErrorKind::Backend("Error decoding body".into()).into()),
}
None => Ok(None),
}
}
fn justification(&self, id: BlockId<Block>) -> Result<Option<Justification<Block::Hash>>, client::error::Error> {
match read_db(&*self.db, columns::BLOCK_INDEX, columns::JUSTIFICATION, id)? {
Some(justification) => match Decode::decode(&mut &justification[..]) {
Some(justification) => Ok(Some(justification)),
None => return Err(client::error::ErrorKind::Backend("Error decoding justification".into()).into()),
}
None => Ok(None),
}
}
fn cache(&self) -> Option<&client::blockchain::Cache<Block>> {
None
}
}
/// Database transaction
pub struct BlockImportOperation<Block: BlockT, H: Hasher> {
old_state: DbState,
updates: MemoryDB<H>,
pending_block: Option<PendingBlock<Block>>,
}
impl<Block> client::backend::BlockImportOperation<Block, Blake2Hasher, RlpCodec>
for BlockImportOperation<Block, Blake2Hasher>
where Block: BlockT,
{
type State = DbState;
fn state(&self) -> Result<Option<&Self::State>, client::error::Error> {
Ok(Some(&self.old_state))
}
fn set_block_data(&mut self, header: Block::Header, body: Option<Vec<Block::Extrinsic>>, justification: Option<Justification<Block::Hash>>, is_best: bool) -> Result<(), client::error::Error> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block = Some(PendingBlock {
header,
body,
justification,
is_best,
});
Ok(())
}
fn update_authorities(&mut self, _authorities: Vec<AuthorityId>) {
// currently authorities are not cached on full nodes
}
fn update_storage(&mut self, update: MemoryDB<Blake2Hasher>) -> Result<(), client::error::Error> {
self.updates = update;
Ok(())
}
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> Result<(), client::error::Error> {
// TODO: wipe out existing trie.
let (_, update) = self.old_state.storage_root(iter.into_iter().map(|(k, v)| (k, Some(v))));
self.updates = update;
Ok(())
}
}
struct StorageDb<Block: BlockT> {
pub db: Arc<KeyValueDB>,
pub state_db: StateDb<Block::Hash, H256>,
}
impl<Block: BlockT> state_machine::Storage<Blake2Hasher> for StorageDb<Block> {
fn get(&self, key: &H256) -> Result<Option<DBValue>, String> {
self.state_db.get(&key.0.into(), self).map(|r| r.map(|v| DBValue::from_slice(&v)))
.map_err(|e| format!("Database backend error: {:?}", e))
}
}
impl<Block: BlockT> state_db::HashDb for StorageDb<Block> {
type Error = io::Error;
type Hash = H256;
fn get(&self, key: &H256) -> Result<Option<Vec<u8>>, Self::Error> {
self.db.get(columns::STATE, &key[..]).map(|r| r.map(|v| v.to_vec()))
}
}
/// 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<Block: BlockT> {
storage: Arc<StorageDb<Block>>,
blockchain: BlockchainDb<Block>,
finalization_window: u64,
}
impl<Block: BlockT> Backend<Block> {
/// Create a new instance of database backend.
pub fn new(config: DatabaseSettings, finalization_window: u64) -> Result<Self, client::error::Error> {
let db = open_database(&config, "full")?;
Backend::from_kvdb(db as Arc<_>, config.pruning, finalization_window)
}
#[cfg(test)]
fn new_test(keep_blocks: u32) -> Self {
use utils::NUM_COLUMNS;
let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS));
Backend::from_kvdb(db as Arc<_>, PruningMode::keep_blocks(keep_blocks), 0).expect("failed to create test-db")
}
fn from_kvdb(db: Arc<KeyValueDB>, pruning: PruningMode, finalization_window: u64) -> Result<Self, client::error::Error> {
let blockchain = BlockchainDb::new(db.clone())?;
let map_e = |e: state_db::Error<io::Error>| ::client::error::Error::from(format!("State database error: {:?}", e));
let state_db: StateDb<Block::Hash, H256> = StateDb::new(pruning, &StateMetaDb(&*db)).map_err(map_e)?;
let storage_db = StorageDb {
db,
state_db,
};
Ok(Backend {
storage: Arc::new(storage_db),
blockchain,
finalization_window,
})
}
}
fn apply_state_commit(transaction: &mut DBTransaction, commit: state_db::CommitSet<H256>) {
for (key, val) in commit.data.inserted.into_iter() {
transaction.put(columns::STATE, &key[..], &val);
}
for key in commit.data.deleted.into_iter() {
transaction.delete(columns::STATE, &key[..]);
}
for (key, val) in commit.meta.inserted.into_iter() {
transaction.put(columns::STATE_META, &key[..], &val);
}
for key in commit.meta.deleted.into_iter() {
transaction.delete(columns::STATE_META, &key[..]);
}
}
impl<Block> client::backend::Backend<Block, Blake2Hasher, RlpCodec> for Backend<Block> where Block: BlockT {
type BlockImportOperation = BlockImportOperation<Block, Blake2Hasher>;
type Blockchain = BlockchainDb<Block>;
type State = DbState;
fn begin_operation(&self, block: BlockId<Block>) -> Result<Self::BlockImportOperation, client::error::Error> {
let state = self.state_at(block)?;
Ok(BlockImportOperation {
pending_block: None,
old_state: state,
updates: MemoryDB::default(),
})
}
fn commit_operation(&self, mut operation: Self::BlockImportOperation) -> Result<(), client::error::Error> {
use client::blockchain::HeaderBackend;
let mut transaction = DBTransaction::new();
if let Some(pending_block) = operation.pending_block {
let hash = pending_block.header.hash();
let number = pending_block.header.number().clone();
let key = number_to_db_key(number.clone());
transaction.put(columns::HEADER, &key, &pending_block.header.encode());
if let Some(body) = pending_block.body {
transaction.put(columns::BODY, &key, &body.encode());
}
if let Some(justification) = pending_block.justification {
transaction.put(columns::JUSTIFICATION, &key, &justification.encode());
}
transaction.put(columns::BLOCK_INDEX, hash.as_ref(), &key);
if pending_block.is_best {
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
}
let mut changeset: state_db::ChangeSet<H256> = state_db::ChangeSet::default();
for (key, (val, rc)) in operation.updates.drain() {
if rc > 0 {
changeset.inserted.push((key.0.into(), val.to_vec()));
} else if rc < 0 {
changeset.deleted.push(key.0.into());
}
}
let number_u64 = number.as_().into();
let commit = self.storage.state_db.insert_block(&hash, number_u64, &pending_block.header.parent_hash(), changeset);
apply_state_commit(&mut transaction, commit);
//finalize an older block
if number_u64 > self.finalization_window {
let finalizing_hash = if self.finalization_window == 0 {
Some(hash)
} else {
let finalizing = number_u64 - self.finalization_window;
if finalizing > self.storage.state_db.best_finalized() {
self.blockchain.hash(As::sa(finalizing))?
} else {
None
}
};
if let Some(finalizing_hash) = finalizing_hash {
trace!(target: "db", "Finalizing block #{} ({:?})", number_u64 - self.finalization_window, finalizing_hash);
let commit = self.storage.state_db.finalize_block(&finalizing_hash);
apply_state_commit(&mut transaction, commit);
}
}
debug!(target: "db", "DB Commit {:?} ({}), best = {}", hash, number, pending_block.is_best);
self.storage.db.write(transaction).map_err(db_err)?;
self.blockchain.update_meta(hash, number, pending_block.is_best);
}
Ok(())
}
fn revert(&self, n: NumberFor<Block>) -> Result<NumberFor<Block>, client::error::Error> {
use client::blockchain::HeaderBackend;
let mut best = self.blockchain.info()?.best_number;
for c in 0 .. n.as_() {
if best == As::sa(0) {
return Ok(As::sa(c))
}
let mut transaction = DBTransaction::new();
match self.storage.state_db.revert_one() {
Some(commit) => {
apply_state_commit(&mut transaction, commit);
let removed = self.blockchain.hash(best)?.ok_or_else(
|| client::error::ErrorKind::UnknownBlock(
format!("Error reverting to {}. Block hash not found.", best)))?;
best -= As::sa(1);
let key = number_to_db_key(best.clone());
let hash = self.blockchain.hash(best)?.ok_or_else(
|| client::error::ErrorKind::UnknownBlock(
format!("Error reverting to {}. Block hash not found.", best)))?;
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
transaction.delete(columns::BLOCK_INDEX, removed.as_ref());
self.storage.db.write(transaction).map_err(db_err)?;
self.blockchain.update_meta(hash, best, true);
}
None => return Ok(As::sa(c))
}
}
Ok(n)
}
fn blockchain(&self) -> &BlockchainDb<Block> {
&self.blockchain
}
fn state_at(&self, block: BlockId<Block>) -> Result<Self::State, client::error::Error> {
use client::blockchain::HeaderBackend as BcHeaderBackend;
// special case for genesis initialization
match block {
BlockId::Hash(h) if h == Default::default() =>
return Ok(DbState::with_storage_for_genesis(self.storage.clone())),
_ => {}
}
match self.blockchain.header(block) {
Ok(Some(ref hdr)) if !self.storage.state_db.is_pruned(hdr.number().as_()) => {
let root = H256::from_slice(hdr.state_root().as_ref());
Ok(DbState::with_storage(self.storage.clone(), root))
},
Err(e) => Err(e),
_ => Err(client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()),
}
}
}
impl<Block> client::backend::LocalBackend<Block, Blake2Hasher, RlpCodec> for Backend<Block>
where Block: BlockT {}
#[cfg(test)]
mod tests {
use hashdb::HashDB;
use super::*;
use client::backend::Backend as BTrait;
use client::backend::BlockImportOperation as Op;
use client::blockchain::HeaderBackend as BlockchainHeaderBackend;
use runtime_primitives::testing::{Header, Block as RawBlock};
type Block = RawBlock<u64>;
#[test]
fn block_hash_inserted_correctly() {
let db = Backend::<Block>::new_test(1);
for i in 0..10 {
assert!(db.blockchain().hash(i).unwrap().is_none());
{
let id = if i == 0 {
BlockId::Hash(Default::default())
} else {
BlockId::Number(i - 1)
};
let mut op = db.begin_operation(id).unwrap();
let header = Header {
number: i,
parent_hash: if i == 0 {
Default::default()
} else {
db.blockchain.hash(i - 1).unwrap().unwrap()
},
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
op.set_block_data(
header,
Some(vec![]),
None,
true,
).unwrap();
db.commit_operation(op).unwrap();
}
assert!(db.blockchain().hash(i).unwrap().is_some())
}
}
#[test]
fn set_state_data() {
let db = Backend::<Block>::new_test(2);
{
let mut op = db.begin_operation(BlockId::Hash(Default::default())).unwrap();
let mut header = Header {
number: 0,
parent_hash: Default::default(),
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
let storage = vec![
(vec![1, 3, 5], vec![2, 4, 6]),
(vec![1, 2, 3], vec![9, 9, 9]),
];
header.state_root = op.old_state.storage_root(storage
.iter()
.cloned()
.map(|(x, y)| (x, Some(y)))
).0.into();
op.reset_storage(storage.iter().cloned()).unwrap();
op.set_block_data(
header,
Some(vec![]),
None,
true
).unwrap();
db.commit_operation(op).unwrap();
let state = db.state_at(BlockId::Number(0)).unwrap();
assert_eq!(state.storage(&[1, 3, 5]).unwrap(), Some(vec![2, 4, 6]));
assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
assert_eq!(state.storage(&[5, 5, 5]).unwrap(), None);
}
{
let mut op = db.begin_operation(BlockId::Number(0)).unwrap();
let mut header = Header {
number: 1,
parent_hash: Default::default(),
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
let storage = vec![
(vec![1, 3, 5], None),
(vec![5, 5, 5], Some(vec![4, 5, 6])),
];
let (root, overlay) = op.old_state.storage_root(storage.iter().cloned());
op.update_storage(overlay).unwrap();
header.state_root = root.into();
op.set_block_data(
header,
Some(vec![]),
None,
true
).unwrap();
db.commit_operation(op).unwrap();
let state = db.state_at(BlockId::Number(1)).unwrap();
assert_eq!(state.storage(&[1, 3, 5]).unwrap(), None);
assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
assert_eq!(state.storage(&[5, 5, 5]).unwrap(), Some(vec![4, 5, 6]));
}
}
#[test]
fn delete_only_when_negative_rc() {
let key;
let backend = Backend::<Block>::new_test(0);
let hash = {
let mut op = backend.begin_operation(BlockId::Hash(Default::default())).unwrap();
let mut header = 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.old_state.storage_root(storage
.iter()
.cloned()
.map(|(x, y)| (x, Some(y)))
).0.into();
let hash = header.hash();
op.reset_storage(storage.iter().cloned()).unwrap();
key = op.updates.insert(b"hello");
op.set_block_data(
header,
Some(vec![]),
None,
true
).unwrap();
backend.commit_operation(op).unwrap();
assert_eq!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]);
hash
};
let hash = {
let mut op = backend.begin_operation(BlockId::Number(0)).unwrap();
let mut header = Header {
number: 1,
parent_hash: hash,
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
let storage: Vec<(_, _)> = vec![];
header.state_root = op.old_state.storage_root(storage
.iter()
.cloned()
.map(|(x, y)| (x, Some(y)))
).0.into();
let hash = header.hash();
op.updates.insert(b"hello");
op.updates.remove(&key);
op.set_block_data(
header,
Some(vec![]),
None,
true
).unwrap();
backend.commit_operation(op).unwrap();
assert_eq!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]);
hash
};
{
let mut op = backend.begin_operation(BlockId::Number(1)).unwrap();
let mut header = Header {
number: 2,
parent_hash: hash,
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
let storage: Vec<(_, _)> = vec![];
header.state_root = op.old_state.storage_root(storage
.iter()
.cloned()
.map(|(x, y)| (x, Some(y)))
).0.into();
op.updates.remove(&key);
op.set_block_data(
header,
Some(vec![]),
None,
true
).unwrap();
backend.commit_operation(op).unwrap();
assert!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().is_none());
}
}
}
+392
View File
@@ -0,0 +1,392 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! RocksDB-based light client blockchain storage.
use std::sync::Arc;
use parking_lot::RwLock;
use kvdb::{KeyValueDB, DBTransaction};
use client::blockchain::{BlockStatus, Cache as BlockchainCache,
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo};
use client::cht;
use client::error::{ErrorKind as ClientErrorKind, Result as ClientResult};
use client::light::blockchain::Storage as LightBlockchainStorage;
use codec::{Decode, Encode};
use primitives::{AuthorityId, H256, Blake2Hasher};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor,
Zero, One, As, NumberFor};
use cache::DbCache;
use utils::{meta_keys, Meta, db_err, number_to_db_key, db_key_to_number, open_database,
read_db, read_id, read_meta};
use DatabaseSettings;
pub(crate) mod columns {
pub const META: Option<u32> = ::utils::COLUMN_META;
pub const BLOCK_INDEX: Option<u32> = Some(1);
pub const HEADER: Option<u32> = Some(2);
pub const AUTHORITIES: Option<u32> = Some(3);
pub const CHT: Option<u32> = Some(4);
}
/// Keep authorities for last 'AUTHORITIES_ENTRIES_TO_KEEP' blocks.
pub(crate) const AUTHORITIES_ENTRIES_TO_KEEP: u64 = cht::SIZE;
/// Light blockchain storage. Stores most recent headers + CHTs for older headers.
pub struct LightStorage<Block: BlockT> {
db: Arc<KeyValueDB>,
meta: RwLock<Meta<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>>,
cache: DbCache<Block>,
}
#[derive(Clone, PartialEq, Debug)]
struct BestAuthorities<N> {
/// first block, when this set became actual
valid_from: N,
/// None means that we do not know the set starting from `valid_from` block
authorities: Option<Vec<AuthorityId>>,
}
impl<Block> LightStorage<Block>
where
Block: BlockT,
{
/// Create new storage with given settings.
pub fn new(config: DatabaseSettings) -> ClientResult<Self> {
let db = open_database(&config, "light")?;
Self::from_kvdb(db as Arc<_>)
}
#[cfg(test)]
pub(crate) fn new_test() -> Self {
use utils::NUM_COLUMNS;
let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS));
Self::from_kvdb(db as Arc<_>).expect("failed to create test-db")
}
fn from_kvdb(db: Arc<KeyValueDB>) -> ClientResult<Self> {
let cache = DbCache::new(db.clone(), columns::BLOCK_INDEX, columns::AUTHORITIES)?;
let meta = RwLock::new(read_meta::<Block>(&*db, columns::HEADER)?);
Ok(LightStorage {
db,
meta,
cache,
})
}
#[cfg(test)]
pub(crate) fn db(&self) -> &Arc<KeyValueDB> {
&self.db
}
#[cfg(test)]
pub(crate) fn cache(&self) -> &DbCache<Block> {
&self.cache
}
fn update_meta(&self, hash: Block::Hash, number: <<Block as BlockT>::Header as HeaderT>::Number, is_best: bool) {
if is_best {
let mut meta = self.meta.write();
if number == <<Block as BlockT>::Header as HeaderT>::Number::zero() {
meta.genesis_hash = hash;
}
meta.best_number = number;
meta.best_hash = hash;
}
}
}
impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
where
Block: BlockT,
{
fn header(&self, id: BlockId<Block>) -> ClientResult<Option<Block::Header>> {
match read_db(&*self.db, columns::BLOCK_INDEX, columns::HEADER, id)? {
Some(header) => match Block::Header::decode(&mut &header[..]) {
Some(header) => Ok(Some(header)),
None => return Err(ClientErrorKind::Backend("Error decoding header".into()).into()),
}
None => Ok(None),
}
}
fn info(&self) -> ClientResult<BlockchainInfo<Block>> {
let meta = self.meta.read();
Ok(BlockchainInfo {
best_hash: meta.best_hash,
best_number: meta.best_number,
genesis_hash: meta.genesis_hash,
})
}
fn status(&self, id: BlockId<Block>) -> ClientResult<BlockStatus> {
let exists = match id {
BlockId::Hash(_) => read_id(&*self.db, columns::BLOCK_INDEX, id)?.is_some(),
BlockId::Number(n) => n <= self.meta.read().best_number,
};
match exists {
true => Ok(BlockStatus::InChain),
false => Ok(BlockStatus::Unknown),
}
}
fn number(&self, hash: Block::Hash) -> ClientResult<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
.and_then(|key| match key {
Some(key) => Ok(Some(db_key_to_number(&key)?)),
None => Ok(None),
})
}
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Option<Block::Hash>> {
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
)
}
}
impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
where
Block: BlockT,
Block::Hash: From<H256>,
{
fn import_header(&self, is_new_best: bool, header: Block::Header, authorities: Option<Vec<AuthorityId>>) -> ClientResult<()> {
let mut transaction = DBTransaction::new();
let hash = header.hash();
let number = *header.number();
let key = number_to_db_key(number);
transaction.put(columns::HEADER, &key, &header.encode());
transaction.put(columns::BLOCK_INDEX, hash.as_ref(), &key);
let best_authorities = if is_new_best {
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
// cache authorities for previous block
let number: u64 = number.as_();
let previous_number = number.checked_sub(1);
let best_authorities = previous_number
.and_then(|previous_number| self.cache.authorities_at_cache()
.commit_best_entry(&mut transaction, As::sa(previous_number), authorities));
// prune authorities from 'ancient' blocks
if let Some(ancient_number) = number.checked_sub(AUTHORITIES_ENTRIES_TO_KEEP) {
self.cache.authorities_at_cache().prune_entries(&mut transaction, As::sa(ancient_number))?;
}
best_authorities
} else {
None
};
// build new CHT if required
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
let new_cht_start: NumberFor<Block> = cht::start_number(cht::SIZE, new_cht_number);
let new_cht_root: Option<Block::Hash> = cht::compute_root::<Block::Header, Blake2Hasher, _>(
cht::SIZE, new_cht_number, (new_cht_start.as_()..)
.map(|num| self.hash(As::sa(num)).unwrap_or_default()));
if let Some(new_cht_root) = new_cht_root {
transaction.put(columns::CHT, &number_to_db_key(new_cht_start), new_cht_root.as_ref());
let mut prune_block = new_cht_start;
let new_cht_end = cht::end_number(cht::SIZE, new_cht_number);
trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", new_cht_start, new_cht_end, new_cht_number);
while prune_block <= new_cht_end {
transaction.delete(columns::HEADER, &number_to_db_key(prune_block));
prune_block += <<Block as BlockT>::Header as HeaderT>::Number::one();
}
}
}
debug!("Light DB Commit {:?} ({})", hash, number);
self.db.write(transaction).map_err(db_err)?;
self.update_meta(hash, number, is_new_best);
if let Some(best_authorities) = best_authorities {
self.cache.authorities_at_cache().update_best_entry(Some(best_authorities));
}
Ok(())
}
fn cht_root(&self, cht_size: u64, block: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Block::Hash> {
let no_cht_for_block = || ClientErrorKind::Backend(format!("CHT for block {} not exists", block)).into();
let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?;
let cht_start = cht::start_number(cht_size, cht_number);
self.db.get(columns::CHT, &number_to_db_key(cht_start)).map_err(db_err)?
.ok_or_else(no_cht_for_block)
.and_then(|hash| Block::Hash::decode(&mut &*hash).ok_or_else(no_cht_for_block))
}
fn cache(&self) -> Option<&BlockchainCache<Block>> {
Some(&self.cache)
}
}
#[cfg(test)]
pub(crate) mod tests {
use client::cht;
use runtime_primitives::testing::{H256 as Hash, Header, Block as RawBlock};
use super::*;
type Block = RawBlock<u32>;
pub fn insert_block(
db: &LightStorage<Block>,
parent: &Hash,
number: u64,
authorities: Option<Vec<AuthorityId>>
) -> Hash {
let header = Header {
number: number.into(),
parent_hash: *parent,
state_root: Default::default(),
digest: Default::default(),
extrinsics_root: Default::default(),
};
let hash = header.hash();
db.import_header(true, header, authorities).unwrap();
hash
}
#[test]
fn returns_known_header() {
let db = LightStorage::new_test();
let known_hash = insert_block(&db, &Default::default(), 0, None);
let header_by_hash = db.header(BlockId::Hash(known_hash)).unwrap().unwrap();
let header_by_number = db.header(BlockId::Number(0)).unwrap().unwrap();
assert_eq!(header_by_hash, header_by_number);
}
#[test]
fn does_not_return_unknown_header() {
let db = LightStorage::<Block>::new_test();
assert!(db.header(BlockId::Hash(1.into())).unwrap().is_none());
assert!(db.header(BlockId::Number(0)).unwrap().is_none());
}
#[test]
fn returns_info() {
let db = LightStorage::new_test();
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
let info = db.info().unwrap();
assert_eq!(info.best_hash, genesis_hash);
assert_eq!(info.best_number, 0);
assert_eq!(info.genesis_hash, genesis_hash);
let best_hash = insert_block(&db, &genesis_hash, 1, None);
let info = db.info().unwrap();
assert_eq!(info.best_hash, best_hash);
assert_eq!(info.best_number, 1);
assert_eq!(info.genesis_hash, genesis_hash);
}
#[test]
fn returns_block_status() {
let db = LightStorage::new_test();
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
assert_eq!(db.status(BlockId::Hash(genesis_hash)).unwrap(), BlockStatus::InChain);
assert_eq!(db.status(BlockId::Number(0)).unwrap(), BlockStatus::InChain);
assert_eq!(db.status(BlockId::Hash(1.into())).unwrap(), BlockStatus::Unknown);
assert_eq!(db.status(BlockId::Number(1)).unwrap(), BlockStatus::Unknown);
}
#[test]
fn returns_block_hash() {
let db = LightStorage::new_test();
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
assert_eq!(db.hash(0).unwrap(), Some(genesis_hash));
assert_eq!(db.hash(1).unwrap(), None);
}
#[test]
fn import_header_works() {
let db = LightStorage::new_test();
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
assert_eq!(db.db.iter(columns::HEADER).count(), 1);
assert_eq!(db.db.iter(columns::BLOCK_INDEX).count(), 1);
let _ = insert_block(&db, &genesis_hash, 1, None);
assert_eq!(db.db.iter(columns::HEADER).count(), 2);
assert_eq!(db.db.iter(columns::BLOCK_INDEX).count(), 2);
}
#[test]
fn ancient_headers_are_replaced_with_cht() {
let db = LightStorage::new_test();
// insert genesis block header (never pruned)
let mut prev_hash = insert_block(&db, &Default::default(), 0, None);
// insert SIZE blocks && ensure that nothing is pruned
for number in 0..cht::SIZE {
prev_hash = insert_block(&db, &prev_hash, 1 + number, None);
}
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE) as usize);
assert_eq!(db.db.iter(columns::CHT).count(), 0);
// insert next SIZE blocks && ensure that nothing is pruned
for number in 0..cht::SIZE {
prev_hash = insert_block(&db, &prev_hash, 1 + cht::SIZE + number, None);
}
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + cht::SIZE) as usize);
assert_eq!(db.db.iter(columns::CHT).count(), 0);
// insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned
insert_block(&db, &prev_hash, 1 + cht::SIZE + cht::SIZE, None);
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize);
assert_eq!(db.db.iter(columns::CHT).count(), 1);
assert!((0..cht::SIZE).all(|i| db.db.get(columns::HEADER, &number_to_db_key(1 + i)).unwrap().is_none()));
}
#[test]
fn get_cht_fails_for_genesis_block() {
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, 0).is_err());
}
#[test]
fn get_cht_fails_for_non_existant_cht() {
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, (cht::SIZE / 2) as u64).is_err());
}
#[test]
fn get_cht_works() {
let db = LightStorage::new_test();
// insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created
let mut prev_hash = Default::default();
for i in 0..1 + cht::SIZE + cht::SIZE + 1 {
prev_hash = insert_block(&db, &prev_hash, i as u64, None);
}
let cht_root_1 = db.cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap();
let cht_root_2 = db.cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap();
let cht_root_3 = db.cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap();
assert_eq!(cht_root_1, cht_root_2);
assert_eq!(cht_root_2, cht_root_3);
}
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2017 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Db-based backend utility structures and functions, used by both
//! full and light storages.
use std::sync::Arc;
use std::io;
use kvdb::{KeyValueDB, DBTransaction};
use kvdb_rocksdb::{Database, DatabaseConfig};
use client;
use codec::Decode;
use hashdb::DBValue;
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, Hash, HashFor, Zero};
use DatabaseSettings;
/// Number of columns in the db. Must be the same for both full && light dbs.
/// Otherwise RocksDb will fail to open database && check its type.
pub const NUM_COLUMNS: u32 = 7;
/// Meta column. The set of keys in the column is shared by full && light storages.
pub const COLUMN_META: Option<u32> = Some(0);
/// Keys of entries in COLUMN_META.
pub mod meta_keys {
/// Type of storage (full or light).
pub const TYPE: &[u8; 4] = b"type";
/// Best block key.
pub const BEST_BLOCK: &[u8; 4] = b"best";
/// Best authorities block key.
pub const BEST_AUTHORITIES: &[u8; 4] = b"auth";
}
/// Database metadata.
pub struct Meta<N, H> {
/// Hash of the best known block.
pub best_hash: H,
/// Number of the best known block.
pub best_number: N,
/// Hash of the genesis block.
pub genesis_hash: H,
}
/// Type of block key in the database (LE block number).
pub type BlockKey = [u8; 4];
/// Convert block number into key (LE representation).
pub fn number_to_db_key<N>(n: N) -> BlockKey where N: As<u64> {
let n: u64 = n.as_();
assert!(n & 0xffffffff00000000 == 0);
[
(n >> 24) as u8,
((n >> 16) & 0xff) as u8,
((n >> 8) & 0xff) as u8,
(n & 0xff) as u8
]
}
/// Convert block key into block number.
pub fn db_key_to_number<N>(key: &[u8]) -> client::error::Result<N> where N: As<u64> {
match key.len() {
4 => Ok((key[0] as u64) << 24
| (key[1] as u64) << 16
| (key[2] as u64) << 8
| (key[3] as u64)).map(As::sa),
_ => Err(client::error::ErrorKind::Backend("Invalid block key".into()).into()),
}
}
/// Maps database error to client error
pub fn db_err(err: io::Error) -> client::error::Error {
use std::error::Error;
client::error::ErrorKind::Backend(err.description().into()).into()
}
/// Open RocksDB database.
pub fn open_database(config: &DatabaseSettings, db_type: &str) -> client::error::Result<Arc<KeyValueDB>> {
let mut db_config = DatabaseConfig::with_columns(Some(NUM_COLUMNS));
db_config.memory_budget = config.cache_size;
let path = config.path.to_str().ok_or_else(|| client::error::ErrorKind::Backend("Invalid database path".into()))?;
let db = Database::open(&db_config, &path).map_err(db_err)?;
// check database type
match db.get(COLUMN_META, meta_keys::TYPE).map_err(db_err)? {
Some(stored_type) => {
if db_type.as_bytes() != &*stored_type {
return Err(client::error::ErrorKind::Backend(
format!("Unexpected database type. Expected: {}", db_type)).into());
}
},
None => {
let mut transaction = DBTransaction::new();
transaction.put(COLUMN_META, meta_keys::TYPE, db_type.as_bytes());
db.write(transaction).map_err(db_err)?;
},
}
Ok(Arc::new(db))
}
/// Convert block id to block key, reading number from db if required.
pub fn read_id<Block>(db: &KeyValueDB, col_index: Option<u32>, id: BlockId<Block>) -> Result<Option<BlockKey>, client::error::Error>
where
Block: BlockT,
{
match id {
BlockId::Hash(h) => db.get(col_index, h.as_ref())
.map(|v| v.map(|v| {
let mut key: [u8; 4] = [0; 4];
key.copy_from_slice(&v);
key
})).map_err(db_err),
BlockId::Number(n) => Ok(Some(number_to_db_key(n))),
}
}
/// Read database column entry for the given block.
pub fn read_db<Block>(db: &KeyValueDB, col_index: Option<u32>, col: Option<u32>, id: BlockId<Block>) -> client::error::Result<Option<DBValue>>
where
Block: BlockT,
{
read_id(db, col_index, id).and_then(|key| match key {
Some(key) => db.get(col, &key).map_err(db_err),
None => Ok(None),
})
}
/// Read meta from the database.
pub fn read_meta<Block>(db: &KeyValueDB, col_header: Option<u32>) -> Result<Meta<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>, client::error::Error>
where
Block: BlockT,
{
let genesis_number = <<Block as BlockT>::Header as HeaderT>::Number::zero();
let (best_hash, best_number) = if let Some(Some(header)) = db.get(COLUMN_META, meta_keys::BEST_BLOCK).and_then(|id|
match id {
Some(id) => db.get(col_header, &id).map(|h| h.map(|b| Block::Header::decode(&mut &b[..]))),
None => Ok(None),
}).map_err(db_err)?
{
let hash = header.hash();
debug!("DB Opened blockchain db, best {:?} ({})", hash, header.number());
(hash, *header.number())
} else {
(Default::default(), genesis_number)
};
let genesis_hash = db.get(col_header, &number_to_db_key(genesis_number))
.map_err(db_err)?
.map(|raw| HashFor::<Block>::hash(&raw[..]))
.unwrap_or_default()
.into();
Ok(Meta {
best_hash,
best_number,
genesis_hash,
})
}