New database trait (#5549)

* Introduce trait

* The trait

* Generic

* Basic impls.

* Remove unneeded bounds

* Minor changes

* Switch over to the new DB trait

* Integrated parity-db and added CLI for db selection

* Default impl.

* Fix logs.

* Started integrating subdb

* Apply suggestions from code review

Co-Authored-By: Cecile Tonglet <cecile@parity.io>

* Apply suggestions from code review

Co-Authored-By: Nikolay Volf <nikvolf@gmail.com>

* Enable subdb

* Bump parity-db

* Fixed CLI macro

* Fixed browser build

* Fixed features

* Sort out features

* Use parity-db from crates.io

* Typo

Co-authored-by: arkpar <arkady.paronyan@gmail.com>
Co-authored-by: Cecile Tonglet <cecile@parity.io>
Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
This commit is contained in:
Gavin Wood
2020-04-15 14:38:39 +02:00
committed by GitHub
parent 3426d662f7
commit 91af5b6fcc
43 changed files with 1036 additions and 579 deletions
+97
View File
@@ -1081,6 +1081,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if",
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.4"
@@ -4686,6 +4696,19 @@ dependencies = [
"sp-storage",
]
[[package]]
name = "parity-db"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4174d70be686b0d7cdee964b2d723e1461e28390c8804f01efc907d81043be9"
dependencies = [
"blake2-rfc",
"libc",
"log",
"memmap",
"parking_lot 0.10.2",
]
[[package]]
name = "parity-multiaddr"
version = "0.7.3"
@@ -5034,6 +5057,16 @@ dependencies = [
"output_vt100",
]
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger 0.7.1",
"log",
]
[[package]]
name = "primitive-types"
version = "0.7.0"
@@ -5900,6 +5933,7 @@ dependencies = [
"sp-blockchain",
"sp-consensus",
"sp-core",
"sp-database 2.0.0-dev",
"sp-externalities",
"sp-inherents",
"sp-keyring",
@@ -5954,6 +5988,7 @@ dependencies = [
name = "sc-client-db"
version = "0.8.0-dev"
dependencies = [
"blake2-rfc",
"env_logger 0.7.1",
"hash-db",
"kvdb",
@@ -5961,6 +5996,7 @@ dependencies = [
"kvdb-rocksdb",
"linked-hash-map",
"log",
"parity-db",
"parity-scale-codec",
"parity-util-mem",
"parking_lot 0.10.2",
@@ -5972,10 +6008,12 @@ dependencies = [
"sp-blockchain",
"sp-consensus",
"sp-core",
"sp-database 2.0.0-dev",
"sp-keyring",
"sp-runtime",
"sp-state-machine",
"sp-trie",
"subdb",
"substrate-prometheus-endpoint",
"substrate-test-runtime-client",
"tempfile",
@@ -6995,6 +7033,17 @@ dependencies = [
"libc",
]
[[package]]
name = "simplelog"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcacac97349a890d437921dfb23cbec52ab5b4752551cb637df2721371acd467"
dependencies = [
"chrono",
"log",
"term",
]
[[package]]
name = "slab"
version = "0.4.2"
@@ -7375,6 +7424,23 @@ dependencies = [
"zeroize",
]
[[package]]
name = "sp-database"
version = "2.0.0-alpha.5"
source = "git+https://github.com/paritytech/substrate?branch=gav-db-trait#9404815700a840586fc8760a60180f4e1bf97ce4"
dependencies = [
"kvdb",
"parking_lot 0.10.2",
]
[[package]]
name = "sp-database"
version = "2.0.0-dev"
dependencies = [
"kvdb",
"parking_lot 0.10.2",
]
[[package]]
name = "sp-debug-derive"
version = "2.0.0-dev"
@@ -7855,6 +7921,26 @@ dependencies = [
"syn 1.0.17",
]
[[package]]
name = "subdb"
version = "0.1.0"
source = "git+https://github.com/paritytech/subdb#353bd49a95e618641b552fe890b272f0feb6d752"
dependencies = [
"blake2-rfc",
"derive_more",
"hash-db",
"hex",
"log",
"memmap",
"parity-scale-codec",
"parking_lot 0.10.2",
"pretty_env_logger",
"simplelog",
"smallvec 1.3.0",
"sp-database 2.0.0-alpha.5",
"twox-hash",
]
[[package]]
name = "subkey"
version = "2.0.0-dev"
@@ -7917,6 +8003,7 @@ dependencies = [
"sc-informant",
"sc-network",
"sc-service",
"sp-database 2.0.0-dev",
"wasm-bindgen",
"wasm-bindgen-futures",
]
@@ -8272,6 +8359,16 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "term"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5"
dependencies = [
"dirs",
"winapi 0.3.8",
]
[[package]]
name = "termcolor"
version = "1.1.0"
+1
View File
@@ -123,6 +123,7 @@ members = [
"primitives/consensus/vrf",
"primitives/core",
"primitives/chain-spec",
"primitives/database",
"primitives/debug-derive",
"primitives/storage",
"primitives/externalities",
+1 -1
View File
@@ -148,7 +148,7 @@ cli = [
"node-transaction-factory",
"sc-cli",
"frame-benchmarking-cli",
"sc-service/rocksdb",
"sc-service/db",
"structopt",
"substrate-build-script-utils",
]
+2 -2
View File
@@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
pallet-balances = { version = "2.0.0-dev", path = "../../../frame/balances" }
sc-client = { version = "0.8.0-dev", path = "../../../client/" }
sc-client-db = { version = "0.8.0-dev", path = "../../../client/db/", features = ["kvdb-rocksdb"] }
sc-client-db = { version = "0.8.0-dev", path = "../../../client/db/", features = ["kvdb-rocksdb", "parity-db"] }
sc-client-api = { version = "2.0.0-dev", path = "../../../client/api/" }
codec = { package = "parity-scale-codec", version = "1.3.0" }
pallet-contracts = { version = "2.0.0-dev", path = "../../../frame/contracts" }
@@ -54,4 +54,4 @@ fs_extra = "1"
[dev-dependencies]
criterion = "0.3.0"
sc-cli = { version = "0.8.0-dev", path = "../../../client/cli" }
sc-service = { version = "0.8.0-dev", path = "../../../client/service", features = ["rocksdb"] }
sc-service = { version = "0.8.0-dev", path = "../../../client/service", features = ["db"] }
+1 -1
View File
@@ -184,7 +184,7 @@ impl BenchDb {
state_cache_size: 16*1024*1024,
state_cache_child_ratio: Some((0, 100)),
pruning: PruningMode::ArchiveAll,
source: sc_client_db::DatabaseSettingsSrc::Path {
source: sc_client_db::DatabaseSettingsSrc::RocksDb {
path: dir.into(),
cache_size: 512,
},
+1
View File
@@ -38,6 +38,7 @@ sp-blockchain = { version = "2.0.0-dev", path = "../primitives/blockchain" }
sp-state-machine = { version = "0.8.0-dev", path = "../primitives/state-machine" }
sc-telemetry = { version = "2.0.0-dev", path = "telemetry" }
sp-trie = { version = "2.0.0-dev", path = "../primitives/trie" }
sp-database = { version = "2.0.0-dev", path = "../primitives/database" }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-dev", path = "../utils/prometheus" }
tracing = "0.1.10"
+14
View File
@@ -122,6 +122,20 @@ impl ExecutionStrategy {
}
}
arg_enum! {
/// Database backend
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
pub enum Database {
// Facebooks RocksDB
RocksDb,
// Subdb. https://github.com/paritytech/subdb/
SubDb,
// ParityDb. https://github.com/paritytech/parity-db/
ParityDb,
}
}
/// Default value for the `--execution-syncing` parameter.
pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::NativeElseWasm;
/// Default value for the `--execution-import-block` parameter.
@@ -74,7 +74,7 @@ impl ExportBlocksCmd {
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
<BB as BlockT>::Hash: std::str::FromStr,
{
if let DatabaseConfig::Path { ref path, .. } = &config.database {
if let DatabaseConfig::RocksDb { ref path, .. } = &config.database {
info!("DB path: {}", path.display());
}
+8 -1
View File
@@ -204,9 +204,16 @@ macro_rules! substrate_cli_subcommands {
&self,
base_path: &::std::path::PathBuf,
cache_size: usize,
database: $crate::Database,
) -> $crate::Result<::sc_service::config::DatabaseConfig> {
match self {
$($enum::$variant(cmd) => cmd.database_config(base_path, cache_size)),*
$($enum::$variant(cmd) => cmd.database_config(base_path, cache_size, database)),*
}
}
fn database(&self) -> $crate::Result<::std::option::Option<$crate::Database>> {
match self {
$($enum::$variant(cmd) => cmd.database()),*
}
}
@@ -39,7 +39,7 @@ impl PurgeChainCmd {
/// Run the purge command
pub fn run(&self, config: Configuration) -> error::Result<()> {
let db_path = match &config.database {
DatabaseConfig::Path { path, .. } => path,
DatabaseConfig::RocksDb { path, .. } => path,
_ => {
eprintln!("Cannot purge custom database implementation");
return Ok(());
+20 -3
View File
@@ -21,6 +21,7 @@ use crate::{
init_logger, ImportParams, KeystoreParams, NetworkParams, NodeKeyParams,
PruningParams, SharedParams, SubstrateCli,
};
use crate::arg_enums::Database;
use app_dirs::{AppDataType, AppInfo};
use names::{Generator, Name};
use sc_service::config::{
@@ -152,11 +153,26 @@ pub trait CliConfiguration: Sized {
.unwrap_or(Default::default()))
}
/// Get the database backend variant.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its `None`.
fn database(&self) -> Result<Option<Database>> {
Ok(self.import_params().map(|x| x.database()))
}
/// Get the database configuration.
///
/// By default this is retrieved from `SharedParams`
fn database_config(&self, base_path: &PathBuf, cache_size: usize) -> Result<DatabaseConfig> {
Ok(self.shared_params().database_config(base_path, cache_size))
fn database_config(&self,
base_path: &PathBuf,
cache_size: usize,
database: Database,
) -> Result<DatabaseConfig> {
Ok(self.shared_params().database_config(
base_path,
cache_size,
database,
))
}
/// Get the state cache size.
@@ -376,6 +392,7 @@ pub trait CliConfiguration: Sized {
let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH);
let client_id = C::client_id();
let database_cache_size = self.database_cache_size()?.unwrap_or(128);
let database = self.database()?.unwrap_or(Database::RocksDb);
let node_key = self.node_key(&net_config_dir)?;
let role = self.role(is_dev)?;
let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8);
@@ -394,7 +411,7 @@ pub trait CliConfiguration: Sized {
node_key,
)?,
keystore: self.keystore_config(&config_dir)?,
database: self.database_config(&config_dir, database_cache_size)?,
database: self.database_config(&config_dir, database_cache_size, database)?,
state_cache_size: self.state_cache_size()?,
state_cache_child_ratio: self.state_cache_child_ratio()?,
pruning: self.pruning(is_dev, &role)?,
@@ -17,7 +17,7 @@
use crate::arg_enums::{
ExecutionStrategy, TracingReceiver, WasmExecutionMethod, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION,
DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER,
DEFAULT_EXECUTION_SYNCING,
DEFAULT_EXECUTION_SYNCING, Database,
};
use crate::params::PruningParams;
use crate::Result;
@@ -54,6 +54,16 @@ pub struct ImportParams {
#[structopt(flatten)]
pub execution_strategies: ExecutionStrategiesParams,
/// Select database backend to use.
#[structopt(
long = "database",
alias = "db",
value_name = "DB",
case_insensitive = true,
default_value = "RocksDb"
)]
pub database: Database,
/// Limit the memory the database cache can use.
#[structopt(long = "db-cache", value_name = "MiB")]
pub database_cache_size: Option<usize>,
@@ -132,6 +142,11 @@ impl ImportParams {
pub fn database_cache_size(&self) -> Option<usize> {
self.database_cache_size
}
/// Limit the memory the database cache can use.
pub fn database(&self) -> Database {
self.database
}
}
/// Execution strategies parameters.
@@ -17,9 +17,7 @@
use sc_service::config::DatabaseConfig;
use std::path::PathBuf;
use structopt::StructOpt;
/// default sub directory to store database
const DEFAULT_DB_CONFIG_PATH: &'static str = "db";
use crate::arg_enums::Database;
/// Shared parameters used by all `CoreParams`.
#[derive(Debug, StructOpt, Clone)]
@@ -79,10 +77,19 @@ impl SharedParams {
&self,
base_path: &PathBuf,
cache_size: usize,
database: Database,
) -> DatabaseConfig {
DatabaseConfig::Path {
path: base_path.join(DEFAULT_DB_CONFIG_PATH),
cache_size,
match database {
Database::RocksDb => DatabaseConfig::RocksDb {
path: base_path.join("db"),
cache_size,
},
Database::SubDb => DatabaseConfig::SubDb {
path: base_path.join("subdb"),
},
Database::ParityDb => DatabaseConfig::ParityDb {
path: base_path.join("paritydb"),
},
}
}
+1 -1
View File
@@ -466,7 +466,7 @@ impl<T: Clone> SlotDuration<T> {
cb(client.runtime_api(), &BlockId::number(Zero::zero()))?;
info!(
"⏱ Loaded block-time = {:?} milliseconds from genesis on first-launch",
" Loaded block-time = {:?} milliseconds from genesis on first-launch",
genesis_slot_duration
);
+4
View File
@@ -21,6 +21,7 @@ linked-hash-map = "0.5.2"
hash-db = "0.15.2"
parity-util-mem = { version = "0.6.0", default-features = false, features = ["std"] }
codec = { package = "parity-scale-codec", version = "1.3.0", features = ["derive"] }
blake2-rfc = "0.2.18"
sc-client-api = { version = "2.0.0-dev", path = "../api" }
sp-core = { version = "2.0.0-dev", path = "../../primitives/core" }
@@ -32,6 +33,9 @@ sc-state-db = { version = "0.8.0-dev", path = "../state-db" }
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 }
subdb = { git = "https://github.com/paritytech/subdb", optional = true }
prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.8.0-dev", path = "../../utils/prometheus" }
[dev-dependencies]
+24 -28
View File
@@ -18,17 +18,17 @@
use std::sync::Arc;
use kvdb::{KeyValueDB, DBTransaction};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use codec::{Encode, Decode};
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use crate::utils::{self, db_err, meta_keys};
use sp_database::{Database, Transaction};
use crate::utils::{self, meta_keys};
use crate::cache::{CacheItemT, ComplexBlockId};
use crate::cache::list_cache::{CommitOperation, Fork};
use crate::cache::list_entry::{Entry, StorageEntry};
use crate::DbHash;
/// Single list-cache metadata.
#[derive(Debug)]
@@ -97,19 +97,19 @@ pub struct DbColumns {
pub struct DbStorage {
name: Vec<u8>,
meta_key: Vec<u8>,
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
columns: DbColumns,
}
impl DbStorage {
/// Create new database-backed list cache storage.
pub fn new(name: Vec<u8>, db: Arc<dyn KeyValueDB>, columns: DbColumns) -> Self {
pub fn new(name: Vec<u8>, db: Arc<dyn Database<DbHash>>, columns: DbColumns) -> Self {
let meta_key = meta::key(&name);
DbStorage { name, meta_key, db, columns }
}
/// Get reference to the database.
pub fn db(&self) -> &Arc<dyn KeyValueDB> { &self.db }
pub fn db(&self) -> &Arc<dyn Database<DbHash>> { &self.db }
/// Get reference to the database columns.
pub fn columns(&self) -> &DbColumns { &self.columns }
@@ -135,49 +135,45 @@ impl<Block: BlockT, T: CacheItemT> Storage<Block, T> for DbStorage {
}
fn read_meta(&self) -> ClientResult<Metadata<Block>> {
self.db.get(self.columns.meta, &self.meta_key)
.map_err(db_err)
.and_then(|meta| match meta {
Some(meta) => meta::decode(&*meta),
None => Ok(Metadata {
finalized: None,
unfinalized: Vec::new(),
}),
match self.db.get(self.columns.meta, &self.meta_key) {
Some(meta) => meta::decode(&*meta),
None => Ok(Metadata {
finalized: None,
unfinalized: Vec::new(),
})
}
}
fn read_entry(&self, at: &ComplexBlockId<Block>) -> ClientResult<Option<StorageEntry<Block, T>>> {
self.db.get(self.columns.cache, &self.encode_block_id(at))
.map_err(db_err)
.and_then(|entry| match entry {
Some(entry) => StorageEntry::<Block, T>::decode(&mut &entry[..])
.map_err(|_| ClientError::Backend("Failed to decode cache entry".into()))
.map(Some),
None => Ok(None),
})
match self.db.get(self.columns.cache, &self.encode_block_id(at)) {
Some(entry) => StorageEntry::<Block, T>::decode(&mut &entry[..])
.map_err(|_| ClientError::Backend("Failed to decode cache entry".into()))
.map(Some),
None => Ok(None),
}
}
}
/// Database-backed list cache storage transaction.
pub struct DbStorageTransaction<'a> {
storage: &'a DbStorage,
tx: &'a mut DBTransaction,
tx: &'a mut Transaction<DbHash>,
}
impl<'a> DbStorageTransaction<'a> {
/// Create new database transaction.
pub fn new(storage: &'a DbStorage, tx: &'a mut DBTransaction) -> Self {
pub fn new(storage: &'a DbStorage, tx: &'a mut Transaction<DbHash>) -> Self {
DbStorageTransaction { storage, tx }
}
}
impl<'a, Block: BlockT, T: CacheItemT> StorageTransaction<Block, T> for DbStorageTransaction<'a> {
fn insert_storage_entry(&mut self, at: &ComplexBlockId<Block>, entry: &StorageEntry<Block, T>) {
self.tx.put(self.storage.columns.cache, &self.storage.encode_block_id(at), &entry.encode());
self.tx.set_from_vec(self.storage.columns.cache, &self.storage.encode_block_id(at), entry.encode());
}
fn remove_storage_entry(&mut self, at: &ComplexBlockId<Block>) {
self.tx.delete(self.storage.columns.cache, &self.storage.encode_block_id(at));
self.tx.remove(self.storage.columns.cache, &self.storage.encode_block_id(at));
}
fn update_meta(
@@ -186,10 +182,10 @@ impl<'a, Block: BlockT, T: CacheItemT> StorageTransaction<Block, T> for DbStorag
unfinalized: &[Fork<Block, T>],
operation: &CommitOperation<Block, T>,
) {
self.tx.put(
self.tx.set_from_vec(
self.storage.columns.meta,
&self.storage.meta_key,
&meta::encode(best_finalized_entry, unfinalized, operation));
meta::encode(best_finalized_entry, unfinalized, operation));
}
}
+11 -11
View File
@@ -19,14 +19,14 @@
use std::{sync::Arc, collections::{HashMap, hash_map::Entry}};
use parking_lot::RwLock;
use kvdb::{KeyValueDB, DBTransaction};
use sc_client_api::blockchain::{well_known_cache_keys::{self, Id as CacheKeyId}, Cache as BlockchainCache};
use sp_blockchain::Result as ClientResult;
use sp_database::{Database, Transaction};
use codec::{Encode, Decode};
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
use crate::utils::{self, COLUMN_META, db_err};
use crate::utils::{self, COLUMN_META};
use crate::DbHash;
use self::list_cache::{ListCache, PruningStrategy};
@@ -78,7 +78,7 @@ impl<T> CacheItemT for T where T: Clone + Decode + Encode + PartialEq {}
/// Database-backed blockchain data cache.
pub struct DbCache<Block: BlockT> {
cache_at: HashMap<CacheKeyId, ListCache<Block, Vec<u8>, self::list_storage::DbStorage>>,
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
key_lookup_column: u32,
header_column: u32,
cache_column: u32,
@@ -89,7 +89,7 @@ pub struct DbCache<Block: BlockT> {
impl<Block: BlockT> DbCache<Block> {
/// Create new cache.
pub fn new(
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
key_lookup_column: u32,
header_column: u32,
cache_column: u32,
@@ -113,7 +113,7 @@ impl<Block: BlockT> DbCache<Block> {
}
/// Begin cache transaction.
pub fn transaction<'a>(&'a mut self, tx: &'a mut DBTransaction) -> DbCacheTransaction<'a, Block> {
pub fn transaction<'a>(&'a mut self, tx: &'a mut Transaction<DbHash>) -> DbCacheTransaction<'a, Block> {
DbCacheTransaction {
cache: self,
tx,
@@ -125,7 +125,7 @@ impl<Block: BlockT> DbCache<Block> {
/// Begin cache transaction with given ops.
pub fn transaction_with_ops<'a>(
&'a mut self,
tx: &'a mut DBTransaction,
tx: &'a mut Transaction<DbHash>,
ops: DbCacheTransactionOps<Block>,
) -> DbCacheTransaction<'a, Block> {
DbCacheTransaction {
@@ -169,7 +169,7 @@ impl<Block: BlockT> DbCache<Block> {
fn get_cache_helper<'a, Block: BlockT>(
cache_at: &'a mut HashMap<CacheKeyId, ListCache<Block, Vec<u8>, self::list_storage::DbStorage>>,
name: CacheKeyId,
db: &Arc<dyn KeyValueDB>,
db: &Arc<dyn Database<DbHash>>,
key_lookup: u32,
header: u32,
cache: u32,
@@ -215,7 +215,7 @@ impl<Block: BlockT> DbCacheTransactionOps<Block> {
/// Database-backed blockchain data cache transaction valid for single block import.
pub struct DbCacheTransaction<'a, Block: BlockT> {
cache: &'a mut DbCache<Block>,
tx: &'a mut DBTransaction,
tx: &'a mut Transaction<DbHash>,
cache_at_ops: HashMap<CacheKeyId, self::list_cache::CommitOperations<Block, Vec<u8>>>,
best_finalized_block: Option<ComplexBlockId<Block>>,
}
@@ -328,7 +328,7 @@ impl<Block: BlockT> BlockchainCache<Block> for DbCacheSync<Block> {
let genesis_hash = cache.genesis_hash;
let cache_contents = vec![(*key, data)].into_iter().collect();
let db = cache.db.clone();
let mut dbtx = DBTransaction::new();
let mut dbtx = Transaction::new();
let tx = cache.transaction(&mut dbtx);
let tx = tx.on_block_insert(
ComplexBlockId::new(Default::default(), Zero::zero()),
@@ -337,7 +337,7 @@ impl<Block: BlockT> BlockchainCache<Block> for DbCacheSync<Block> {
EntryType::Genesis,
)?;
let tx_ops = tx.into_ops();
db.write(dbtx).map_err(db_err)?;
db.commit(dbtx);
cache.commit(tx_ops)?;
Ok(())
}
@@ -19,7 +19,6 @@
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use hash_db::Prefix;
use kvdb::{KeyValueDB, DBTransaction};
use codec::{Decode, Encode};
use parking_lot::RwLock;
use sp_blockchain::{Error as ClientError, Result as ClientResult};
@@ -27,12 +26,14 @@ use sp_trie::MemoryDB;
use sc_client_api::backend::PrunableStateChangesTrieStorage;
use sp_blockchain::{well_known_cache_keys, Cache as BlockchainCache};
use sp_core::{ChangesTrieConfiguration, ChangesTrieConfigurationRange, convert_hash};
use sp_database::Transaction;
use sp_runtime::traits::{
Block as BlockT, Header as HeaderT, HashFor, NumberFor, One, Zero, CheckedSub,
};
use sp_runtime::generic::{BlockId, DigestItem, ChangesTrieSignal};
use sp_state_machine::{DBValue, ChangesTrieBuildCache, ChangesTrieCacheAction};
use crate::utils::{self, Meta, meta_keys, db_err};
use sp_state_machine::{ChangesTrieBuildCache, ChangesTrieCacheAction};
use crate::{Database, DbHash};
use crate::utils::{self, Meta, meta_keys};
use crate::cache::{
DbCacheSync, DbCache, DbCacheTransactionOps,
ComplexBlockId, EntryType as CacheEntryType,
@@ -76,7 +77,7 @@ impl<Block: BlockT> From<DbCacheTransactionOps<Block>> for DbChangesTrieStorageT
/// Stores all tries in separate DB column.
/// Lock order: meta, tries_meta, cache, build_cache.
pub struct DbChangesTrieStorage<Block: BlockT> {
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
meta_column: u32,
changes_tries_column: u32,
key_lookup_column: u32,
@@ -111,7 +112,7 @@ struct ChangesTriesMeta<Block: BlockT> {
impl<Block: BlockT> DbChangesTrieStorage<Block> {
/// Create new changes trie storage.
pub fn new(
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
meta_column: u32,
changes_tries_column: u32,
key_lookup_column: u32,
@@ -149,7 +150,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
/// Commit new changes trie.
pub fn commit(
&self,
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
mut changes_trie: MemoryDB<HashFor<Block>>,
parent_block: ComplexBlockId<Block>,
block: ComplexBlockId<Block>,
@@ -160,7 +161,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
) -> ClientResult<DbChangesTrieStorageTransaction<Block>> {
// insert changes trie, associated with block, into DB
for (key, (val, _)) in changes_trie.drain() {
tx.put(self.changes_tries_column, key.as_ref(), &val);
tx.set(self.changes_tries_column, key.as_ref(), &val);
}
// if configuration has not been changed AND block is not finalized => nothing to do here
@@ -205,7 +206,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
/// Called when block is finalized.
pub fn finalize(
&self,
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
parent_block_hash: Block::Hash,
block_hash: Block::Hash,
block_num: NumberFor<Block>,
@@ -254,7 +255,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
/// When block is reverted.
pub fn revert(
&self,
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
block: &ComplexBlockId<Block>,
) -> ClientResult<DbChangesTrieStorageTransaction<Block>> {
Ok(self.cache.0.write().transaction(tx)
@@ -280,7 +281,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
/// Prune obsolete changes tries.
fn prune(
&self,
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
block_hash: Block::Hash,
block_num: NumberFor<Block>,
new_header: Option<&Block::Header>,
@@ -313,7 +314,7 @@ impl<Block: BlockT> DbChangesTrieStorage<Block> {
hash: convert_hash(&block_hash),
number: block_num,
},
|node| tx.delete(self.changes_tries_column, node.as_ref()),
|node| tx.remove(self.changes_tries_column, node.as_ref()),
);
next_digest_range_start = end + One::one();
@@ -486,18 +487,17 @@ where
self.build_cache.read().with_changed_keys(root, functor)
}
fn get(&self, key: &Block::Hash, _prefix: Prefix) -> Result<Option<DBValue>, String> {
self.db.get(self.changes_tries_column, key.as_ref())
.map_err(|err| format!("{}", err))
fn get(&self, key: &Block::Hash, _prefix: Prefix) -> Result<Option<Vec<u8>>, String> {
Ok(self.db.get(self.changes_tries_column, key.as_ref()))
}
}
/// Read changes tries metadata from database.
fn read_tries_meta<Block: BlockT>(
db: &dyn KeyValueDB,
db: &dyn Database<DbHash>,
meta_column: u32,
) -> ClientResult<ChangesTriesMeta<Block>> {
match db.get(meta_column, meta_keys::CHANGES_TRIES_META).map_err(db_err)? {
match db.get(meta_column, meta_keys::CHANGES_TRIES_META) {
Some(h) => match Decode::decode(&mut &h[..]) {
Ok(h) => Ok(h),
Err(err) => Err(ClientError::Backend(format!("Error decoding changes tries metadata: {}", err))),
@@ -511,11 +511,11 @@ fn read_tries_meta<Block: BlockT>(
/// Write changes tries metadata from database.
fn write_tries_meta<Block: BlockT>(
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
meta_column: u32,
meta: &ChangesTriesMeta<Block>,
) {
tx.put(meta_column, meta_keys::CHANGES_TRIES_META, &meta.encode());
tx.set_from_vec(meta_column, meta_keys::CHANGES_TRIES_META, meta.encode());
}
#[cfg(test)]
@@ -707,7 +707,7 @@ mod tests {
let finalize_block = |number| {
let header = backend.blockchain().header(BlockId::Number(number)).unwrap().unwrap();
let mut tx = DBTransaction::new();
let mut tx = Transaction::new();
let cache_ops = backend.changes_tries_storage.finalize(
&mut tx,
*header.parent_hash(),
@@ -716,7 +716,7 @@ mod tests {
None,
None,
).unwrap();
backend.storage.db.write(tx).unwrap();
backend.storage.db.commit(tx);
backend.changes_tries_storage.post_commit(Some(cache_ops));
};
+17 -18
View File
@@ -16,23 +16,21 @@
//! Functionality for reading and storing children hashes from db.
use kvdb::{KeyValueDB, DBTransaction};
use codec::{Encode, Decode};
use sp_blockchain;
use std::hash::Hash;
use sp_database::{Database, Transaction};
use crate::DbHash;
/// Returns the hashes of the children blocks of the block with `parent_hash`.
pub fn read_children<
K: Eq + Hash + Clone + Encode + Decode,
V: Eq + Hash + Clone + Encode + Decode,
>(db: &dyn KeyValueDB, column: u32, prefix: &[u8], parent_hash: K) -> sp_blockchain::Result<Vec<V>> {
>(db: &dyn Database<DbHash>, column: u32, prefix: &[u8], parent_hash: K) -> sp_blockchain::Result<Vec<V>> {
let mut buf = prefix.to_vec();
parent_hash.using_encoded(|s| buf.extend(s));
let raw_val_opt = match db.get(column, &buf[..]) {
Ok(raw_val_opt) => raw_val_opt,
Err(_) => return Err(sp_blockchain::Error::Backend("Error reading value from database".into())),
};
let raw_val_opt = db.get(column, &buf[..]);
let raw_val = match raw_val_opt {
Some(val) => val,
@@ -53,7 +51,7 @@ pub fn write_children<
K: Eq + Hash + Clone + Encode + Decode,
V: Eq + Hash + Clone + Encode + Decode,
>(
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
column: u32,
prefix: &[u8],
parent_hash: K,
@@ -61,34 +59,35 @@ pub fn write_children<
) {
let mut key = prefix.to_vec();
parent_hash.using_encoded(|s| key.extend(s));
tx.put_vec(column, &key[..], children_hashes.encode());
tx.set_from_vec(column, &key[..], children_hashes.encode());
}
/// Prepare transaction to remove the children of `parent_hash`.
pub fn remove_children<
K: Eq + Hash + Clone + Encode + Decode,
>(
tx: &mut DBTransaction,
tx: &mut Transaction<DbHash>,
column: u32,
prefix: &[u8],
parent_hash: K,
) {
let mut key = prefix.to_vec();
parent_hash.using_encoded(|s| key.extend(s));
tx.delete(column, &key[..]);
tx.remove(column, &key);
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn children_write_read_remove() {
const PREFIX: &[u8] = b"children";
let db = ::kvdb_memorydb::create(1);
let db = Arc::new(sp_database::MemDb::default());
let mut tx = DBTransaction::new();
let mut tx = Transaction::new();
let mut children1 = Vec::new();
children1.push(1_3);
@@ -100,19 +99,19 @@ mod tests {
children2.push(1_6);
write_children(&mut tx, 0, PREFIX, 1_2, children2);
db.write(tx.clone()).expect("(2) Committing transaction failed");
db.commit(tx.clone());
let r1: Vec<u32> = read_children(&db, 0, PREFIX, 1_1).expect("(1) Getting r1 failed");
let r2: Vec<u32> = read_children(&db, 0, PREFIX, 1_2).expect("(1) Getting r2 failed");
let r1: Vec<u32> = read_children(&*db, 0, PREFIX, 1_1).expect("(1) Getting r1 failed");
let r2: Vec<u32> = read_children(&*db, 0, PREFIX, 1_2).expect("(1) Getting r2 failed");
assert_eq!(r1, vec![1_3, 1_5]);
assert_eq!(r2, vec![1_4, 1_6]);
remove_children(&mut tx, 0, PREFIX, 1_2);
db.write(tx).expect("(2) Committing transaction failed");
db.commit(tx);
let r1: Vec<u32> = read_children(&db, 0, PREFIX, 1_1).expect("(2) Getting r1 failed");
let r2: Vec<u32> = read_children(&db, 0, PREFIX, 1_2).expect("(2) Getting r2 failed");
let r1: Vec<u32> = read_children(&*db, 0, PREFIX, 1_1).expect("(2) Getting r1 failed");
let r2: Vec<u32> = read_children(&*db, 0, PREFIX, 1_2).expect("(2) Getting r2 failed");
assert_eq!(r1, vec![1_3, 1_5]);
assert_eq!(r2.len(), 0);
+112 -162
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Client backend that uses RocksDB database as storage.
//! Client backend that is backed by a database.
//!
//! # Canonicality vs. Finality
//!
@@ -40,9 +40,13 @@ mod storage_cache;
mod upgrade;
mod utils;
mod stats;
#[cfg(feature = "parity-db")]
mod parity_db;
#[cfg(feature = "subdb")]
mod subdb;
use std::sync::Arc;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::io;
use std::collections::HashMap;
@@ -57,8 +61,8 @@ use sp_blockchain::{
};
use codec::{Decode, Encode};
use hash_db::Prefix;
use kvdb::{KeyValueDB, DBTransaction};
use sp_trie::{MemoryDB, PrefixedMemoryDB, prefixed_key};
use sp_database::Transaction;
use parking_lot::RwLock;
use sp_core::{ChangesTrieConfiguration, traits::CodeExecutor};
use sp_core::storage::{well_known_keys, ChildInfo};
@@ -75,7 +79,7 @@ use sp_state_machine::{
StorageCollection, ChildStorageCollection,
backend::Backend as StateBackend, StateMachineStats,
};
use crate::utils::{DatabaseType, Meta, db_err, meta_keys, read_db, read_meta};
use crate::utils::{DatabaseType, Meta, meta_keys, read_db, read_meta};
use crate::changes_tries_storage::{DbChangesTrieStorage, DbChangesTrieStorageTransaction};
use sc_client::leaves::{LeafSet, FinalizationDisplaced};
use sc_state_db::StateDb;
@@ -83,15 +87,15 @@ use sp_blockchain::{CachedHeaderMetadata, HeaderMetadata, HeaderMetadataCache};
use crate::storage_cache::{CachingState, SyncingCachingState, SharedCache, new_shared_cache};
use crate::stats::StateUsageStats;
use log::{trace, debug, warn};
pub use sc_state_db::PruningMode;
use prometheus_endpoint::Registry;
// Re-export the Database trait so that one can pass an implementation of it.
pub use sp_database::Database;
pub use sc_state_db::PruningMode;
#[cfg(any(feature = "kvdb-rocksdb", test))]
pub use bench::BenchmarkingState;
#[cfg(feature = "test-helpers")]
use sc_client::in_mem::Backend as InMemoryBackend;
const CANONICALIZATION_DELAY: u64 = 4096;
const MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR: u32 = 32768;
@@ -103,8 +107,8 @@ pub type DbState<B> = sp_state_machine::TrieBackend<
Arc<dyn sp_state_machine::Storage<HashFor<B>>>, HashFor<B>
>;
/// Re-export the KVDB trait so that one can pass an implementation of it.
pub use kvdb;
/// Hash type that this backend uses for the database.
pub type DbHash = [u8; 32];
/// A reference tracking state.
///
@@ -279,17 +283,42 @@ pub struct DatabaseSettings {
}
/// Where to find the database..
#[derive(Clone)]
pub enum DatabaseSettingsSrc {
/// Load a database from a given path. Recommended for most uses.
Path {
/// Load a RocksDB database from a given path. Recommended for most uses.
RocksDb {
/// Path to the database.
path: PathBuf,
/// Cache size in MiB.
cache_size: usize,
},
/// Load a ParityDb database from a given path.
ParityDb {
/// Path to the database.
path: PathBuf,
},
/// Load a Subdb database from a given path.
SubDb {
/// Path to the database.
path: PathBuf,
},
/// Use a custom already-open database.
Custom(Arc<dyn KeyValueDB>),
Custom(Arc<dyn Database<DbHash>>),
}
impl DatabaseSettingsSrc {
/// Return dabase path for databases that are on the disk.
pub fn path(&self) -> Option<&Path> {
match self {
DatabaseSettingsSrc::RocksDb { path, .. } => Some(path.as_path()),
DatabaseSettingsSrc::ParityDb { path, .. } => Some(path.as_path()),
DatabaseSettingsSrc::SubDb { path, .. } => Some(path.as_path()),
DatabaseSettingsSrc::Custom(_) => None,
}
}
}
/// Create an instance of db-backed client.
@@ -357,26 +386,26 @@ struct PendingBlock<Block: BlockT> {
}
// wrapper that implements trait required for state_db
struct StateMetaDb<'a>(&'a dyn KeyValueDB);
struct StateMetaDb<'a>(&'a dyn Database<DbHash>);
impl<'a> sc_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()))
Ok(self.0.get(columns::STATE_META, key))
}
}
/// Block database
pub struct BlockchainDb<Block: BlockT> {
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
meta: Arc<RwLock<Meta<NumberFor<Block>, Block::Hash>>>,
leaves: RwLock<LeafSet<Block::Hash, NumberFor<Block>>>,
header_metadata_cache: HeaderMetadataCache<Block>,
}
impl<Block: BlockT> BlockchainDb<Block> {
fn new(db: Arc<dyn KeyValueDB>) -> ClientResult<Self> {
fn new(db: Arc<dyn Database<DbHash>>) -> ClientResult<Self> {
let meta = read_meta::<Block>(&*db, columns::HEADER)?;
let leaves = LeafSet::read_from_db(&*db, columns::META, meta_keys::LEAF_PREFIX)?;
Ok(BlockchainDb {
@@ -547,11 +576,11 @@ pub struct BlockImportOperation<Block: BlockT> {
}
impl<Block: BlockT> BlockImportOperation<Block> {
fn apply_aux(&mut self, transaction: &mut DBTransaction) {
fn apply_aux(&mut self, transaction: &mut Transaction<DbHash>) {
for (key, maybe_val) in self.aux_ops.drain(..) {
match maybe_val {
Some(val) => transaction.put_vec(columns::AUX, &key, val),
None => transaction.delete(columns::AUX, &key),
Some(val) => transaction.set_from_vec(columns::AUX, &key, val),
None => transaction.remove(columns::AUX, &key),
}
}
}
@@ -676,7 +705,7 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block> for Bloc
}
struct StorageDb<Block: BlockT> {
pub db: Arc<dyn KeyValueDB>,
pub db: Arc<dyn Database<DbHash>>,
pub state_db: StateDb<Block::Hash, Vec<u8>>,
}
@@ -693,7 +722,7 @@ impl<Block: BlockT> sc_state_db::NodeDb for StorageDb<Block> {
type Key = [u8];
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
self.db.get(columns::STATE, key).map(|r| r.map(|v| v.to_vec()))
Ok(self.db.get(columns::STATE, key))
}
}
@@ -776,13 +805,14 @@ impl<Block: BlockT> Backend<Block> {
/// The pruning window is how old a block must be before the state is pruned.
pub fn new(config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult<Self> {
let db = crate::utils::open_database::<Block>(&config, DatabaseType::Full)?;
Self::from_kvdb(db as Arc<_>, canonicalization_delay, &config)
Self::from_database(db as Arc<_>, canonicalization_delay, &config)
}
/// Create new memory-backed client backend for tests.
#[cfg(any(test, feature = "test-helpers"))]
pub fn new_test(keep_blocks: u32, canonicalization_delay: u64) -> Self {
let db = Arc::new(kvdb_memorydb::create(crate::utils::NUM_COLUMNS));
let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS);
let db = sp_database::as_database(db);
let db_setting = DatabaseSettings {
state_cache_size: 16777216,
state_cache_child_ratio: Some((50, 100)),
@@ -793,8 +823,8 @@ impl<Block: BlockT> Backend<Block> {
Self::new(db_setting, canonicalization_delay).expect("failed to create test-db")
}
fn from_kvdb(
db: Arc<dyn KeyValueDB>,
fn from_database(
db: Arc<dyn Database<DbHash>>,
canonicalization_delay: u64,
config: &DatabaseSettings,
) -> ClientResult<Self> {
@@ -843,61 +873,6 @@ impl<Block: BlockT> Backend<Block> {
})
}
/// Returns in-memory blockchain that contains the same set of blocks as self.
#[cfg(feature = "test-helpers")]
pub fn as_in_memory(&self) -> InMemoryBackend<Block> {
use sc_client_api::backend::{Backend as ClientBackend, BlockImportOperation};
use sc_client::blockchain::Backend as BlockchainBackend;
let inmem = InMemoryBackend::<Block>::new();
// get all headers hashes && sort them by number (could be duplicate)
let mut headers: Vec<(NumberFor<Block>, Block::Hash, Block::Header)> = Vec::new();
for (_, header) in self.blockchain.db.iter(columns::HEADER) {
let header = Block::Header::decode(&mut &header[..]).unwrap();
let hash = header.hash();
let number = *header.number();
let pos = headers.binary_search_by(|item| item.0.cmp(&number));
match pos {
Ok(pos) => headers.insert(pos, (number, hash, header)),
Err(pos) => headers.insert(pos, (number, hash, header)),
}
}
// insert all other headers + bodies + justifications
let info = self.blockchain.info();
for (number, hash, header) in headers {
let id = BlockId::Hash(hash);
let justification = self.blockchain.justification(id).unwrap();
let body = self.blockchain.body(id).unwrap();
let state = self.state_at(id).unwrap().pairs();
let new_block_state = if number.is_zero() {
NewBlockState::Final
} else if hash == info.best_hash {
NewBlockState::Best
} else {
NewBlockState::Normal
};
let mut op = inmem.begin_operation().unwrap();
op.set_block_data(header, body, justification, new_block_state).unwrap();
op.update_db_storage(vec![(None, state.into_iter().map(|(k, v)| (k, Some(v))).collect())])
.unwrap();
inmem.commit_operation(op).unwrap();
}
// and now finalize the best block we have
inmem.finalize_block(BlockId::Hash(info.finalized_hash), None).unwrap();
inmem
}
/// Returns total number of blocks (headers) in the block DB.
#[cfg(feature = "test-helpers")]
pub fn blocks_count(&self) -> u64 {
self.blockchain.db.iter(columns::HEADER).count() as u64
}
/// Handle setting head within a transaction. `route_to` should be the last
/// block that existed in the database. `best_to` should be the best block
/// to be set.
@@ -907,7 +882,7 @@ impl<Block: BlockT> Backend<Block> {
/// to be best, `route_to` should equal to `best_to`.
fn set_head_with_transaction(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
route_to: Block::Hash,
best_to: (NumberFor<Block>, Block::Hash),
) -> ClientResult<(Vec<Block::Hash>, Vec<Block::Hash>)> {
@@ -957,7 +932,7 @@ impl<Block: BlockT> Backend<Block> {
}
let lookup_key = utils::number_and_hash_to_lookup_key(best_to.0, &best_to.1)?;
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, lookup_key);
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
@@ -984,7 +959,7 @@ impl<Block: BlockT> Backend<Block> {
fn finalize_block_with_transaction(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
hash: &Block::Hash,
header: &Block::Header,
last_finalized: Option<Block::Hash>,
@@ -1005,10 +980,10 @@ impl<Block: BlockT> Backend<Block> {
)?;
if let Some(justification) = justification {
transaction.put(
transaction.set_from_vec(
columns::JUSTIFICATION,
&utils::number_and_hash_to_lookup_key(number, hash)?,
&justification.encode(),
justification.encode(),
);
}
Ok((*hash, number, false, true))
@@ -1017,7 +992,7 @@ impl<Block: BlockT> Backend<Block> {
// performs forced canonicalization with a delay after importing a non-finalized block.
fn force_delayed_canonicalize(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
hash: Block::Hash,
number: NumberFor<Block>,
)
@@ -1051,7 +1026,7 @@ impl<Block: BlockT> Backend<Block> {
fn try_commit_operation(&self, mut operation: BlockImportOperation<Block>)
-> ClientResult<()>
{
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
let mut finalization_displaced_leaves = None;
operation.apply_aux(&mut transaction);
@@ -1103,17 +1078,17 @@ impl<Block: BlockT> Backend<Block> {
header_metadata,
);
transaction.put(columns::HEADER, &lookup_key, &pending_block.header.encode());
transaction.set_from_vec(columns::HEADER, &lookup_key, pending_block.header.encode());
if let Some(body) = &pending_block.body {
transaction.put(columns::BODY, &lookup_key, &body.encode());
transaction.set_from_vec(columns::BODY, &lookup_key, body.encode());
}
if let Some(justification) = pending_block.justification {
transaction.put(columns::JUSTIFICATION, &lookup_key, &justification.encode());
transaction.set_from_vec(columns::JUSTIFICATION, &lookup_key, justification.encode());
}
if number.is_zero() {
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
transaction.put(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key);
transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
// for tests, because config is set from within the reset_storage
if operation.changes_trie_config_update.is_none() {
@@ -1260,31 +1235,17 @@ impl<Block: BlockT> Backend<Block> {
None
};
let write_result = self.storage.db.write(transaction).map_err(db_err);
self.storage.db.commit(transaction);
if let Some((
number,
hash,
enacted,
retracted,
displaced_leaf,
_displaced_leaf,
is_best,
mut cache,
)) = imported {
if let Err(e) = write_result {
let mut leaves = self.blockchain.leaves.write();
let mut undo = leaves.undo();
if let Some(displaced_leaf) = displaced_leaf {
undo.undo_import(displaced_leaf);
}
if let Some(finalization_displaced) = finalization_displaced_leaves {
undo.undo_finalization(finalization_displaced);
}
return Err(e)
}
cache.sync_cache(
&enacted,
&retracted,
@@ -1317,7 +1278,7 @@ impl<Block: BlockT> Backend<Block> {
// was not a child of the last finalized block.
fn note_finalized(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
is_inserted: bool,
f_header: &Block::Header,
f_hash: Block::Hash,
@@ -1328,7 +1289,7 @@ impl<Block: BlockT> Backend<Block> {
if self.storage.state_db.best_canonical().map(|c| f_num.saturated_into::<u64>() > c).unwrap_or(true) {
let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash.clone())?;
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key);
let commit = self.storage.state_db.canonicalize_block(&f_hash)
.map_err(|e: sc_state_db::Error<io::Error>| sp_blockchain::Error::from(format!("State database error: {:?}", e)))?;
@@ -1357,18 +1318,18 @@ impl<Block: BlockT> Backend<Block> {
}
}
fn apply_state_commit(transaction: &mut DBTransaction, commit: sc_state_db::CommitSet<Vec<u8>>) {
fn apply_state_commit(transaction: &mut Transaction<DbHash>, commit: sc_state_db::CommitSet<Vec<u8>>) {
for (key, val) in commit.data.inserted.into_iter() {
transaction.put(columns::STATE, &key[..], &val);
transaction.set_from_vec(columns::STATE, &key[..], val);
}
for key in commit.data.deleted.into_iter() {
transaction.delete(columns::STATE, &key[..]);
transaction.remove(columns::STATE, &key[..]);
}
for (key, val) in commit.meta.inserted.into_iter() {
transaction.put(columns::STATE_META, &key[..], &val);
transaction.set_from_vec(columns::STATE_META, &key[..], val);
}
for key in commit.meta.deleted.into_iter() {
transaction.delete(columns::STATE_META, &key[..]);
transaction.remove(columns::STATE_META, &key[..]);
}
}
@@ -1380,19 +1341,19 @@ impl<Block> sc_client_api::backend::AuxStore for Backend<Block> where Block: Blo
I: IntoIterator<Item=&'a(&'c [u8], &'c [u8])>,
D: IntoIterator<Item=&'a &'b [u8]>,
>(&self, insert: I, delete: D) -> ClientResult<()> {
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
for (k, v) in insert {
transaction.put(columns::AUX, k, v);
transaction.set(columns::AUX, k, v);
}
for k in delete {
transaction.delete(columns::AUX, k);
transaction.remove(columns::AUX, k);
}
self.storage.db.write(transaction).map_err(db_err)?;
self.storage.db.commit(transaction);
Ok(())
}
fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
Ok(self.storage.db.get(columns::AUX, key).map(|r| r.map(|v| v.to_vec())).map_err(db_err)?)
Ok(self.storage.db.get(columns::AUX, key))
}
}
@@ -1453,36 +1414,24 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
fn finalize_block(&self, block: BlockId<Block>, justification: Option<Justification>)
-> ClientResult<()>
{
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
let hash = self.blockchain.expect_block_hash_from_id(&block)?;
let header = self.blockchain.expect_header(block)?;
let mut displaced = None;
let commit = |displaced| {
let mut changes_trie_cache_ops = None;
let (hash, number, is_best, is_finalized) = self.finalize_block_with_transaction(
&mut transaction,
&hash,
&header,
None,
justification,
&mut changes_trie_cache_ops,
displaced,
)?;
self.storage.db.write(transaction).map_err(db_err)?;
self.blockchain.update_meta(hash, number, is_best, is_finalized);
self.changes_tries_storage.post_commit(changes_trie_cache_ops);
Ok(())
};
match commit(&mut displaced) {
Ok(()) => self.storage.state_db.apply_pending(),
e @ Err(_) => {
self.storage.state_db.revert_pending();
if let Some(displaced) = displaced {
self.blockchain.leaves.write().undo().undo_finalization(displaced);
}
return e;
}
}
let mut changes_trie_cache_ops = None;
let (hash, number, is_best, is_finalized) = self.finalize_block_with_transaction(
&mut transaction,
&hash,
&header,
None,
justification,
&mut changes_trie_cache_ops,
&mut displaced,
)?;
self.storage.db.commit(transaction);
self.blockchain.update_meta(hash, number, is_best, is_finalized);
self.changes_tries_storage.post_commit(changes_trie_cache_ops);
Ok(())
}
@@ -1497,11 +1446,12 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
fn usage_info(&self) -> Option<UsageInfo> {
let (io_stats, state_stats) = self.io_stats.take_or_else(||
(
self.storage.db.io_stats(kvdb::IoStatsKind::SincePrevious),
// TODO: implement DB stats and cache size retrieval
kvdb::IoStats::empty(),
self.state_usage.take(),
)
);
let database_cache = MemorySize::from_bytes(parity_util_mem::malloc_size(&*self.storage.db));
let database_cache = MemorySize::from_bytes(0);
let state_cache = MemorySize::from_bytes(
(*&self.shared_cache).lock().used_storage_cache_size(),
);
@@ -1547,7 +1497,7 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
if best_number.is_zero() {
return Ok(c.saturated_into::<NumberFor<Block>>())
}
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
match self.storage.state_db.revert_one() {
Some(commit) => {
apply_state_commit(&mut transaction, commit);
@@ -1571,13 +1521,13 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
removed_number,
),
)?;
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
if update_finalized {
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &key);
transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, key.clone());
}
transaction.delete(columns::KEY_LOOKUP, removed.hash().as_ref());
transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, key);
transaction.remove(columns::KEY_LOOKUP, removed.hash().as_ref());
children::remove_children(&mut transaction, columns::META, meta_keys::CHILDREN_PREFIX, best_hash);
self.storage.db.write(transaction).map_err(db_err)?;
self.storage.db.commit(transaction);
self.changes_tries_storage.post_commit(Some(changes_trie_cache_ops));
self.blockchain.update_meta(best_hash, best_number, true, update_finalized);
}
@@ -1591,12 +1541,12 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
let reverted = revert_blocks()?;
let revert_leaves = || -> ClientResult<()> {
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
let mut leaves = self.blockchain.leaves.write();
leaves.revert(best_hash, best_number);
leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX);
self.storage.db.write(transaction).map_err(db_err)?;
self.storage.db.commit(transaction);
Ok(())
};
@@ -1959,7 +1909,7 @@ pub(crate) mod tests {
assert_eq!(backend.storage.db.get(
columns::STATE,
&sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX)
).unwrap().unwrap(), &b"hello"[..]);
).unwrap(), &b"hello"[..]);
hash
};
@@ -1996,7 +1946,7 @@ pub(crate) mod tests {
assert_eq!(backend.storage.db.get(
columns::STATE,
&sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX)
).unwrap().unwrap(), &b"hello"[..]);
).unwrap(), &b"hello"[..]);
hash
};
@@ -2034,7 +1984,7 @@ pub(crate) mod tests {
assert!(backend.storage.db.get(
columns::STATE,
&sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX)
).unwrap().is_some());
).is_some());
hash
};
@@ -2068,7 +2018,7 @@ pub(crate) mod tests {
assert!(backend.storage.db.get(
columns::STATE,
&sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX)
).unwrap().is_none());
).is_none());
}
backend.finalize_block(BlockId::Number(1), None).unwrap();
@@ -2077,7 +2027,7 @@ pub(crate) mod tests {
assert!(backend.storage.db.get(
columns::STATE,
&sp_trie::prefixed_key::<BlakeTwo256>(&key, EMPTY_PREFIX)
).unwrap().is_none());
).is_none());
}
#[test]
+58 -56
View File
@@ -20,8 +20,6 @@ use std::{sync::Arc, collections::HashMap};
use std::convert::TryInto;
use parking_lot::RwLock;
use kvdb::{KeyValueDB, DBTransaction};
use sc_client_api::{backend::{AuxStore, NewBlockState}, UsageInfo};
use sc_client::blockchain::{
BlockStatus, Cache as BlockchainCache,Info as BlockchainInfo,
@@ -33,13 +31,14 @@ use sp_blockchain::{
HeaderBackend as BlockchainHeaderBackend,
well_known_cache_keys,
};
use sp_database::{Database, Transaction};
use sc_client::light::blockchain::Storage as LightBlockchainStorage;
use codec::{Decode, Encode};
use sp_runtime::generic::{DigestItem, BlockId};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Zero, One, NumberFor, HashFor};
use crate::cache::{DbCacheSync, DbCache, ComplexBlockId, EntryType as CacheEntryType};
use crate::utils::{self, meta_keys, DatabaseType, Meta, db_err, read_db, block_id_to_lookup_key, read_meta};
use crate::{DatabaseSettings, FrozenForDuration};
use crate::utils::{self, meta_keys, DatabaseType, Meta, read_db, block_id_to_lookup_key, read_meta};
use crate::{DatabaseSettings, FrozenForDuration, DbHash};
use log::{trace, warn, debug};
pub(crate) mod columns {
@@ -59,7 +58,7 @@ const CHANGES_TRIE_CHT_PREFIX: u8 = 1;
/// Light blockchain storage. Stores most recent headers + CHTs for older headers.
/// Locks order: meta, cache.
pub struct LightStorage<Block: BlockT> {
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
meta: RwLock<Meta<NumberFor<Block>, Block::Hash>>,
cache: Arc<DbCacheSync<Block>>,
header_metadata_cache: HeaderMetadataCache<Block>,
@@ -78,14 +77,11 @@ impl<Block: BlockT> LightStorage<Block> {
/// Create new memory-backed `LightStorage` for tests.
#[cfg(any(test, feature = "test-helpers"))]
pub fn new_test() -> Self {
use utils::NUM_COLUMNS;
let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS));
let db = Arc::new(sp_database::MemDb::default());
Self::from_kvdb(db as Arc<_>).expect("failed to create test-db")
}
fn from_kvdb(db: Arc<dyn KeyValueDB>) -> ClientResult<Self> {
fn from_kvdb(db: Arc<dyn Database<DbHash>>) -> ClientResult<Self> {
let meta = read_meta::<Block>(&*db, columns::HEADER)?;
let cache = DbCache::new(
db.clone(),
@@ -230,7 +226,7 @@ impl<Block: BlockT> LightStorage<Block> {
/// to be best, `route_to` should equal to `best_to`.
fn set_head_with_transaction(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
route_to: Block::Hash,
best_to: (NumberFor<Block>, Block::Hash),
) -> ClientResult<()> {
@@ -266,7 +262,7 @@ impl<Block: BlockT> LightStorage<Block> {
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, lookup_key);
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
@@ -280,7 +276,7 @@ impl<Block: BlockT> LightStorage<Block> {
// Note that a block is finalized. Only call with child of last finalized block.
fn note_finalized(
&self,
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
header: &Block::Header,
hash: Block::Hash,
) -> ClientResult<()> {
@@ -293,7 +289,7 @@ impl<Block: BlockT> LightStorage<Block> {
}
let lookup_key = utils::number_and_hash_to_lookup_key(header.number().clone(), hash)?;
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key);
transaction.set_from_vec(columns::META, meta_keys::FINALIZED_BLOCK, lookup_key);
// build new CHT(s) if required
if let Some(new_cht_number) = cht::is_build_required(cht::size(), *header.number()) {
@@ -309,7 +305,7 @@ impl<Block: BlockT> LightStorage<Block> {
let new_header_cht_root = cht::compute_root::<Block::Header, HashFor<Block>, _>(
cht::size(), new_cht_number, cht_range.map(|num| self.hash(num))
)?;
transaction.put(
transaction.set(
columns::CHT,
&cht_key(HEADER_CHT_PREFIX, new_cht_start)?,
new_header_cht_root.as_ref()
@@ -327,7 +323,7 @@ impl<Block: BlockT> LightStorage<Block> {
cht::size(), new_cht_number, cht_range
.map(|num| self.changes_trie_root(BlockId::Number(num)))
)?;
transaction.put(
transaction.set(
columns::CHT,
&cht_key(CHANGES_TRIE_CHT_PREFIX, new_cht_start)?,
new_changes_trie_cht_root.as_ref()
@@ -350,7 +346,7 @@ impl<Block: BlockT> LightStorage<Block> {
prune_block,
hash
)?;
transaction.delete(columns::HEADER, &lookup_key);
transaction.remove(columns::HEADER, &lookup_key);
}
prune_block += One::one();
}
@@ -377,7 +373,7 @@ impl<Block: BlockT> LightStorage<Block> {
}
let cht_start = cht::start_number(cht_size, cht_number);
self.db.get(columns::CHT, &cht_key(cht_type, cht_start)?).map_err(db_err)?
self.db.get(columns::CHT, &cht_key(cht_type, cht_start)?)
.ok_or_else(no_cht_for_block)
.and_then(|hash| Block::Hash::decode(&mut &*hash).map_err(|_| no_cht_for_block()))
.map(Some)
@@ -394,18 +390,19 @@ impl<Block> AuxStore for LightStorage<Block>
I: IntoIterator<Item=&'a(&'c [u8], &'c [u8])>,
D: IntoIterator<Item=&'a &'b [u8]>,
>(&self, insert: I, delete: D) -> ClientResult<()> {
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
for (k, v) in insert {
transaction.put(columns::AUX, k, v);
transaction.set(columns::AUX, k, v);
}
for k in delete {
transaction.delete(columns::AUX, k);
transaction.remove(columns::AUX, k);
}
self.db.write(transaction).map_err(db_err)
self.db.commit(transaction);
Ok(())
}
fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
self.db.get(columns::AUX, key).map(|r| r.map(|v| v.to_vec())).map_err(db_err)
Ok(self.db.get(columns::AUX, key))
}
}
@@ -419,7 +416,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
leaf_state: NewBlockState,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
) -> ClientResult<()> {
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
let hash = header.hash();
let number = *header.number();
@@ -427,8 +424,8 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
for (key, maybe_val) in aux_ops {
match maybe_val {
Some(val) => transaction.put_vec(columns::AUX, &key, val),
None => transaction.delete(columns::AUX, &key),
Some(val) => transaction.set_from_vec(columns::AUX, &key, val),
None => transaction.remove(columns::AUX, &key),
}
}
@@ -445,7 +442,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
number,
hash,
)?;
transaction.put(columns::HEADER, &lookup_key, &header.encode());
transaction.set_from_vec(columns::HEADER, &lookup_key, header.encode());
let header_metadata = CachedHeaderMetadata::from(&header);
self.header_metadata_cache.insert_header_metadata(
@@ -456,7 +453,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
let is_genesis = number.is_zero();
if is_genesis {
self.cache.0.write().set_genesis_hash(hash);
transaction.put(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
transaction.set(columns::META, meta_keys::GENESIS_HASH, hash.as_ref());
}
let finalized = match leaf_state {
@@ -493,7 +490,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
debug!("Light DB Commit {:?} ({})", hash, number);
self.db.write(transaction).map_err(db_err)?;
self.db.commit(transaction);
cache.commit(cache_ops)
.expect("only fails if cache with given name isn't loaded yet;\
cache is already loaded because there are cache_ops; qed");
@@ -509,9 +506,9 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
let hash = header.hash();
let number = header.number();
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
self.set_head_with_transaction(&mut transaction, hash.clone(), (number.clone(), hash.clone()))?;
self.db.write(transaction).map_err(db_err)?;
self.db.commit(transaction);
self.update_meta(hash, header.number().clone(), true, false);
Ok(())
} else {
@@ -537,7 +534,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
fn finalize_header(&self, id: BlockId<Block>) -> ClientResult<()> {
if let Some(header) = self.header(id)? {
let mut transaction = DBTransaction::new();
let mut transaction = Transaction::new();
let hash = header.hash();
let number = *header.number();
self.note_finalized(&mut transaction, &header, hash.clone())?;
@@ -550,7 +547,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
)?
.into_ops();
self.db.write(transaction).map_err(db_err)?;
self.db.commit(transaction);
cache.commit(cache_ops)
.expect("only fails if cache with given name isn't loaded yet;\
cache is already loaded because there are cache_ops; qed");
@@ -575,8 +572,9 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
fn usage_info(&self) -> Option<UsageInfo> {
use sc_client_api::{MemoryInfo, IoInfo, MemorySize};
let database_cache = MemorySize::from_bytes(parity_util_mem::malloc_size(&*self.db));
let io_stats = self.io_stats.take_or_else(|| self.db.io_stats(kvdb::IoStatsKind::SincePrevious));
// TODO: reimplement IO stats
let database_cache = MemorySize::from_bytes(0);
let io_stats = self.io_stats.take_or_else(|| kvdb::IoStats::empty());
Some(UsageInfo {
memory: MemoryInfo {
@@ -732,21 +730,25 @@ pub(crate) mod tests {
#[test]
fn import_header_works() {
let db = LightStorage::new_test();
let raw_db = Arc::new(sp_database::MemDb::default());
let db = LightStorage::from_kvdb(raw_db.clone()).unwrap();
let genesis_hash = insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0));
assert_eq!(db.db.iter(columns::HEADER).count(), 1);
assert_eq!(db.db.iter(columns::KEY_LOOKUP).count(), 2);
assert_eq!(raw_db.count(columns::HEADER), 1);
assert_eq!(raw_db.count(columns::KEY_LOOKUP), 2);
let _ = insert_block(&db, HashMap::new(), || default_header(&genesis_hash, 1));
assert_eq!(db.db.iter(columns::HEADER).count(), 2);
assert_eq!(db.db.iter(columns::KEY_LOOKUP).count(), 4);
assert_eq!(raw_db.count(columns::HEADER), 2);
assert_eq!(raw_db.count(columns::KEY_LOOKUP), 4);
}
#[test]
fn finalized_ancient_headers_are_replaced_with_cht() {
fn insert_headers<F: Fn(&Hash, u64) -> Header>(header_producer: F) -> LightStorage<Block> {
let db = LightStorage::new_test();
fn insert_headers<F: Fn(&Hash, u64) -> Header>(header_producer: F) ->
(Arc<sp_database::MemDb<DbHash>>, LightStorage<Block>)
{
let raw_db = Arc::new(sp_database::MemDb::default());
let db = LightStorage::from_kvdb(raw_db.clone()).unwrap();
let cht_size: u64 = cht::size();
let ucht_size: usize = cht_size as _;
@@ -758,8 +760,8 @@ pub(crate) mod tests {
for number in 0..cht::size() {
prev_hash = insert_block(&db, HashMap::new(), || header_producer(&prev_hash, 1 + number));
}
assert_eq!(db.db.iter(columns::HEADER).count(), 1 + ucht_size);
assert_eq!(db.db.iter(columns::CHT).count(), 0);
assert_eq!(raw_db.count(columns::HEADER), 1 + ucht_size);
assert_eq!(raw_db.count(columns::CHT), 0);
// insert next SIZE blocks && ensure that nothing is pruned
for number in 0..(cht_size as _) {
@@ -769,8 +771,8 @@ pub(crate) mod tests {
|| header_producer(&prev_hash, 1 + cht_size + number),
);
}
assert_eq!(db.db.iter(columns::HEADER).count(), 1 + ucht_size + ucht_size);
assert_eq!(db.db.iter(columns::CHT).count(), 0);
assert_eq!(raw_db.count(columns::HEADER), 1 + ucht_size + ucht_size);
assert_eq!(raw_db.count(columns::CHT), 0);
// insert block #{2 * cht::size() + 1} && check that new CHT is created + headers of this CHT are pruned
// nothing is yet finalized, so nothing is pruned.
@@ -779,23 +781,23 @@ pub(crate) mod tests {
HashMap::new(),
|| header_producer(&prev_hash, 1 + cht_size + cht_size),
);
assert_eq!(db.db.iter(columns::HEADER).count(), 2 + ucht_size + ucht_size);
assert_eq!(db.db.iter(columns::CHT).count(), 0);
assert_eq!(raw_db.count(columns::HEADER), 2 + ucht_size + ucht_size);
assert_eq!(raw_db.count(columns::CHT), 0);
// now finalize the block.
for i in (0..(ucht_size + ucht_size)).map(|i| i + 1) {
db.finalize_header(BlockId::Number(i as _)).unwrap();
}
db.finalize_header(BlockId::Hash(prev_hash)).unwrap();
db
(raw_db, db)
}
// when headers are created without changes tries roots
let db = insert_headers(default_header);
let (raw_db, db) = insert_headers(default_header);
let cht_size: u64 = cht::size();
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht_size + 1) as usize);
assert_eq!(db.db.iter(columns::KEY_LOOKUP).count(), (2 * (1 + cht_size + 1)) as usize);
assert_eq!(db.db.iter(columns::CHT).count(), 1);
assert_eq!(raw_db.count(columns::HEADER), (1 + cht_size + 1) as usize);
assert_eq!(raw_db.count(columns::KEY_LOOKUP), (2 * (1 + cht_size + 1)) as usize);
assert_eq!(raw_db.count(columns::CHT), 1);
assert!((0..cht_size as _).all(|i| db.header(BlockId::Number(1 + i)).unwrap().is_none()));
assert!(db.header_cht_root(cht_size, cht_size / 2).unwrap().is_some());
assert!(db.header_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none());
@@ -803,9 +805,9 @@ pub(crate) mod tests {
assert!(db.changes_trie_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none());
// when headers are created with changes tries roots
let db = insert_headers(header_with_changes_trie);
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht_size + 1) as usize);
assert_eq!(db.db.iter(columns::CHT).count(), 2);
let (raw_db, db) = insert_headers(header_with_changes_trie);
assert_eq!(raw_db.count(columns::HEADER), (1 + cht_size + 1) as usize);
assert_eq!(raw_db.count(columns::CHT), 2);
assert!((0..cht_size as _).all(|i| db.header(BlockId::Number(1 + i)).unwrap().is_none()));
assert!(db.header_cht_root(cht_size, cht_size / 2).unwrap().is_some());
assert!(db.header_cht_root(cht_size, cht_size + cht_size / 2).unwrap().is_none());
+9 -16
View File
@@ -21,14 +21,13 @@ use std::{
sync::Arc,
};
use crate::columns;
use kvdb::KeyValueDB;
use crate::{columns, Database, DbHash, Transaction};
use parking_lot::Mutex;
/// Offchain local storage
#[derive(Clone)]
pub struct LocalStorage {
db: Arc<dyn KeyValueDB>,
db: Arc<dyn Database<DbHash>>,
locks: Arc<Mutex<HashMap<Vec<u8>, Arc<Mutex<()>>>>>,
}
@@ -43,12 +42,13 @@ impl LocalStorage {
/// Create new offchain storage for tests (backed by memorydb)
#[cfg(any(test, feature = "test-helpers"))]
pub fn new_test() -> Self {
let db = Arc::new(kvdb_memorydb::create(crate::utils::NUM_COLUMNS));
let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS);
let db = sp_database::as_database(db);
Self::new(db as _)
}
/// Create offchain local storage with given `KeyValueDB` backend.
pub fn new(db: Arc<dyn KeyValueDB>) -> Self {
pub fn new(db: Arc<dyn Database<DbHash>>) -> Self {
Self {
db,
locks: Default::default(),
@@ -59,20 +59,15 @@ impl LocalStorage {
impl sp_core::offchain::OffchainStorage for LocalStorage {
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
let mut tx = self.db.transaction();
tx.put(columns::OFFCHAIN, &key, value);
let mut tx = Transaction::new();
tx.set(columns::OFFCHAIN, &key, value);
if let Err(e) = self.db.write(tx) {
log::warn!("Error writing to the offchain DB: {:?}", e);
}
self.db.commit(tx);
}
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.db.get(columns::OFFCHAIN, &key)
.ok()
.and_then(|x| x)
.map(|v| v.to_vec())
}
fn compare_and_set(
@@ -91,9 +86,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage {
let is_set;
{
let _key_guard = key_lock.lock();
let val = self.db.get(columns::OFFCHAIN, &key)
.ok()
.and_then(|x| x);
let val = self.db.get(columns::OFFCHAIN, &key);
is_set = val.as_ref().map(|x| &**x) == old_value;
if is_set {
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2017-2020 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/>.
/// A `Database` adapter for parity-db.
use sp_database::{Database, Change, Transaction, ColumnId};
struct DbAdapter(parity_db::Db);
fn handle_err<T>(result: parity_db::Result<T>) -> T {
match result {
Ok(r) => r,
Err(e) => {
panic!("Critical database eror: {:?}", e);
}
}
}
/// 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)?;
Ok(std::sync::Arc::new(DbAdapter(db)))
}
impl<H: Clone> Database<H> for DbAdapter {
fn commit(&self, transaction: Transaction<H>) {
handle_err(self.0.commit(transaction.0.into_iter().map(|change|
match change {
Change::Set(col, key, value) => (col as u8, key, Some(value)),
Change::Remove(col, key) => (col as u8, key, None),
_ => unimplemented!(),
}))
);
}
fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
handle_err(self.0.get(col as u8, key))
}
fn lookup(&self, _hash: &H) -> Option<Vec<u8>> {
unimplemented!();
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2017-2020 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/>.
/// A `Database` adapter for subdb.
use sp_database::{self, ColumnId};
use parking_lot::RwLock;
use blake2_rfc::blake2b::blake2b;
use codec::Encode;
use subdb::{Database, KeyType};
/// A database hidden behind an RwLock, so that it implements Send + Sync.
///
/// Construct by creating a `Database` and then using `.into()`.
pub struct DbAdapter<H: KeyType>(RwLock<Database<H>>);
/// Wrap RocksDb database into a trait object that implements `sp_database::Database`
pub fn open<H: KeyType + 'static>(
path: &std::path::Path,
_num_columns: u32,
) -> Result<std::sync::Arc<dyn sp_database::Database<H>>, subdb::Error> {
let db = subdb::Options::from_path(path.into()).open()?;
Ok(std::sync::Arc::new(DbAdapter(RwLock::new(db))))
}
impl<H: KeyType> sp_database::Database<H> for DbAdapter<H> {
fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
let mut hash = H::default();
(col, key).using_encoded(|d|
hash.as_mut().copy_from_slice(blake2b(32, &[], d).as_bytes())
);
self.0.read().get(&hash)
}
fn with_get(&self, col: ColumnId, key: &[u8], f: &mut dyn FnMut(&[u8])) {
let mut hash = H::default();
(col, key).using_encoded(|d|
hash.as_mut().copy_from_slice(blake2b(32, &[], d).as_bytes())
);
let _ = self.0.read().get_ref(&hash).map(|d| f(d.as_ref()));
}
fn set(&self, col: ColumnId, key: &[u8], value: &[u8]) {
let mut hash = H::default();
(col, key).using_encoded(|d|
hash.as_mut().copy_from_slice(blake2b(32, &[], d).as_bytes())
);
self.0.write().insert(&value, &hash);
}
fn remove(&self, col: ColumnId, key: &[u8]) {
let mut hash = H::default();
(col, key).using_encoded(|d|
hash.as_mut().copy_from_slice(blake2b(32, &[], d).as_bytes())
);
let _ = self.0.write().remove(&hash);
}
fn lookup(&self, hash: &H) -> Option<Vec<u8>> {
self.0.read().get(hash)
}
fn with_lookup(&self, hash: &H, f: &mut dyn FnMut(&[u8])) {
let _ = self.0.read().get_ref(hash).map(|d| f(d.as_ref()));
}
fn store(&self, hash: &H, preimage: &[u8]) {
self.0.write().insert(preimage, hash);
}
fn release(&self, hash: &H) {
let _ = self.0.write().remove(hash);
}
}
+14 -89
View File
@@ -19,18 +19,9 @@
use std::fs;
use std::io::{Read, Write, ErrorKind};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use codec::Encode;
use kvdb_rocksdb::{Database, DatabaseConfig};
use parking_lot::RwLock;
use sp_blockchain::{well_known_cache_keys, Cache};
use sp_core::ChangesTrieConfiguration;
use sp_runtime::traits::Block as BlockT;
use crate::{
cache::{ComplexBlockId, DbCache, DbCacheSync},
utils::{DatabaseType, check_database_type, db_err, read_genesis_hash},
};
use crate::utils::DatabaseType;
/// Version file name.
const VERSION_FILE_NAME: &'static str = "db_version";
@@ -38,69 +29,21 @@ const VERSION_FILE_NAME: &'static str = "db_version";
/// Current db version.
const CURRENT_VERSION: u32 = 1;
/// Number of columns in v0.
const V0_NUM_COLUMNS: u32 = 10;
/// Upgrade database to current version.
pub fn upgrade_db<Block: BlockT>(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> {
let db_version = current_version(db_path)?;
match db_version {
0 => migrate_0_to_1::<Block>(db_path, db_type)?,
1 => (),
_ => Err(sp_blockchain::Error::Backend(format!("Future database version: {}", db_version)))?,
pub fn upgrade_db<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> sp_blockchain::Result<()> {
let is_empty = db_path.read_dir().map_or(true, |mut d| d.next().is_none());
if !is_empty {
let db_version = current_version(db_path)?;
match db_version {
0 => Err(sp_blockchain::Error::Backend(format!("Unsupported database version: {}", db_version)))?,
1 => (),
_ => Err(sp_blockchain::Error::Backend(format!("Future database version: {}", db_version)))?,
}
}
update_version(db_path)
}
/// Migration from version0 to version1:
/// 1) the number of columns has changed from 10 to 11;
/// 2) changes tries configuration are now cached.
fn migrate_0_to_1<Block: BlockT>(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> {
{
let db = open_database(db_path, db_type, V0_NUM_COLUMNS)?;
db.add_column().map_err(db_err)?;
db.flush().map_err(db_err)?;
}
let db = open_database(db_path, db_type, V0_NUM_COLUMNS + 1)?;
const V0_FULL_KEY_LOOKUP_COLUMN: u32 = 3;
const V0_FULL_HEADER_COLUMN: u32 = 4;
const V0_FULL_CACHE_COLUMN: u32 = 10; // that's the column we have just added
const V0_LIGHT_KEY_LOOKUP_COLUMN: u32 = 1;
const V0_LIGHT_HEADER_COLUMN: u32 = 2;
const V0_LIGHT_CACHE_COLUMN: u32 = 3;
let (key_lookup_column, header_column, cache_column) = match db_type {
DatabaseType::Full => (
V0_FULL_KEY_LOOKUP_COLUMN,
V0_FULL_HEADER_COLUMN,
V0_FULL_CACHE_COLUMN,
),
DatabaseType::Light => (
V0_LIGHT_KEY_LOOKUP_COLUMN,
V0_LIGHT_HEADER_COLUMN,
V0_LIGHT_CACHE_COLUMN,
),
};
let genesis_hash: Option<Block::Hash> = read_genesis_hash(&db)?;
if let Some(genesis_hash) = genesis_hash {
let cache: DbCacheSync<Block> = DbCacheSync(RwLock::new(DbCache::new(
Arc::new(db),
key_lookup_column,
header_column,
cache_column,
genesis_hash,
ComplexBlockId::new(genesis_hash, 0.into()),
)));
let changes_trie_config: Option<ChangesTrieConfiguration> = None;
cache.initialize(&well_known_cache_keys::CHANGES_TRIE_CONFIG, changes_trie_config.encode())?;
}
Ok(())
}
/// Reads current database version from the file at given path.
/// If the file does not exist returns 0.
@@ -118,14 +61,9 @@ fn current_version(path: &Path) -> sp_blockchain::Result<u32> {
}
}
/// Opens database of given type with given number of columns.
fn open_database(db_path: &Path, db_type: DatabaseType, db_columns: u32) -> sp_blockchain::Result<Database> {
let db_path = db_path.to_str()
.ok_or_else(|| sp_blockchain::Error::Backend("Invalid database path".into()))?;
let db_cfg = DatabaseConfig::with_columns(db_columns);
let db = Database::open(&db_cfg, db_path).map_err(db_err)?;
check_database_type(&db, db_type)?;
Ok(db)
/// Maps database error to client error
fn db_err(err: std::io::Error) -> sp_blockchain::Error {
sp_blockchain::Error::Backend(format!("{}", err))
}
/// Writes current database version to the file.
@@ -152,8 +90,6 @@ mod tests {
use super::*;
fn create_db(db_path: &Path, version: Option<u32>) {
let db_cfg = DatabaseConfig::with_columns(V0_NUM_COLUMNS);
Database::open(&db_cfg, db_path.to_str().unwrap()).unwrap();
if let Some(version) = version {
fs::create_dir_all(db_path).unwrap();
let mut file = fs::File::create(version_file_path(db_path)).unwrap();
@@ -166,7 +102,7 @@ mod tests {
state_cache_size: 0,
state_cache_child_ratio: None,
pruning: PruningMode::ArchiveAll,
source: DatabaseSettingsSrc::Path { path: db_path.to_owned(), cache_size: 128 },
source: DatabaseSettingsSrc::RocksDb { path: db_path.to_owned(), cache_size: 128 },
}, DatabaseType::Full).map(|_| ())
}
@@ -184,15 +120,4 @@ mod tests {
open_database(db_dir.path()).unwrap();
assert_eq!(current_version(db_dir.path()).unwrap(), CURRENT_VERSION);
}
#[test]
fn upgrade_from_0_to_1_works() {
for version_from_file in &[None, Some(0)] {
let db_dir = tempfile::TempDir::new().unwrap();
let db_path = db_dir.path();
create_db(db_path, *version_from_file);
open_database(db_path).unwrap();
assert_eq!(current_version(db_path).unwrap(), CURRENT_VERSION);
}
}
}
+53 -52
View File
@@ -18,21 +18,19 @@
//! full and light storages.
use std::sync::Arc;
use std::{io, convert::TryInto};
use std::convert::TryInto;
use kvdb::{KeyValueDB, DBTransaction};
#[cfg(any(feature = "kvdb-rocksdb", test))]
use kvdb_rocksdb::{Database, DatabaseConfig};
use log::debug;
use codec::Decode;
use sp_trie::DBValue;
use sp_database::Transaction;
use sp_runtime::generic::BlockId;
use sp_runtime::traits::{
Block as BlockT, Header as HeaderT, Zero,
UniqueSaturatedFrom, UniqueSaturatedInto,
};
use crate::{DatabaseSettings, DatabaseSettingsSrc};
use crate::{DatabaseSettings, DatabaseSettingsSrc, Database, DbHash};
/// 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.
@@ -136,35 +134,35 @@ pub fn lookup_key_to_number<N>(key: &[u8]) -> sp_blockchain::Result<N> where
/// Delete number to hash mapping in DB transaction.
pub fn remove_number_to_key_mapping<N: TryInto<u32>>(
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
key_lookup_col: u32,
number: N,
) -> sp_blockchain::Result<()> {
transaction.delete(key_lookup_col, number_index_key(number)?.as_ref());
transaction.remove(key_lookup_col, number_index_key(number)?.as_ref());
Ok(())
}
/// Remove key mappings.
pub fn remove_key_mappings<N: TryInto<u32>, H: AsRef<[u8]>>(
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
key_lookup_col: u32,
number: N,
hash: H,
) -> sp_blockchain::Result<()> {
remove_number_to_key_mapping(transaction, key_lookup_col, number)?;
transaction.delete(key_lookup_col, hash.as_ref());
transaction.remove(key_lookup_col, hash.as_ref());
Ok(())
}
/// Place a number mapping into the database. This maps number to current perceived
/// block hash at that position.
pub fn insert_number_to_key_mapping<N: TryInto<u32> + Clone, H: AsRef<[u8]>>(
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
key_lookup_col: u32,
number: N,
hash: H,
) -> sp_blockchain::Result<()> {
transaction.put_vec(
transaction.set_from_vec(
key_lookup_col,
number_index_key(number.clone())?.as_ref(),
number_and_hash_to_lookup_key(number, hash)?,
@@ -174,12 +172,12 @@ pub fn insert_number_to_key_mapping<N: TryInto<u32> + Clone, H: AsRef<[u8]>>(
/// Insert a hash to key mapping in the database.
pub fn insert_hash_to_key_mapping<N: TryInto<u32>, H: AsRef<[u8]> + Clone>(
transaction: &mut DBTransaction,
transaction: &mut Transaction<DbHash>,
key_lookup_col: u32,
number: N,
hash: H,
) -> sp_blockchain::Result<()> {
transaction.put_vec(
transaction.set_from_vec(
key_lookup_col,
hash.clone().as_ref(),
number_and_hash_to_lookup_key(number, hash)?,
@@ -191,42 +189,35 @@ pub fn insert_hash_to_key_mapping<N: TryInto<u32>, H: AsRef<[u8]> + Clone>(
/// block lookup key is the DB-key header, block and justification are stored under.
/// looks up lookup key by hash from DB as necessary.
pub fn block_id_to_lookup_key<Block>(
db: &dyn KeyValueDB,
db: &dyn Database<DbHash>,
key_lookup_col: u32,
id: BlockId<Block>
) -> Result<Option<Vec<u8>>, sp_blockchain::Error> where
Block: BlockT,
::sp_runtime::traits::NumberFor<Block>: UniqueSaturatedFrom<u64> + UniqueSaturatedInto<u64>,
{
let res = match id {
Ok(match id {
BlockId::Number(n) => db.get(
key_lookup_col,
number_index_key(n)?.as_ref(),
),
BlockId::Hash(h) => db.get(key_lookup_col, h.as_ref()),
};
res.map_err(db_err)
BlockId::Hash(h) => db.get(key_lookup_col, h.as_ref())
})
}
/// Maps database error to client error
pub fn db_err(err: io::Error) -> sp_blockchain::Error {
sp_blockchain::Error::Backend(format!("{}", err))
}
/// Open RocksDB database.
/// Opens the configured database.
pub fn open_database<Block: BlockT>(
config: &DatabaseSettings,
db_type: DatabaseType,
) -> sp_blockchain::Result<Arc<dyn KeyValueDB>> {
let db: Arc<dyn KeyValueDB> = match &config.source {
) -> sp_blockchain::Result<Arc<dyn Database<DbHash>>> {
let db: Arc<dyn Database<DbHash>> = match &config.source {
#[cfg(any(feature = "kvdb-rocksdb", test))]
DatabaseSettingsSrc::Path { path, cache_size } => {
DatabaseSettingsSrc::RocksDb { path, cache_size } => {
// first upgrade database to required version
crate::upgrade::upgrade_db::<Block>(&path, db_type)?;
// and now open database assuming that it has the latest version
let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS);
let mut db_config = kvdb_rocksdb::DatabaseConfig::with_columns(NUM_COLUMNS);
let state_col_budget = (*cache_size as f64 * 0.9) as usize;
let other_col_budget = (cache_size - state_col_budget) / (NUM_COLUMNS as usize - 1);
let mut memory_budget = std::collections::HashMap::new();
@@ -245,21 +236,32 @@ pub fn open_database<Block: BlockT>(
log::trace!(
target: "db",
"Open database at {}, state column budget: {} MiB, others({}) column cache: {} MiB",
"Open RocksDB database at {}, state column budget: {} MiB, others({}) column cache: {} MiB",
path,
state_col_budget,
NUM_COLUMNS,
other_col_budget,
);
Arc::new(Database::open(&db_config, &path).map_err(db_err)?)
let db = kvdb_rocksdb::Database::open(&db_config, &path)
.map_err(|err| sp_blockchain::Error::Backend(format!("{}", err)))?;
sp_database::as_database(db)
},
#[cfg(not(any(feature = "kvdb-rocksdb", test)))]
DatabaseSettingsSrc::Path { .. } => {
let msg = "Try to open RocksDB database with RocksDB disabled".into();
return Err(sp_blockchain::Error::Backend(msg));
#[cfg(feature = "subdb")]
DatabaseSettingsSrc::SubDb { path } => {
crate::subdb::open(&path, NUM_COLUMNS)
.map_err(|e| sp_blockchain::Error::Backend(format!("{:?}", e)))?
},
#[cfg(feature = "parity-db")]
DatabaseSettingsSrc::ParityDb { path } => {
crate::parity_db::open(&path, NUM_COLUMNS)
.map_err(|e| sp_blockchain::Error::Backend(format!("{:?}", e)))?
},
DatabaseSettingsSrc::Custom(db) => db.clone(),
_ => {
let msg = "Trying to open a unsupported database".into();
return Err(sp_blockchain::Error::Backend(msg));
},
};
check_database_type(&*db, db_type)?;
@@ -268,8 +270,8 @@ pub fn open_database<Block: BlockT>(
}
/// Check database type.
pub fn check_database_type(db: &dyn KeyValueDB, db_type: DatabaseType) -> sp_blockchain::Result<()> {
match db.get(COLUMN_META, meta_keys::TYPE).map_err(db_err)? {
pub fn check_database_type(db: &dyn Database<DbHash>, db_type: DatabaseType) -> sp_blockchain::Result<()> {
match db.get(COLUMN_META, meta_keys::TYPE) {
Some(stored_type) => {
if db_type.as_str().as_bytes() != &*stored_type {
return Err(sp_blockchain::Error::Backend(
@@ -277,9 +279,9 @@ pub fn check_database_type(db: &dyn KeyValueDB, db_type: DatabaseType) -> sp_blo
}
},
None => {
let mut transaction = DBTransaction::new();
transaction.put(COLUMN_META, meta_keys::TYPE, db_type.as_str().as_bytes());
db.write(transaction).map_err(db_err)?;
let mut transaction = Transaction::new();
transaction.set(COLUMN_META, meta_keys::TYPE, db_type.as_str().as_bytes());
db.commit(transaction)
},
}
@@ -288,7 +290,7 @@ pub fn check_database_type(db: &dyn KeyValueDB, db_type: DatabaseType) -> sp_blo
/// Read database column entry for the given block.
pub fn read_db<Block>(
db: &dyn KeyValueDB,
db: &dyn Database<DbHash>,
col_index: u32,
col: u32,
id: BlockId<Block>
@@ -297,14 +299,14 @@ pub fn read_db<Block>(
Block: BlockT,
{
block_id_to_lookup_key(db, col_index, id).and_then(|key| match key {
Some(key) => db.get(col, key.as_ref()).map_err(db_err),
Some(key) => Ok(db.get(col, key.as_ref())),
None => Ok(None),
})
}
/// Read a header from the database.
pub fn read_header<Block: BlockT>(
db: &dyn KeyValueDB,
db: &dyn Database<DbHash>,
col_index: u32,
col: u32,
id: BlockId<Block>,
@@ -322,7 +324,7 @@ pub fn read_header<Block: BlockT>(
/// Required header from the database.
pub fn require_header<Block: BlockT>(
db: &dyn KeyValueDB,
db: &dyn Database<DbHash>,
col_index: u32,
col: u32,
id: BlockId<Block>,
@@ -334,7 +336,7 @@ pub fn require_header<Block: BlockT>(
}
/// Read meta from the database.
pub fn read_meta<Block>(db: &dyn KeyValueDB, col_header: u32) -> Result<
pub fn read_meta<Block>(db: &dyn Database<DbHash>, col_header: u32) -> Result<
Meta<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>,
sp_blockchain::Error,
>
@@ -353,11 +355,10 @@ pub fn read_meta<Block>(db: &dyn KeyValueDB, col_header: u32) -> Result<
};
let load_meta_block = |desc, key| -> Result<_, sp_blockchain::Error> {
if let Some(Some(header)) = db.get(COLUMN_META, key).and_then(|id|
match id {
Some(id) => db.get(col_header, &id).map(|h| h.map(|b| Block::Header::decode(&mut &b[..]).ok())),
None => Ok(None),
}).map_err(db_err)?
if let Some(Some(header)) = match db.get(COLUMN_META, key) {
Some(id) => db.get(col_header, &id).map(|b| Block::Header::decode(&mut &b[..]).ok()),
None => None,
}
{
let hash = header.hash();
debug!("DB Opened blockchain db, fetched {} = {:?} ({})", desc, hash, header.number());
@@ -380,8 +381,8 @@ pub fn read_meta<Block>(db: &dyn KeyValueDB, col_header: u32) -> Result<
}
/// Read genesis hash from database.
pub fn read_genesis_hash<Hash: Decode>(db: &dyn KeyValueDB) -> sp_blockchain::Result<Option<Hash>> {
match db.get(COLUMN_META, meta_keys::GENESIS_HASH).map_err(db_err)? {
pub fn read_genesis_hash<Hash: Decode>(db: &dyn Database<DbHash>) -> sp_blockchain::Result<Option<Hash>> {
match db.get(COLUMN_META, meta_keys::GENESIS_HASH) {
Some(h) => match Decode::decode(&mut &h[..]) {
Ok(h) => Ok(Some(h)),
Err(err) => Err(sp_blockchain::Error::Backend(
+9 -6
View File
@@ -32,6 +32,7 @@ use sp_blockchain::{
use sc_client_api::{BlockchainEvents, BlockImportNotification, FinalityNotifications, ImportNotifications, FinalityNotification, backend::{TransactionFor, AuxStore, Backend, Finalizer}, BlockBackend};
use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
use sc_client::LongestChain;
use sc_client::blockchain::HeaderBackend;
use sc_network::config::Role;
use sp_consensus::block_validation::DefaultBlockAnnounceValidator;
use sp_consensus::import_queue::{
@@ -359,13 +360,9 @@ impl<D> Peer<D> {
/// Test helper to compare the blockchain state of multiple (networked)
/// clients.
/// Potentially costly, as it creates in-memory copies of both blockchains in order
/// to compare them. If you have easier/softer checks that are sufficient, e.g.
/// by using .info(), you should probably use it instead of this.
pub fn blockchain_canon_equals(&self, other: &Self) -> bool {
if let (Some(mine), Some(others)) = (self.backend.clone(), other.backend.clone()) {
mine.as_in_memory().blockchain()
.canon_equals_to(others.as_in_memory().blockchain())
mine.blockchain().info().best_hash == others.blockchain().info().best_hash
} else {
false
}
@@ -374,7 +371,7 @@ impl<D> Peer<D> {
/// Count the total number of imported blocks.
pub fn blocks_count(&self) -> u64 {
self.backend.as_ref().map(
|backend| backend.blocks_count()
|backend| backend.blockchain().info().best_number
).unwrap_or(0)
}
@@ -382,6 +379,12 @@ impl<D> Peer<D> {
pub fn failed_verifications(&self) -> HashMap<<Block as BlockT>::Hash, String> {
self.verifier.failed_verifications.lock().clone()
}
pub fn has_block(&self, hash: &H256) -> bool {
self.backend.as_ref().map(
|backend| backend.blockchain().header(BlockId::hash(*hash)).unwrap().is_some()
).unwrap_or(false)
}
}
/// Implements `BlockImport` for any `Transaction`. Internally the transaction is
+8 -19
View File
@@ -339,13 +339,15 @@ fn syncs_all_forks() {
net.peer(0).push_blocks(2, false);
net.peer(1).push_blocks(2, false);
net.peer(0).push_blocks(2, true);
net.peer(1).push_blocks(4, false);
let b1 = net.peer(0).push_blocks(2, true);
let b2 = net.peer(1).push_blocks(4, false);
net.block_until_sync();
// Check that all peers have all of the blocks.
assert_eq!(9, net.peer(0).blocks_count());
assert_eq!(9, net.peer(1).blocks_count());
// Check that all peers have all of the branches.
assert!(net.peer(0).has_block(&b1));
assert!(net.peer(0).has_block(&b2));
assert!(net.peer(1).has_block(&b1));
assert!(net.peer(1).has_block(&b2));
}
#[test]
@@ -587,24 +589,11 @@ fn syncs_header_only_forks() {
net.peer(0).push_blocks(2, true);
let small_hash = net.peer(0).client().info().best_hash;
let small_number = net.peer(0).client().info().best_number;
net.peer(1).push_blocks(4, false);
net.block_until_sync();
// Peer 1 will sync the small fork even though common block state is missing
assert_eq!(9, net.peer(0).blocks_count());
assert_eq!(9, net.peer(1).blocks_count());
// Request explicit header-only sync request for the ancient fork.
let first_peer_id = net.peer(0).id();
net.peer(1).set_sync_fork_request(vec![first_peer_id], small_hash, small_number);
block_on(futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
if net.peer(1).client().header(&BlockId::Hash(small_hash)).unwrap().is_none() {
return Poll::Pending
}
Poll::Ready(())
}));
assert!(net.peer(1).has_block(&small_hash));
}
#[test]
+2 -2
View File
@@ -12,10 +12,10 @@ description = "Substrate service. Starts a thread that spins up the network, cli
targets = ["x86_64-unknown-linux-gnu"]
[features]
default = ["rocksdb"]
default = ["db"]
# The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass
# a path to a database, an error will be produced at runtime.
rocksdb = ["sc-client-db/kvdb-rocksdb"]
db = ["sc-client-db/kvdb-rocksdb", "sc-client-db/parity-db"]
wasmtime = [
"sc-executor/wasmtime",
]
+3 -19
View File
@@ -17,7 +17,7 @@
use crate::{Service, NetworkStatus, NetworkState, error::Error, DEFAULT_PROTOCOL_ID, MallocSizeOfWasm};
use crate::{TaskManagerBuilder, start_rpc_servers, build_network_future, TransactionPoolAdapter};
use crate::status_sinks;
use crate::config::{Configuration, DatabaseConfig, KeystoreConfig, PrometheusConfig};
use crate::config::{Configuration, KeystoreConfig, PrometheusConfig};
use crate::metrics::MetricsService;
use sc_client_api::{
self,
@@ -196,15 +196,7 @@ fn new_full_parts<TBl, TRtApi, TExecDisp>(
state_cache_child_ratio:
config.state_cache_child_ratio.map(|v| (v, 100)),
pruning: config.pruning.clone(),
source: match &config.database {
DatabaseConfig::Path { path, cache_size } =>
sc_client_db::DatabaseSettingsSrc::Path {
path: path.clone(),
cache_size: *cache_size,
},
DatabaseConfig::Custom(db) =>
sc_client_db::DatabaseSettingsSrc::Custom(db.clone()),
},
source: config.database.clone(),
};
let extensions = sc_client_api::execution_extensions::ExecutionExtensions::new(
@@ -308,15 +300,7 @@ impl ServiceBuilder<(), (), (), (), (), (), (), (), (), (), ()> {
state_cache_child_ratio:
config.state_cache_child_ratio.map(|v| (v, 100)),
pruning: config.pruning.clone(),
source: match &config.database {
DatabaseConfig::Path { path, cache_size } =>
sc_client_db::DatabaseSettingsSrc::Path {
path: path.clone(),
cache_size: *cache_size,
},
DatabaseConfig::Custom(db) =>
sc_client_db::DatabaseSettingsSrc::Custom(db.clone()),
},
source: config.database.clone(),
};
sc_client_db::light::LightStorage::new(db_settings)?
};
+1 -16
View File
@@ -17,7 +17,7 @@
//! Service configuration.
pub use sc_client::ExecutionStrategies;
pub use sc_client_db::{kvdb::KeyValueDB, PruningMode};
pub use sc_client_db::{Database, PruningMode, DatabaseSettingsSrc as DatabaseConfig};
pub use sc_network::Multiaddr;
pub use sc_network::config::{ExtTransport, MultiaddrWithPeerId, NetworkConfiguration, Role, NodeKeyConfig};
pub use sc_executor::WasmExecutionMethod;
@@ -124,21 +124,6 @@ impl KeystoreConfig {
}
}
/// Configuration of the database of the client.
#[derive(Clone)]
pub enum DatabaseConfig {
/// Database file at a specific path. Recommended for most uses.
Path {
/// Path to the database.
path: PathBuf,
/// Cache Size for internal database in MiB
cache_size: usize,
},
/// A custom implementation of an already-open database.
Custom(Arc<dyn KeyValueDB>),
}
/// Configuration of the Prometheus endpoint.
#[derive(Clone)]
pub struct PrometheusConfig {
+1 -1
View File
@@ -175,7 +175,7 @@ fn node_config<G: RuntimeGenesis + 'static, E: ChainSpecExtension + Clone + 'sta
path: root.join("key"),
password: None
},
database: DatabaseConfig::Path {
database: DatabaseConfig::RocksDb {
path: root.join("db"),
cache_size: 128,
},
+2 -2
View File
@@ -3022,7 +3022,7 @@ pub(crate) mod tests {
state_cache_size: 1 << 20,
state_cache_child_ratio: None,
pruning: PruningMode::ArchiveAll,
source: DatabaseSettingsSrc::Path {
source: DatabaseSettingsSrc::RocksDb {
path: tmp.path().into(),
cache_size: 128,
}
@@ -3224,7 +3224,7 @@ pub(crate) mod tests {
state_cache_size: 1 << 20,
state_cache_child_ratio: None,
pruning: PruningMode::keep_blocks(1),
source: DatabaseSettingsSrc::Path {
source: DatabaseSettingsSrc::RocksDb {
path: tmp.path().into(),
cache_size: 128,
}
+34 -39
View File
@@ -18,11 +18,13 @@
use std::collections::BTreeMap;
use std::cmp::Reverse;
use kvdb::{KeyValueDB, DBTransaction};
use sp_database::{Database, Transaction};
use sp_runtime::traits::AtLeast32Bit;
use codec::{Encode, Decode};
use sp_blockchain::{Error, Result};
type DbHash = [u8; 32];
#[derive(Debug, Clone, PartialEq, Eq)]
struct LeafSetItem<H, N> {
hash: H,
@@ -59,7 +61,7 @@ impl<H, N: Ord> FinalizationDisplaced<H, N> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LeafSet<H, N> {
storage: BTreeMap<Reverse<N>, Vec<H>>,
pending_added: Vec<LeafSetItem<H, N>>,
pending_added: Vec<(H, N)>,
pending_removed: Vec<H>,
}
@@ -77,21 +79,20 @@ impl<H, N> LeafSet<H, N> where
}
/// Read the leaf list from the DB, using given prefix for keys.
pub fn read_from_db(db: &dyn KeyValueDB, column: u32, prefix: &[u8]) -> Result<Self> {
pub fn read_from_db(db: &dyn Database<DbHash>, column: u32, prefix: &[u8]) -> Result<Self> {
let mut storage = BTreeMap::new();
for (key, value) in db.iter_from_prefix(column, prefix) {
if !key.starts_with(prefix) { break }
let raw_hash = &mut &key[prefix.len()..];
let hash = match Decode::decode(raw_hash) {
Ok(hash) => hash,
Err(_) => return Err(Error::Backend("Error decoding hash".into())),
};
let number = match Decode::decode(&mut &value[..]) {
Ok(number) => number,
Err(_) => return Err(Error::Backend("Error decoding number".into())),
};
storage.entry(Reverse(number)).or_insert_with(Vec::new).push(hash);
match db.get(column, prefix) {
Some(leaves) => {
let vals: Vec<_> = match Decode::decode(&mut leaves.as_ref()) {
Ok(vals) => vals,
Err(_) => return Err(Error::Backend("Error decoding leaves".into())),
};
for (number, hashes) in vals.into_iter() {
storage.insert(Reverse(number), hashes);
}
}
None => {},
}
Ok(Self {
storage,
@@ -124,7 +125,7 @@ impl<H, N> LeafSet<H, N> where
};
self.insert_leaf(Reverse(number.clone()), hash.clone());
self.pending_added.push(LeafSetItem { hash, number: Reverse(number) });
self.pending_added.push((hash, number));
displaced
}
@@ -185,7 +186,7 @@ impl<H, N> LeafSet<H, N> where
// this is an invariant of regular block import.
if !leaves_contains_best {
self.insert_leaf(best_number.clone(), best_hash.clone());
self.pending_added.push(LeafSetItem { hash: best_hash, number: best_number });
self.pending_added.push((best_hash, best_number.0));
}
}
@@ -201,18 +202,11 @@ impl<H, N> LeafSet<H, N> where
}
/// Write the leaf list to the database transaction.
pub fn prepare_transaction(&mut self, tx: &mut DBTransaction, column: u32, prefix: &[u8]) {
let mut buf = prefix.to_vec();
for LeafSetItem { hash, number } in self.pending_added.drain(..) {
hash.using_encoded(|s| buf.extend(s));
tx.put_vec(column, &buf[..], number.0.encode());
buf.truncate(prefix.len()); // reuse allocation.
}
for hash in self.pending_removed.drain(..) {
hash.using_encoded(|s| buf.extend(s));
tx.delete(column, &buf[..]);
buf.truncate(prefix.len()); // reuse allocation.
}
pub fn prepare_transaction(&mut self, tx: &mut Transaction<DbHash>, column: u32, prefix: &[u8]) {
let leaves: Vec<_> = self.storage.iter().map(|(n, h)| (n.0.clone(), h.clone())).collect();
tx.set_from_vec(column, prefix, leaves.encode());
self.pending_added.clear();
self.pending_removed.clear();
}
#[cfg(test)]
@@ -281,6 +275,7 @@ impl<'a, H: 'a, N: 'a> Drop for Undo<'a, H, N> {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn it_works() {
@@ -305,7 +300,7 @@ mod tests {
#[test]
fn flush_to_disk() {
const PREFIX: &[u8] = b"abcdefg";
let db = ::kvdb_memorydb::create(1);
let db = Arc::new(sp_database::MemDb::default());
let mut set = LeafSet::new();
set.import(0u32, 0u32, 0u32);
@@ -314,12 +309,12 @@ mod tests {
set.import(2_1, 2, 1_1);
set.import(3_1, 3, 2_1);
let mut tx = DBTransaction::new();
let mut tx = Transaction::new();
set.prepare_transaction(&mut tx, 0, PREFIX);
db.write(tx).unwrap();
db.commit(tx);
let set2 = LeafSet::read_from_db(&db, 0, PREFIX).unwrap();
let set2 = LeafSet::read_from_db(&*db, 0, PREFIX).unwrap();
assert_eq!(set, set2);
}
@@ -339,7 +334,7 @@ mod tests {
#[test]
fn finalization_consistent_with_disk() {
const PREFIX: &[u8] = b"prefix";
let db = ::kvdb_memorydb::create(1);
let db = Arc::new(sp_database::MemDb::default());
let mut set = LeafSet::new();
set.import(10_1u32, 10u32, 0u32);
@@ -349,21 +344,21 @@ mod tests {
assert!(set.contains(10, 10_1));
let mut tx = DBTransaction::new();
let mut tx = Transaction::new();
set.prepare_transaction(&mut tx, 0, PREFIX);
db.write(tx).unwrap();
db.commit(tx);
let _ = set.finalize_height(11);
let mut tx = DBTransaction::new();
let mut tx = Transaction::new();
set.prepare_transaction(&mut tx, 0, PREFIX);
db.write(tx).unwrap();
db.commit(tx);
assert!(set.contains(11, 11_1));
assert!(set.contains(11, 11_2));
assert!(set.contains(12, 12_1));
assert!(!set.contains(10, 10_1));
let set2 = LeafSet::read_from_db(&db, 0, PREFIX).unwrap();
let set2 = LeafSet::read_from_db(&*db, 0, PREFIX).unwrap();
assert_eq!(set, set2);
}
+1 -1
View File
@@ -208,7 +208,7 @@ pub trait Contains<T: Ord> {
///
/// **Should be used for benchmarking only!!!**
#[cfg(feature = "runtime-benchmarks")]
fn add(t: &T);
fn add(t: &T) { unimplemented!() }
}
/// Determiner to say whether a given account is unused.
@@ -242,7 +242,7 @@ pub trait Cache<Block: BlockT>: Send + Sync {
}
/// Blockchain info
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub struct Info<Block: BlockT> {
/// Best block hash.
pub best_hash: Block::Hash,
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "sp-database"
version = "2.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "Substrate database trait."
documentation = "https://docs.rs/sp-database"
[dependencies]
parking_lot = "0.10.0"
kvdb = "0.5.0"
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2017-2020 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/>.
/// A wrapper around `kvdb::Database` that implements `sp_database::Database` trait
use ::kvdb::{DBTransaction, KeyValueDB};
use crate::{Database, Change, Transaction, ColumnId};
struct DbAdapter<D: KeyValueDB + 'static>(D);
fn handle_err<T>(result: std::io::Result<T>) -> T {
match result {
Ok(r) => r,
Err(e) => {
panic!("Critical database eror: {:?}", e);
}
}
}
/// Wrap RocksDb database into a trait object that implements `sp_database::Database`
pub fn as_database<D: KeyValueDB + 'static, H: Clone>(db: D) -> std::sync::Arc<dyn Database<H>> {
std::sync::Arc::new(DbAdapter(db))
}
impl<D: KeyValueDB, H: Clone> Database<H> for DbAdapter<D> {
fn commit(&self, transaction: Transaction<H>) {
let mut tx = DBTransaction::new();
for change in transaction.0.into_iter() {
match change {
Change::Set(col, key, value) => tx.put_vec(col, &key, value),
Change::Remove(col, key) => tx.delete(col, &key),
_ => unimplemented!(),
}
}
handle_err(self.0.write(tx));
}
fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
handle_err(self.0.get(col, key))
}
fn lookup(&self, _hash: &H) -> Option<Vec<u8>> {
unimplemented!();
}
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright 2017-2020 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/>.
//! The main database trait, allowing Substrate to store data persistently.
mod mem;
mod kvdb;
pub use mem::MemDb;
pub use crate::kvdb::as_database;
/// An identifier for a column.
pub type ColumnId = u32;
/// An alteration to the database.
#[derive(Clone)]
pub enum Change<H> {
Set(ColumnId, Vec<u8>, Vec<u8>),
Remove(ColumnId, Vec<u8>),
Store(H, Vec<u8>),
Release(H),
}
/// An alteration to the database that references the data.
pub enum ChangeRef<'a, H> {
Set(ColumnId, &'a [u8], &'a [u8]),
Remove(ColumnId, &'a [u8]),
Store(H, &'a [u8]),
Release(H),
}
/// A series of changes to the database that can be committed atomically. They do not take effect
/// until passed into `Database::commit`.
#[derive(Default, Clone)]
pub struct Transaction<H>(pub Vec<Change<H>>);
impl<H> Transaction<H> {
/// Create a new transaction to be prepared and committed atomically.
pub fn new() -> Self {
Transaction(Vec::new())
}
/// Set the value of `key` in `col` to `value`, replacing anything that is there currently.
pub fn set(&mut self, col: ColumnId, key: &[u8], value: &[u8]) {
self.0.push(Change::Set(col, key.to_vec(), value.to_vec()))
}
/// Set the value of `key` in `col` to `value`, replacing anything that is there currently.
pub fn set_from_vec(&mut self, col: ColumnId, key: &[u8], value: Vec<u8>) {
self.0.push(Change::Set(col, key.to_vec(), value))
}
/// Remove the value of `key` in `col`.
pub fn remove(&mut self, col: ColumnId, key: &[u8]) {
self.0.push(Change::Remove(col, key.to_vec()))
}
/// Store the `preimage` of `hash` into the database, so that it may be looked up later with
/// `Database::lookup`. This may be called multiple times, but `Database::lookup` but subsequent
/// calls will ignore `preimage` and simply increase the number of references on `hash`.
pub fn store(&mut self, hash: H, preimage: &[u8]) {
self.0.push(Change::Store(hash, preimage.to_vec()))
}
/// Release the preimage of `hash` from the database. An equal number of these to the number of
/// corresponding `store`s must have been given before it is legal for `Database::lookup` to
/// be unable to provide the preimage.
pub fn release(&mut self, hash: H) {
self.0.push(Change::Release(hash))
}
}
pub trait Database<H: Clone>: Send + Sync {
/// Commit the `transaction` to the database atomically. Any further calls to `get` or `lookup`
/// will reflect the new state.
fn commit(&self, transaction: Transaction<H>) {
for change in transaction.0.into_iter() {
match change {
Change::Set(col, key, value) => self.set(col, &key, &value),
Change::Remove(col, key) => self.remove(col, &key),
Change::Store(hash, preimage) => self.store(&hash, &preimage),
Change::Release(hash) => self.release(&hash),
}
}
}
/// Commit the `transaction` to the database atomically. Any further calls to `get` or `lookup`
/// will reflect the new state.
fn commit_ref<'a>(&self, transaction: &mut dyn Iterator<Item=ChangeRef<'a, H>>) {
let mut tx = Transaction::new();
for change in transaction {
match change {
ChangeRef::Set(col, key, value) => tx.set(col, key, value),
ChangeRef::Remove(col, key) => tx.remove(col, key),
ChangeRef::Store(hash, preimage) => tx.store(hash, preimage),
ChangeRef::Release(hash) => tx.release(hash),
}
}
self.commit(tx);
}
/// Retrieve the value previously stored against `key` or `None` if
/// `key` is not currently in the database.
fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>>;
/// Call `f` with the value previously stored against `key`.
///
/// This may be faster than `get` since it doesn't allocate.
/// Use `with_get` helper function if you need `f` to return a value from `f`
fn with_get(&self, col: ColumnId, key: &[u8], f: &mut dyn FnMut(&[u8])) {
self.get(col, key).map(|v| f(&v));
}
/// Set the value of `key` in `col` to `value`, replacing anything that is there currently.
fn set(&self, col: ColumnId, key: &[u8], value: &[u8]) {
let mut t = Transaction::new();
t.set(col, key, value);
self.commit(t);
}
/// Remove the value of `key` in `col`.
fn remove(&self, col: ColumnId, key: &[u8]) {
let mut t = Transaction::new();
t.remove(col, key);
self.commit(t);
}
/// Retrieve the first preimage previously `store`d for `hash` or `None` if no preimage is
/// currently stored.
fn lookup(&self, hash: &H) -> Option<Vec<u8>>;
/// Call `f` with the preimage stored for `hash` and return the result, or `None` if no preimage
/// is currently stored.
///
/// This may be faster than `lookup` since it doesn't allocate.
/// Use `with_lookup` helper function if you need `f` to return a value from `f`
fn with_lookup(&self, hash: &H, f: &mut dyn FnMut(&[u8])) {
self.lookup(hash).map(|v| f(&v));
}
/// Store the `preimage` of `hash` into the database, so that it may be looked up later with
/// `Database::lookup`. This may be called multiple times, but `Database::lookup` but subsequent
/// calls will ignore `preimage` and simply increase the number of references on `hash`.
fn store(&self, hash: &H, preimage: &[u8]) {
let mut t = Transaction::new();
t.store(hash.clone(), preimage);
self.commit(t);
}
/// Release the preimage of `hash` from the database. An equal number of these to the number of
/// corresponding `store`s must have been given before it is legal for `Database::lookup` to
/// be unable to provide the preimage.
fn release(&self, hash: &H) {
let mut t = Transaction::new();
t.release(hash.clone());
self.commit(t);
}
}
/// Call `f` with the value previously stored against `key` and return the result, or `None` if
/// `key` is not currently in the database.
///
/// This may be faster than `get` since it doesn't allocate.
pub fn with_get<R, H: Clone>(db: &dyn Database<H>, col: ColumnId, key: &[u8], mut f: impl FnMut(&[u8]) -> R) -> Option<R> {
let mut result: Option<R> = None;
let mut adapter = |k: &_| { result = Some(f(k)); };
db.with_get(col, key, &mut adapter);
result
}
/// Call `f` with the preimage stored for `hash` and return the result, or `None` if no preimage
/// is currently stored.
///
/// This may be faster than `lookup` since it doesn't allocate.
pub fn with_lookup<R, H: Clone>(db: &dyn Database<H>, hash: &H, mut f: impl FnMut(&[u8]) -> R) -> Option<R> {
let mut result: Option<R> = None;
let mut adapter = |k: &_| { result = Some(f(k)); };
db.with_lookup(hash, &mut adapter);
result
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2017-2020 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/>.
//! In-memory implementation of `Database`
use std::collections::HashMap;
use crate::{Database, Transaction, ColumnId, Change};
use parking_lot::RwLock;
#[derive(Default)]
/// This implements `Database` as an in-memory hash map. `commit` is not atomic.
pub struct MemDb<H: Clone + Send + Sync + Eq + PartialEq + Default + std::hash::Hash>
(RwLock<(HashMap<ColumnId, HashMap<Vec<u8>, Vec<u8>>>, HashMap<H, Vec<u8>>)>);
impl<H> Database<H> for MemDb<H>
where H: Clone + Send + Sync + Eq + PartialEq + Default + std::hash::Hash
{
fn commit(&self, transaction: Transaction<H>) {
let mut s = self.0.write();
for change in transaction.0.into_iter() {
match change {
Change::Set(col, key, value) => { s.0.entry(col).or_default().insert(key, value); },
Change::Remove(col, key) => { s.0.entry(col).or_default().remove(&key); },
Change::Store(hash, preimage) => { s.1.insert(hash, preimage); },
Change::Release(hash) => { s.1.remove(&hash); },
}
}
}
fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
let s = self.0.read();
s.0.get(&col).and_then(|c| c.get(key).cloned())
}
fn lookup(&self, hash: &H) -> Option<Vec<u8>> {
let s = self.0.read();
s.1.get(hash).cloned()
}
}
impl<H> MemDb<H>
where H: Clone + Send + Sync + Eq + PartialEq + Default + std::hash::Hash
{
/// Create a new instance
pub fn new() -> Self {
MemDb::default()
}
/// Count number of values in a column
pub fn count(&self, col: ColumnId) -> usize {
let s = self.0.read();
s.0.get(&col).map(|c| c.len()).unwrap_or(0)
}
}
+1
View File
@@ -22,6 +22,7 @@ js-sys = "0.3.34"
wasm-bindgen = "0.2.57"
wasm-bindgen-futures = "0.4.7"
kvdb-web = "0.5"
sp-database = { version = "2.0.0-dev", path = "../../primitives/database" }
sc-informant = { version = "0.8.0-dev", path = "../../client/informant" }
sc-service = { version = "0.8.0-dev", path = "../../client/service", default-features = false }
sc-network = { path = "../../client/network", version = "0.8.0-dev"}
+1 -1
View File
@@ -70,7 +70,7 @@ where
info!("Opening Indexed DB database '{}'...", name);
let db = kvdb_web::Database::open(name, 10).await?;
DatabaseConfig::Custom(Arc::new(db))
DatabaseConfig::Custom(sp_database::as_database(db))
},
keystore: KeystoreConfig::InMemory,
default_heap_pages: Default::default(),
@@ -26,5 +26,5 @@ structopt = "0.3.8"
codec = { version = "1.3.0", package = "parity-scale-codec" }
[features]
default = ["rocksdb"]
rocksdb = ["sc-client-db/kvdb-rocksdb"]
default = ["db"]
db = ["sc-client-db/kvdb-rocksdb", "sc-client-db/parity-db"]