diff --git a/Cargo.lock b/Cargo.lock index 5571b9d3bb..8a19a3d7de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,6 +359,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alloc-no-stdlib" version = "2.0.3" @@ -761,12 +767,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atomic-waker" @@ -2191,6 +2194,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ctor" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed5fff0d93c7559121e9c72bf9c242295869396255071ff2cb1617147b608c5" +dependencies = [ + "quote 1.0.28", + "syn 2.0.22", +] + [[package]] name = "ctr" version = "0.6.0" @@ -4472,6 +4485,13 @@ dependencies = [ "version_check", ] +[[package]] +name = "immutable_env" +version = "0.1.0" +dependencies = [ + "ctor 0.2.3", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -5623,7 +5643,9 @@ dependencies = [ "glob", "libc", "libz-sys", + "lz4-sys", "tikv-jemalloc-sys", + "zstd-sys", ] [[package]] @@ -6657,6 +6679,30 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +[[package]] +name = "ouroboros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d813b7b31a82efae94bd30ffaac09aec85efc18db2d5ec3aead1a220ee954351" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a56f651b4dd45ae3ac3d260ced32eaf0620cddaae5f26c69b554a9016594726" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.22", +] + [[package]] name = "output_vt100" version = "0.1.3" @@ -8372,22 +8418,31 @@ dependencies = [ name = "phala-trie-storage" version = "0.1.0" dependencies = [ + "atomic", + "environmental", "hash-db", "hash256-std-hasher", "hex", "im", + "immutable_env", "impl-serde", "keccak-hasher", + "librocksdb-sys", "log", + "ouroboros", "parity-scale-codec", + "redb", + "rocksdb", "scale-info", "serde", + "serde_cbor", "serde_json", "sp-application-crypto", "sp-core", "sp-runtime", "sp-state-machine", "sp-trie", + "tempfile", "trie-db", ] @@ -8755,6 +8810,7 @@ dependencies = [ "pallet-contracts-primitives", "parity-scale-codec", "phala-crypto", + "phala-trie-storage", "pink", "pink-capi", "pink-extension-runtime", @@ -8983,7 +9039,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ - "ctor", + "ctor 0.1.22", "diff", "output_vt100", "yansi", @@ -9332,6 +9388,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "pyo3-build-config" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554db24f0b3c180a9c0b1268f91287ab3f17c162e15b54caaae5a6b3773396b0" +dependencies = [ + "once_cell", + "target-lexicon", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -9600,6 +9666,16 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "redb" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b48b6e8001eaa7ac3793fbfc7444ade76fc51efa3629dee8c66629425d39595" +dependencies = [ + "libc", + "pyo3-build-config", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -14408,7 +14484,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.6", - "rand 0.4.6", + "rand 0.8.5", "static_assertions", ] @@ -14654,7 +14730,7 @@ version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ - "ctor", + "ctor 0.1.22", "version_check", ] diff --git a/Cargo.toml b/Cargo.toml index 38f5b4b02a..ee0470a1df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/phala-types", "crates/phala-git-revision", "crates/prpc", + "crates/immutable_env", "crates/prpc-build", "crates/phactory", "crates/phactory/api", diff --git a/crates/immutable_env/Cargo.toml b/crates/immutable_env/Cargo.toml new file mode 100644 index 0000000000..df2a9d8923 --- /dev/null +++ b/crates/immutable_env/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "immutable_env" +version = "0.1.0" +edition = "2021" +description = "Immutable environment variables which are taken from the environment when the program starts and never change." +license = "MIT" +repository = "https://github.com/Phala-Network/phala-blockchain" +authors = ["Kevin Wang "] + +[dependencies] +ctor = "0.2" diff --git a/crates/immutable_env/src/lib.rs b/crates/immutable_env/src/lib.rs new file mode 100644 index 0000000000..8ad0e50837 --- /dev/null +++ b/crates/immutable_env/src/lib.rs @@ -0,0 +1,16 @@ +//! Immutable environment variables which are taken from the environment when the program starts +//! and never change. + +#[ctor::ctor] +static VALUES: std::collections::HashMap = std::env::vars().collect(); + +pub fn get(key: &str) -> Option { + VALUES.get(key).cloned() +} + +#[test] +fn it_works() { + let init = std::env::var("PATH").ok(); + std::env::set_var("PATH", "foo"); + assert_eq!(get("PATH"), init); +} \ No newline at end of file diff --git a/crates/phactory/src/contracts/pink.rs b/crates/phactory/src/contracts/pink.rs index 6b828214b2..c9d263256a 100644 --- a/crates/phactory/src/contracts/pink.rs +++ b/crates/phactory/src/contracts/pink.rs @@ -279,7 +279,7 @@ impl OCalls for RuntimeHandle<'_> { } fn storage_get(&self, key: Vec) -> Option> { - self.cluster.storage.get(&key).map(|(_rc, val)| val.clone()) + self.cluster.storage.get(&key).map(|(_rc, val)| val) } fn storage_commit(&mut self, _root: Hash, _changes: StorageChanges) { @@ -410,6 +410,11 @@ impl OCalls for RuntimeHandleMut<'_> { } fn storage_commit(&mut self, root: Hash, changes: StorageChanges) { + // The underlying RocksDB would reject to commit to a snapshot. + if !context::get().mode.is_transaction() { + return; + } + info!("commit state root: {:?}", root); self.cluster.storage.commit(root, changes); } @@ -641,15 +646,13 @@ impl Cluster { context::using(&mut ctx, move || { context::using_entry_contract(contract_id.clone(), || { let mut runtime = self.runtime_mut(log_handler); - if deposit > 0 { - runtime.deposit(origin.clone(), deposit); - } let args = TransactionArguments { origin, transfer, gas_limit: WEIGHT_REF_TIME_PER_SECOND * 10, gas_free: true, storage_deposit_limit: None, + deposit, }; let ink_result = runtime.call(contract_id, input_data, mode, args); let effects = if mode.is_estimating() { @@ -721,15 +724,13 @@ impl Cluster { let log_handler = context.log_handler.clone(); context::using(&mut ctx, move || { let mut runtime = self.runtime_mut(log_handler); - if deposit > 0 { - runtime.deposit(origin.clone(), deposit); - } let args = TransactionArguments { origin, transfer, gas_limit: WEIGHT_REF_TIME_PER_SECOND * 10, gas_free: true, storage_deposit_limit: None, + deposit, }; let ink_result = runtime.instantiate( code_hash, @@ -778,6 +779,7 @@ impl Cluster { gas_limit, gas_free, storage_deposit_limit, + deposit: 0, }; let mut runtime = self.runtime_mut(context.log_handler.clone()); diff --git a/crates/phactory/src/contracts/support.rs b/crates/phactory/src/contracts/support.rs index 425b046f52..bb976c0228 100644 --- a/crates/phactory/src/contracts/support.rs +++ b/crates/phactory/src/contracts/support.rs @@ -209,6 +209,7 @@ impl Contract { gas_free: false, storage_deposit_limit: None, gas_limit, + deposit: 0, }; let mut handle = env.contract_cluster.runtime_mut(env.log_handler.clone()); _ = handle.call( diff --git a/crates/phactory/src/prpc_service.rs b/crates/phactory/src/prpc_service.rs index 982ac84527..aec8775c29 100644 --- a/crates/phactory/src/prpc_service.rs +++ b/crates/phactory/src/prpc_service.rs @@ -1324,10 +1324,13 @@ impl PhactoryApi for Rpc // the singleton phactory. This way, we can avoid blocking the RPC server. info!("Cloning Phactory to do RCU dispatch..."); let cloned = phactory.clone(); + // Because the underlying KVDB of the cloned phactory is readonly, so we use the original + // phactory to dispatch the blocks. + let origin = std::mem::replace(&mut **phactory, cloned); // We set rcu_dispatching = true to avoid a reentrant call to dispatch_blocks which // would cause a state inconsistency. phactory.rcu_dispatching = true; - cloned + origin }; // Start to dispatch the blocks with the cloned phactory without locking the singleton phactory. info!("Unlocked Phactory, dispatching blocks..."); diff --git a/crates/phactory/src/storage.rs b/crates/phactory/src/storage.rs index d6fd9592fb..aa7084e302 100644 --- a/crates/phactory/src/storage.rs +++ b/crates/phactory/src/storage.rs @@ -67,6 +67,12 @@ mod storage_ext { } impl ChainStorage { + pub fn default_memdb() -> Self { + Self { + trie_storage: TrieStorage::default_memdb(), + } + } + fn get_raw(&self, key: impl AsRef<[u8]>) -> Option> { self.trie_storage.get(key) } diff --git a/crates/phactory/src/system/gk.rs b/crates/phactory/src/system/gk.rs index 56f7f81d86..a1bafaaae0 100644 --- a/crates/phactory/src/system/gk.rs +++ b/crates/phactory/src/system/gk.rs @@ -1609,7 +1609,7 @@ pub mod tests { fn with_block(block_number: chain::BlockNumber, call: impl FnOnce(&BlockInfo)) { // GK never use the storage ATM. - let storage = crate::ChainStorage::default(); + let storage = crate::ChainStorage::default_memdb(); let mut recv_mq = phala_mq::MessageDispatcher::new(); let mut send_mq = phala_mq::MessageSendQueue::new(); let block = BlockInfo { diff --git a/crates/phactory/src/system/mod.rs b/crates/phactory/src/system/mod.rs index 16eaee73ee..2fc26b7d92 100644 --- a/crates/phactory/src/system/mod.rs +++ b/crates/phactory/src/system/mod.rs @@ -1293,6 +1293,7 @@ impl System { gas_limit, gas_free: false, storage_deposit_limit, + deposit: 0, }; let mut runtime = cluster.runtime_mut(log_handler.clone()); let _result = runtime.instantiate( diff --git a/crates/phala-trie-storage/Cargo.toml b/crates/phala-trie-storage/Cargo.toml index cfd7ce0fce..b3f62f580a 100644 --- a/crates/phala-trie-storage/Cargo.toml +++ b/crates/phala-trie-storage/Cargo.toml @@ -18,7 +18,14 @@ serde = { version = "1.0", default-features = false, features = ["derive", "allo hash-db = "0.16.0" trie-db = "0.27.1" im = { version = "15", features = ["serde"] } +rocksdb = "0.21" +librocksdb-sys = "0.11.0" log = "0.4" +environmental = "1" +immutable_env = { version = "0.1.0", path = "../immutable_env" } +atomic = "0.5.3" +redb = "1" +ouroboros = "0.17" [dev-dependencies] sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } @@ -28,6 +35,8 @@ hex = "0.4" serde_json = "1.0" impl-serde = "0.4.0" keccak-hasher = "0.16.0" +serde_cbor = "0.11.2" +tempfile = "3" [features] default = ["serde"] diff --git a/crates/phala-trie-storage/src/adapter.rs b/crates/phala-trie-storage/src/adapter.rs new file mode 100644 index 0000000000..9eae50e98f --- /dev/null +++ b/crates/phala-trie-storage/src/adapter.rs @@ -0,0 +1,63 @@ +use sp_state_machine::{DefaultError, TrieBackendStorage}; + +use super::*; + +pub enum DatabaseAdapter { + Memory(MemoryDB), + RocksDB(HashRocksDB), + Redb(HashRedb), +} + +impl DatabaseAdapter { + pub fn default_memdb() -> Self { + Self::Memory(MemoryDB::default()) + } + + pub fn load(mdb: MemoryDB, db_type: crate::DBType) -> Self { + match db_type { + DBType::Memory => Self::Memory(mdb), + DBType::RocksDB => Self::RocksDB(HashRocksDB::load(mdb)), + DBType::Redb => Self::Redb(HashRedb::load(mdb)), + } + } + + pub fn new(typ: crate::DBType) -> Self { + match typ { + DBType::Memory => Self::Memory(Default::default()), + DBType::RocksDB => Self::RocksDB(Default::default()), + DBType::Redb => Self::Redb(Default::default()), + } + } + + pub fn snapshot(&self) -> Self { + match self { + DatabaseAdapter::Memory(mdb) => DatabaseAdapter::Memory(mdb.clone()), + DatabaseAdapter::RocksDB(kvdb) => DatabaseAdapter::RocksDB(kvdb.snapshot()), + DatabaseAdapter::Redb(db) => DatabaseAdapter::Redb(db.snapshot()), + } + } + + pub fn consolidate_mdb(&mut self, other: MemoryDB) { + match self { + DatabaseAdapter::Memory(mdb) => mdb.consolidate(other), + DatabaseAdapter::RocksDB(kvdb) => kvdb.consolidate(other), + DatabaseAdapter::Redb(db) => db.consolidate(other), + } + } +} + +impl TrieBackendStorage for DatabaseAdapter { + type Overlay = MemoryDB; + + fn get( + &self, + key: &H::Out, + prefix: hash_db::Prefix, + ) -> Result, DefaultError> { + match self { + DatabaseAdapter::Memory(mdb) => mdb.get(key, prefix), + DatabaseAdapter::RocksDB(kvdb) => kvdb.get(key, prefix), + DatabaseAdapter::Redb(db) => db.get(key, prefix), + } + } +} diff --git a/crates/phala-trie-storage/src/kvdb/cache_dir.rs b/crates/phala-trie-storage/src/kvdb/cache_dir.rs new file mode 100644 index 0000000000..265f7224eb --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/cache_dir.rs @@ -0,0 +1,32 @@ +//! The `cache_dir` is employed to store the trie db. +//! In gramine execution, the PHACTORY_TRIE_CACHE_PATH is hardcoded into the manifest. +//! For unittest execution, `test_cached_path` is set to a temporary directory. +//! In non-SGX test pruntime execution, the default cache directory is used. +//! +//! The `test_cached_path` represents a global cache directory specifically for unit tests. +//! +//! The `with` function, primarily used within unit tests, takes a cache directory +//! and a function as parameters. It temporarily switches the cache directory to +//! the specified one, executes the supplied function, and then restores the +//! original cache directory. +//! +//! The `get` function retrieves the path of the cache directory. It first attempts to +//! fetch the path from the `test_cached_path` variable. If that fails, +//! it turns to the `PHACTORY_TRIE_CACHE_PATH` environment variable. In the event of +//! both attempts failing, it defaults to "data/protected_files/caches". + +// Global cache directory for unit tests. +environmental::environmental!(test_cached_path: String); + +#[cfg(test)] +pub(crate) fn with(cache_dir: &str, f: impl FnOnce() -> T) -> T { + let mut cache_dir = cache_dir.to_string(); + test_cached_path::using(&mut cache_dir, f) +} + +pub(crate) fn get() -> String { + let test_path = test_cached_path::with(|cache_dir| cache_dir.clone()); + test_path + .or_else(|| immutable_env::get("PHACTORY_TRIE_CACHE_PATH")) + .unwrap_or_else(|| "data/protected_files/caches".to_string()) +} diff --git a/crates/phala-trie-storage/src/kvdb/hashdb.rs b/crates/phala-trie-storage/src/kvdb/hashdb.rs new file mode 100644 index 0000000000..10200a0316 --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/hashdb.rs @@ -0,0 +1,122 @@ +use hash_db::Hasher; + +use parity_scale_codec::Decode; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sp_state_machine::{DefaultError, TrieBackendStorage}; + +use crate::{ + memdb::{HashKey, KeyFunction}, + MemoryDB, +}; + +use super::{traits::KvStorage, DecodedDBValue}; + +pub struct HashDB { + inner: DB, + hashed_null_node: H::Out, + null_node_data: Vec, +} + +impl Default for HashDB { + fn default() -> Self { + Self::new() + } +} + +impl HashDB { + pub fn from_inner(inner: DB) -> Self { + let mdb = MemoryDB::::default(); + Self { + inner, + hashed_null_node: mdb.hashed_null_node, + null_node_data: mdb.null_node_data, + } + } + + pub fn new() -> Self { + Self::from_inner(DB::new()) + } + + pub fn consolidate(&self, mut other: MemoryDB) { + self.inner.consolidate(other.drain().into_iter()); + } + + pub fn load(mdb: MemoryDB) -> Self { + let kvdb = Self::new(); + kvdb.consolidate(mdb); + kvdb + } + + pub fn snapshot(&self) -> Self { + Self { + inner: self.inner.snapshot(), + hashed_null_node: self.hashed_null_node, + null_node_data: self.null_node_data.clone(), + } + } + + fn get_decoded(&self, key: &H::Out) -> Option { + let raw = self.inner.get(key.as_ref())?; + Some(DecodedDBValue::decode(&mut &raw[..]).expect("Failed to decode DB value")) + } +} + +impl TrieBackendStorage for HashDB { + type Overlay = MemoryDB; + + fn get(&self, key: &H::Out, prefix: hash_db::Prefix) -> Result>, DefaultError> { + if key == &self.hashed_null_node { + return Ok(Some(self.null_node_data.clone())); + } + let key = HashKey::::key(key, prefix); + match self.get_decoded(&key) { + None => Ok(None), + Some((d, rc)) => { + if rc > 0 { + Ok(Some(d)) + } else { + Ok(None) + } + } + } + } +} + +impl Serialize for HashDB { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + super::serializing::serialize_as_seq(&self.inner, serializer) + } +} +impl<'de, H: Hasher, DB: KvStorage> Deserialize<'de> for HashDB { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(HashDB::from_inner(super::serializing::deserialize_from_seq::< + _, + DB, + H, + >(deserializer)?)) + } +} + +#[test] +fn serde_hashdb_works() { + use hash_db::HashDB; + use sp_core::Blake2Hasher; + use parity_scale_codec::Encode; + use crate::HashRocksDB; + + let cache_dir = tempfile::tempdir().unwrap(); + super::with_cache_dir(cache_dir.path().to_str().unwrap(), || { + let mut mdb = MemoryDB::default(); + mdb.insert((&[], None), &(b"foo".to_vec(), 2).encode()); + let db = HashRocksDB::::new(); + db.consolidate(mdb); + let cobr = serde_cbor::to_vec(&db).unwrap(); + let _: HashRocksDB = serde_cbor::from_slice(&cobr).unwrap(); + }); +} diff --git a/crates/phala-trie-storage/src/kvdb/mod.rs b/crates/phala-trie-storage/src/kvdb/mod.rs new file mode 100644 index 0000000000..1f1aa5d502 --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/mod.rs @@ -0,0 +1,32 @@ +use parity_scale_codec::{Decode, Error}; + +pub use self::rocksdb::RocksDB; +pub use self::redb::Redb; + +pub use hashdb::HashDB; +pub type HashRocksDB = HashDB; +pub type HashRedb = HashDB; + +#[cfg(test)] +pub(crate) use cache_dir::with as with_cache_dir; + +mod hashdb; +mod rocksdb; +mod redb; + +mod serializing; +mod cache_dir; + +pub mod traits; + +pub type DecodedDBValue = (Vec, i32); + +fn decode_value(value: Option>) -> Result, Error> { + match value { + None => Ok(None), + Some(value) => { + let (d, rc): (Vec, i32) = Decode::decode(&mut &value[..])?; + Ok(Some((d, rc))) + } + } +} diff --git a/crates/phala-trie-storage/src/kvdb/redb.rs b/crates/phala-trie-storage/src/kvdb/redb.rs new file mode 100644 index 0000000000..5996cca24d --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/redb.rs @@ -0,0 +1,206 @@ +use std::sync::{atomic::AtomicUsize, Arc}; + +use atomic::Ordering; +use log::info; +use ouroboros::self_referencing; +use redb::{ + Database, ReadOnlyTable, ReadTransaction, ReadableTable, TableDefinition, WriteTransaction, +}; +use serde::{Deserialize, Serialize}; + +use super::traits::{KvStorage, Transaction}; + +const TABLE: TableDefinition<&[u8], &[u8]> = TableDefinition::new("pairs"); + +#[self_referencing] +pub struct OwnedTransaction { + db: Arc, + #[borrows(db)] + #[covariant] + tx: ReadTransaction<'this>, +} + +#[self_referencing] +pub struct OwnedTable { + tx: OwnedTransaction, + #[borrows(tx)] + #[covariant] + table: ReadOnlyTable<'this, &'static [u8], &'static [u8]>, +} + +impl OwnedTransaction { + fn into_table(self) -> OwnedTable { + OwnedTableBuilder { + tx: self, + table_builder: |tx| { + tx.borrow_tx() + .open_table(TABLE) + .expect("Failed to open table in redb") + }, + } + .build() + } +} + +pub enum Redb { + Database { db: Arc, sn: usize }, + Snapshot(Arc), +} + +impl KvStorage for Redb { + type Transaction<'a> = WriteTransaction<'a>; + + fn new() -> Self + where + Self: Sized, + { + let (db, sn) = create_db(); + let db = Arc::new(db); + Redb::Database { db, sn } + } + + fn snapshot(&self) -> Self + where + Self: Sized, + { + match self { + Redb::Database { db, .. } => { + let tx = OwnedTransactionBuilder { + db: db.clone(), + tx_builder: |db| { + db.begin_read() + .expect("Failed to create read transaction for snapshot") + }, + } + .build(); + Redb::Snapshot(Arc::new(tx.into_table())) + } + Redb::Snapshot(snap) => Redb::Snapshot(snap.clone()), + } + } + + fn get(&self, key: &[u8]) -> Option> { + match self { + Redb::Database { db, .. } => { + let tx = db + .begin_read() + .expect("Failed to create read transaction for get"); + let table = tx.open_table(TABLE).expect("Failed to open table in redb"); + table + .get(key) + .expect("Failed to get value from database, the database may be corrupted") + .map(|v| v.value().to_vec()) + } + Redb::Snapshot(snap) => { + let table = snap.borrow_table(); + table + .get(key) + .expect("Failed to get value from snapshot, the database may be corrupted") + .map(|v| v.value().to_vec()) + } + } + } + + fn transaction(&self) -> Self::Transaction<'_> { + let Self::Database { db, sn: _ } = self else { + panic!("transaction() called on snapshot") + }; + db.begin_write() + .expect("Failed to create write transaction") + } + + fn for_each(&self, mut cb: impl FnMut(&[u8], &[u8])) { + match self { + Redb::Database { db, .. } => { + let tx = db + .begin_read() + .expect("Failed to create read transaction for iteration"); + let table = tx + .open_table(TABLE) + .expect("Failed to open table for iteration"); + for result in table.iter().expect("Failed to iterate over redb") { + let (k, v) = result.expect("Failed to iter over redb"); + cb(k.value(), v.value()); + } + } + Redb::Snapshot(snap) => { + let table = snap.borrow_table(); + for result in table.iter().expect("Failed to iterate over redb snapshot") { + let (k, v) = result.expect("Failed to iter over redb snapshot"); + cb(k.value(), v.value()); + } + } + } + } +} + +impl Default for Redb { + fn default() -> Self { + Self::new() + } +} + +impl Serialize for Redb { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + super::serializing::serialize_as_map(self, serializer) + } +} + +impl<'de> Deserialize<'de> for Redb { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + super::serializing::deserialize_from_map(deserializer) + } +} + +impl Transaction for WriteTransaction<'_> { + fn get(&self, key: &[u8]) -> Option> { + self.open_table(TABLE) + .expect("Failed to open table in redb") + .get(key) + .expect("Failed to get value from database, the database may be corrupted") + .map(|v| v.value().to_vec()) + } + + fn put(&self, key: &[u8], value: &[u8]) { + self.open_table(TABLE) + .expect("Failed to open table in redb") + .insert(key, value) + .expect("Failed to put value into database, the database may be corrupted"); + } + + fn delete(&self, key: &[u8]) { + self.open_table(TABLE) + .expect("Failed to open table in redb") + .remove(key) + .expect("Failed to delete value from database, the database may be corrupted"); + } + + fn commit(self) { + self.commit().expect("Failed to commit transaction"); + } +} + +fn create_db() -> (Database, usize) { + let cache_dir = super::cache_dir::get(); + static NEXT_SN: AtomicUsize = AtomicUsize::new(0); + let sn = NEXT_SN.fetch_add(1, Ordering::SeqCst); + if sn == 0 { + if std::path::Path::new(&cache_dir).exists() { + info!("Removing cache folder: {}", &cache_dir); + std::fs::remove_dir_all(&cache_dir).expect("Failed to remove cache folder"); + } + std::fs::create_dir_all(&cache_dir).expect("Failed to create cache folder"); + } + let path = format!("{cache_dir}/cache-{sn}.redb",); + let db = Database::builder() + .set_cache_size(1024 * 1024 * 128) + .create(path) + .expect("Failed to create database"); + (db, sn) +} diff --git a/crates/phala-trie-storage/src/kvdb/rocksdb.rs b/crates/phala-trie-storage/src/kvdb/rocksdb.rs new file mode 100644 index 0000000000..5a3bd88e2c --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/rocksdb.rs @@ -0,0 +1,192 @@ +pub use snapshot::Snapshot; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +use log::info; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use librocksdb_sys as ffi; +use rocksdb::{Error as DBError, IteratorMode, MultiThreaded, Options, Transaction, TransactionDB}; + +mod snapshot; + +type Database = Arc>; + +use super::traits::KvStorage; +pub enum RocksDB { + Database { db: Database, sn: usize }, + Snapshot(Arc), +} + +impl RocksDB { + pub fn new() -> Self { + let (db, sn) = create_db(); + Self::Database { + db: Arc::new(db), + sn, + } + } + + pub fn consolidate>(&self, other: impl Iterator, i32))>) { + KvStorage::consolidate(self, other) + } + + #[cfg(test)] + pub(crate) fn put(&self, key: &[u8], value: &[u8]) -> Result<(), DBError> { + let Self::Database { db, .. } = self else { + panic!("Put on a snapshot") + }; + db.put(key, value) + } + + #[cfg(test)] + pub(crate) fn delete(&self, key: &[u8]) -> Result<(), DBError> { + let Self::Database { db, .. } = self else { + panic!("Delete on a snapshot") + }; + db.delete(key) + } + + fn get(&self, key: &[u8]) -> Result>, DBError> { + match self { + RocksDB::Database { db, .. } => db.get(key), + RocksDB::Snapshot(snap) => snap.get(key), + } + } +} + +impl Default for RocksDB { + fn default() -> Self { + Self::new() + } +} + +impl KvStorage for RocksDB { + type Transaction<'a> = Transaction<'a, TransactionDB>; + + fn transaction(&self) -> Self::Transaction<'_> { + let RocksDB::Database { db, .. } = self else { + panic!("Consolidate on a snapshot") + }; + db.transaction() + } + + fn new() -> Self + where + Self: Sized, + { + Self::new() + } + + fn for_each(&self, mut cb: impl FnMut(&[u8], &[u8])) { + /// To deduplicate the two match arms + macro_rules! db_iter { + ($iter: expr) => { + for item in $iter { + let (k, v) = item.expect("Failed to iterate pairs over Database"); + cb(&k, &v); + } + }; + } + match &self { + RocksDB::Database { db, .. } => { + db_iter!(db.iterator(IteratorMode::Start)); + } + RocksDB::Snapshot(snap) => { + db_iter!(snap.iterator(IteratorMode::Start)); + } + } + } + + fn snapshot(&self) -> Self + where + Self: Sized, + { + match self { + Self::Database { db, .. } => Self::Snapshot(Arc::new(Snapshot::new(db.clone()))), + Self::Snapshot(snap) => Self::Snapshot(snap.clone()), + } + } + + fn get(&self, key: &[u8]) -> Option> { + self.get(key).expect("Failed to get key") + } +} + +impl<'a> super::traits::Transaction for Transaction<'a, TransactionDB> { + fn get(&self, key: &[u8]) -> Option> { + self.get(key).expect("Failed to get key") + } + + fn put(&self, key: &[u8], value: &[u8]) { + self.put(key, value).expect("Failed to put key"); + } + + fn delete(&self, key: &[u8]) { + self.delete(key).expect("Failed to delete key"); + } + + fn commit(self) { + self.commit().expect("Failed to commit transaction"); + } +} + +fn create_db() -> (TransactionDB, usize) { + let cache_dir = super::cache_dir::get(); + static NEXT_SN: AtomicUsize = AtomicUsize::new(0); + let sn = NEXT_SN.fetch_add(1, Ordering::SeqCst); + if sn == 0 && std::path::Path::new(&cache_dir).exists() { + info!("Removing cache folder: {}", &cache_dir); + std::fs::remove_dir_all(&cache_dir).expect("Failed to remove cache folder"); + } + let mut options = Options::default(); + options.set_max_open_files(256); + options.create_if_missing(true); + options.set_error_if_exists(true); + let path = format!("{cache_dir}/cache-{sn}.rocksdb",); + let db = TransactionDB::open(&options, &Default::default(), path).expect("Faile to open KVDB"); + (db, sn) +} + +impl Serialize for RocksDB { + fn serialize(&self, serializer: S) -> Result { + super::serializing::serialize_as_map(self, serializer) + } +} + +impl<'de> Deserialize<'de> for RocksDB { + fn deserialize>(deserializer: D) -> Result { + super::serializing::deserialize_from_map(deserializer) + } +} + +#[test] +fn serde_works() { + use parity_scale_codec::Encode; + + let tmp_dir = tempfile::tempdir().unwrap(); + super::with_cache_dir(tmp_dir.path().to_str().unwrap(), || { + let db = RocksDB::new(); + db.put(b"foo", &(vec![42u8], 1i32).encode()).unwrap(); + let ser = serde_cbor::to_vec(&db).unwrap(); + let de: RocksDB = serde_cbor::from_slice(&ser).unwrap(); + assert_eq!(de.get_decoded(b"foo"), Some((vec![42], 1))); + }); +} + +#[test] +fn snapshot_works() { + let tmp_dir = tempfile::tempdir().unwrap(); + super::with_cache_dir(tmp_dir.path().to_str().unwrap(), || { + let db = RocksDB::new(); + db.put(b"foo", b"bar").unwrap(); + assert_eq!(db.get(b"foo").unwrap().unwrap(), b"bar"); + let snapshot = db.snapshot(); + assert_eq!(snapshot.get(b"foo").unwrap().unwrap(), b"bar"); + db.delete(b"foo").unwrap(); + assert_eq!(db.get(b"foo").unwrap(), None); + assert_eq!(snapshot.get(b"foo").unwrap().unwrap(), b"bar"); + }); +} diff --git a/crates/phala-trie-storage/src/kvdb/rocksdb/snapshot.rs b/crates/phala-trie-storage/src/kvdb/rocksdb/snapshot.rs new file mode 100644 index 0000000000..81f525d1c6 --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/rocksdb/snapshot.rs @@ -0,0 +1,122 @@ +use rocksdb::{ + AsColumnFamilyRef, DBAccess, DBIteratorWithThreadMode, IteratorMode, SnapshotWithThreadMode, +}; + +use super::{ffi, DBError, Database}; + +pub struct Snapshot { + db: Database, + snapshot_ptr: *const ffi::rocksdb_snapshot_t, +} + +unsafe impl Send for Snapshot {} +unsafe impl Sync for Snapshot {} + +impl Drop for Snapshot { + fn drop(&mut self) { + unsafe { self.db.release_snapshot(self.snapshot_ptr) } + } +} + +impl Snapshot { + pub fn new(db: Database) -> Self { + Self { + snapshot_ptr: unsafe { db.create_snapshot() }, + db, + } + } + + pub fn iterator(&self, mode: IteratorMode) -> DBIteratorWithThreadMode { + self.rocks_snapshot().iterator(mode) + } + + pub fn get(&self, key: &[u8]) -> Result>, DBError> { + self.rocks_snapshot().get(key) + } + + fn rocks_snapshot(&self) -> SnapshotWithThreadMode { + SnapshotWithThreadMode::new(self) + } +} + +impl DBAccess for Snapshot { + unsafe fn create_snapshot(&self) -> *const ffi::rocksdb_snapshot_t { + self.snapshot_ptr + } + + unsafe fn release_snapshot(&self, _snapshot: *const ffi::rocksdb_snapshot_t) {} + + unsafe fn create_iterator( + &self, + readopts: &rocksdb::ReadOptions, + ) -> *mut ffi::rocksdb_iterator_t { + self.db.create_iterator(readopts) + } + + unsafe fn create_iterator_cf( + &self, + cf_handle: *mut ffi::rocksdb_column_family_handle_t, + readopts: &rocksdb::ReadOptions, + ) -> *mut ffi::rocksdb_iterator_t { + self.db.create_iterator_cf(cf_handle, readopts) + } + + fn get_opt>( + &self, + key: K, + readopts: &rocksdb::ReadOptions, + ) -> Result>, rocksdb::Error> { + self.db.get_opt(key, readopts) + } + + fn get_cf_opt>( + &self, + cf: &impl AsColumnFamilyRef, + key: K, + readopts: &rocksdb::ReadOptions, + ) -> Result>, rocksdb::Error> { + self.db.get_cf_opt(cf, key, readopts) + } + + fn get_pinned_opt>( + &self, + key: K, + readopts: &rocksdb::ReadOptions, + ) -> Result, rocksdb::Error> { + self.db.get_pinned_opt(key, readopts) + } + + fn get_pinned_cf_opt>( + &self, + cf: &impl AsColumnFamilyRef, + key: K, + readopts: &rocksdb::ReadOptions, + ) -> Result, rocksdb::Error> { + self.db.get_pinned_cf_opt(cf, key, readopts) + } + + fn multi_get_opt( + &self, + keys: I, + readopts: &rocksdb::ReadOptions, + ) -> Vec>, rocksdb::Error>> + where + K: AsRef<[u8]>, + I: IntoIterator, + { + self.db.multi_get_opt(keys, readopts) + } + + fn multi_get_cf_opt<'b, K, I, W>( + &self, + keys_cf: I, + readopts: &rocksdb::ReadOptions, + ) -> Vec>, rocksdb::Error>> + where + K: AsRef<[u8]>, + I: IntoIterator, + W: AsColumnFamilyRef + 'b, + { + self.db.multi_get_cf_opt(keys_cf, readopts) + } +} diff --git a/crates/phala-trie-storage/src/kvdb/serializing.rs b/crates/phala-trie-storage/src/kvdb/serializing.rs new file mode 100644 index 0000000000..e2971d49fa --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/serializing.rs @@ -0,0 +1,107 @@ +use core::marker::PhantomData; +use hash_db::Hasher; +use parity_scale_codec::{Decode, Encode}; +use serde::{ + de::{MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq}, + Deserializer, Serializer, +}; + +use crate::kvdb::{traits::Transaction, DecodedDBValue}; + +use super::traits::KvStorage; + +pub(crate) fn deserialize_from_seq<'de, D, DB: KvStorage, H: Hasher>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + struct VecVisitor { + marker: PhantomData<(H, DB)>, + } + impl<'de, H: Hasher, DB: KvStorage> Visitor<'de> for VecVisitor { + type Value = DB; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let db = DB::new(); + let transaction = db.transaction(); + while let Some((value, rc)) = seq.next_element::()? { + let key = H::hash(&value); + transaction.put(key.as_ref(), &(value, rc).encode()); + } + transaction.commit(); + Ok(db) + } + } + deserializer.deserialize_seq(VecVisitor { + marker: PhantomData::<(H, DB)>, + }) +} + +pub(crate) fn serialize_as_seq(db: &DB, serializer: S) -> Result +where + S: Serializer, + DB: KvStorage, +{ + let mut seq = serializer.serialize_seq(None)?; + db.for_each(|_k, v| { + let element: DecodedDBValue = + Decode::decode(&mut &v[..]).expect("Failed to decode db value"); + seq.serialize_element(&element) + .expect("Failed to serialize element"); + }); + seq.end() +} + +pub(crate) fn deserialize_from_map<'de, D, DB: KvStorage>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + struct MapVisitor(PhantomData); + impl<'de, DB: KvStorage> Visitor<'de> for MapVisitor { + type Value = DB; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut seq: A) -> Result + where + A: MapAccess<'de>, + { + let db = DB::new(); + let transaction = db.transaction(); + while let Some((key, (rc, value))) = seq.next_entry::, (i32, Vec)>()? { + transaction.put(key.as_ref(), &(value, rc).encode()); + } + transaction.commit(); + Ok(db) + } + } + deserializer.deserialize_map(MapVisitor(PhantomData::)) +} + +pub(crate) fn serialize_as_map(db: &DB, serializer: S) -> Result +where + S: Serializer, + DB: KvStorage, +{ + let mut ser = serializer.serialize_map(None)?; + db.for_each(|key, v| { + let (value, rc): DecodedDBValue = + Decode::decode(&mut &v[..]).expect("Failed to decode db value"); + ser.serialize_entry(key, &(rc, value)) + .expect("Failed to serialize element"); + }); + ser.end() +} diff --git a/crates/phala-trie-storage/src/kvdb/traits.rs b/crates/phala-trie-storage/src/kvdb/traits.rs new file mode 100644 index 0000000000..2f424a47e1 --- /dev/null +++ b/crates/phala-trie-storage/src/kvdb/traits.rs @@ -0,0 +1,61 @@ +use parity_scale_codec::Encode; + +use super::decode_value; + +pub trait Transaction { + fn get(&self, key: &[u8]) -> Option>; + fn put(&self, key: &[u8], value: &[u8]); + fn delete(&self, key: &[u8]); + fn commit(self); +} + +pub trait KvStorage { + type Transaction<'a>: Transaction + where + Self: 'a; + + fn new() -> Self + where + Self: Sized; + fn snapshot(&self) -> Self + where + Self: Sized; + fn get(&self, key: &[u8]) -> Option>; + fn transaction(&self) -> Self::Transaction<'_>; + fn for_each(&self, cb: impl FnMut(&[u8], &[u8])); + fn get_decoded(&self, key: &[u8]) -> Option { + decode_value(self.get(key)).expect("Failed to decode value") + } + fn consolidate>(&self, other: impl Iterator, i32))>) { + let transaction = self.transaction(); + for (key, (value, rc)) in other { + if rc == 0 { + continue; + } + + let key = key.as_ref(); + + let pv = transaction.get(key); + let pv = decode_value(pv).expect("Failed to decode value"); + + let raw_value = match pv { + None => (value, rc), + Some((mut d, mut orc)) => { + if orc <= 0 { + d = value; + } + + orc += rc; + + if orc == 0 { + transaction.delete(key); + continue; + } + (d, orc) + } + }; + transaction.put(key, &raw_value.encode()); + } + transaction.commit(); + } +} diff --git a/crates/phala-trie-storage/src/lib.rs b/crates/phala-trie-storage/src/lib.rs index fc1886458c..e0ac305b03 100644 --- a/crates/phala-trie-storage/src/lib.rs +++ b/crates/phala-trie-storage/src/lib.rs @@ -1,20 +1,41 @@ #[cfg(feature = "serde")] pub mod ser; +mod adapter; +mod kvdb; mod memdb; +#[cfg(test)] +mod tests; + +use adapter::DatabaseAdapter; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::iter::FromIterator; +use atomic::Atomic; +use std::sync::atomic::Ordering; + use parity_scale_codec::Codec; use sp_core::storage::ChildInfo; use sp_core::Hasher; use sp_state_machine::{Backend, IterArgs, TrieBackend, TrieBackendBuilder}; use sp_trie::{trie_types::TrieDBMutBuilderV0 as TrieDBMutBuilder, TrieMut}; +pub use kvdb::{ + traits::{KvStorage, Transaction}, + HashRedb, HashRocksDB, Redb, RocksDB, +}; pub use memdb::GenericMemoryDB as MemoryDB; +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[repr(u32)] +pub enum DBType { + Memory, + RocksDB, + Redb, +} + /// Storage key. pub type StorageKey = Vec; @@ -28,16 +49,29 @@ pub type StorageCollection = Vec<(StorageKey, Option)>; pub type ChildStorageCollection = Vec<(StorageKey, StorageCollection)>; pub type InMemoryBackend = TrieBackend, H>; -pub struct TrieStorage(InMemoryBackend) +pub type KvdbBackend = TrieBackend, H>; + +pub struct TrieStorage(KvdbBackend) where H::Out: Ord; +static STORAGE_BACKEND: Atomic = Atomic::::new(DBType::Memory); + +pub fn default_db_type() -> DBType { + STORAGE_BACKEND.load(Ordering::Relaxed) +} + +pub fn set_default_db_type(t: DBType) { + STORAGE_BACKEND.store(t, Ordering::Relaxed); +} + impl Default for TrieStorage where H::Out: Codec + Ord, { fn default() -> Self { - Self(TrieBackendBuilder::new(Default::default(), Default::default()).build()) + let backend = DatabaseAdapter::new(default_db_type()); + Self(TrieBackendBuilder::new(backend, Default::default()).build()) } } @@ -45,6 +79,10 @@ impl TrieStorage where H::Out: Codec + Ord, { + pub fn default_memdb() -> Self { + Self(TrieBackendBuilder::new(DatabaseAdapter::default_memdb(), Default::default()).build()) + } + pub fn snapshot(&self) -> Self { Self(clone_trie_backend(&self.0)) } @@ -52,12 +90,12 @@ where pub fn load_trie_backend( pairs: impl Iterator, impl AsRef<[u8]>)>, -) -> TrieBackend, H> +) -> KvdbBackend where H::Out: Codec + Ord, { let mut root = Default::default(); - let mut mdb = Default::default(); + let mut mdb: MemoryDB = Default::default(); { let mut trie_db = TrieDBMutBuilder::new(&mut mdb, &mut root).build(); for (key, value) in pairs { @@ -66,12 +104,13 @@ where } } } - TrieBackendBuilder::new(mdb, root).build() + let storage = DatabaseAdapter::load(mdb, default_db_type()); + TrieBackendBuilder::new(storage, root).build() } #[cfg(feature = "serde")] pub fn serialize_trie_backend( - trie: &TrieBackend, H>, + trie: &KvdbBackend, serializer: S, ) -> Result where @@ -79,36 +118,51 @@ where S: Serializer, { let root = trie.root(); - let kvs = trie.backend_storage(); - (root, ser::SerAsSeq(kvs)).serialize(serializer) + let db = trie.backend_storage(); + match db { + DatabaseAdapter::Memory(mdb) => (root, ser::SerAsSeq(mdb)).serialize(serializer), + DatabaseAdapter::RocksDB(db) => (root, db).serialize(serializer), + DatabaseAdapter::Redb(db) => (root, db).serialize(serializer), + } } #[cfg(feature = "serde")] pub fn deserialize_trie_backend<'de, H: Hasher, De>( deserializer: De, -) -> Result, H>, De::Error> +) -> Result, De::Error> where H::Out: Codec + Deserialize<'de> + Ord, De: Deserializer<'de>, { - let (root, kvs): (H::Out, Vec<(Vec, i32)>) = Deserialize::deserialize(deserializer)?; - let mdb = MemoryDB::from_inner( - kvs.into_iter() - .map(|(data, rc)| (H::hash(data.as_ref()), (data, rc))) - .collect(), - ); - let backend = TrieBackendBuilder::new(mdb, root).build(); - Ok(backend) + let (root, db) = match default_db_type() { + DBType::Memory => { + let (root, kvs): (H::Out, Vec<(Vec, i32)>) = + Deserialize::deserialize(deserializer)?; + let mdb = MemoryDB::from_inner( + kvs.into_iter() + .map(|(data, rc)| (H::hash(data.as_ref()), (data, rc))) + .collect(), + ); + (root, DatabaseAdapter::Memory(mdb)) + } + DBType::RocksDB => { + let (root, db): (H::Out, HashRocksDB) = Deserialize::deserialize(deserializer)?; + (root, DatabaseAdapter::RocksDB(db)) + } + DBType::Redb => { + let (root, db): (H::Out, HashRedb) = Deserialize::deserialize(deserializer)?; + (root, DatabaseAdapter::Redb(db)) + } + }; + Ok(TrieBackendBuilder::new(db, root).build()) } -pub fn clone_trie_backend( - trie: &TrieBackend, H>, -) -> TrieBackend, H> +pub fn clone_trie_backend(trie: &KvdbBackend) -> KvdbBackend where H::Out: Codec + Ord, { let root = trie.root(); - let mdb = trie.backend_storage().clone(); + let mdb = trie.backend_storage().snapshot(); TrieBackendBuilder::new(mdb, *root).build() } @@ -153,8 +207,10 @@ where /// Apply storage changes calculated from `calc_root_if_changes`. pub fn apply_changes(&mut self, root: H::Out, transaction: MemoryDB) { - let mut storage = core::mem::take(self).0.into_storage(); - storage.consolidate(transaction); + let mut storage = core::mem::replace(self, Self::default_memdb()) + .0 + .into_storage(); + storage.consolidate_mdb(transaction); let _ = core::mem::replace(&mut self.0, TrieBackendBuilder::new(storage, root).build()); } @@ -189,12 +245,14 @@ where .collect() } - pub fn as_trie_backend(&self) -> &InMemoryBackend { + pub fn as_trie_backend(&self) -> &KvdbBackend { &self.0 } pub fn set_root(&mut self, root: H::Out) { - let storage = core::mem::take(self).0.into_storage(); + let storage = core::mem::replace(self, Self::default_memdb()) + .0 + .into_storage(); let _ = core::mem::replace(&mut self.0, TrieBackendBuilder::new(storage, root).build()); } @@ -206,6 +264,7 @@ where let hash = storage.insert(hash_db::EMPTY_PREFIX, &value); log::debug!("Loaded proof {:?}", hash); } + let storage = DatabaseAdapter::Memory(storage); let _ = core::mem::replace(&mut self.0, TrieBackendBuilder::new(storage, root).build()); } } diff --git a/crates/phala-trie-storage/src/memdb.rs b/crates/phala-trie-storage/src/memdb.rs index c448604985..baf69bbcb5 100644 --- a/crates/phala-trie-storage/src/memdb.rs +++ b/crates/phala-trie-storage/src/memdb.rs @@ -65,8 +65,8 @@ where KF: KeyFunction, { data: Map, - hashed_null_node: H::Out, - null_node_data: T, + pub(crate) hashed_null_node: H::Out, + pub(crate) null_node_data: T, _kf: PhantomData, } diff --git a/crates/phala-trie-storage/tests/test_state_root.rs b/crates/phala-trie-storage/src/tests/mod.rs similarity index 73% rename from crates/phala-trie-storage/tests/test_state_root.rs rename to crates/phala-trie-storage/src/tests/mod.rs index 43fef68cdd..6838e2b516 100644 --- a/crates/phala-trie-storage/tests/test_state_root.rs +++ b/crates/phala-trie-storage/src/tests/mod.rs @@ -1,4 +1,4 @@ -use phala_trie_storage::*; +use crate::*; use serde::{Deserialize, Serialize}; use sp_core::Hasher; use sp_runtime::{traits::Hash, StateVersion}; @@ -100,28 +100,34 @@ fn map_storage_collection(collection: TestStorageCollection) -> StorageCollectio #[test] fn test_genesis_root() { - let trie = load_genesis_trie(); - let roots = load_roots(); - assert_eq!(format!("{:?}", trie.root()), roots[0]); + let cache_dir = tempfile::tempdir().unwrap(); + kvdb::with_cache_dir(cache_dir.path().to_str().unwrap(), || { + let trie = load_genesis_trie(); + let roots = load_roots(); + assert_eq!(format!("{:?}", trie.root()), roots[0]); + }); } #[test] fn test_apply_main_changes() { - let mut trie = load_genesis_trie(); - let changes = load_changes(); - let roots = load_roots(); - - for (number, change) in changes.into_iter().skip(1).take(30).enumerate() { - let main_storage_changes = map_storage_collection(change.main_storage_changes); - let child_storage_changes: Vec<_> = change - .child_storage_changes - .into_iter() - .map(|(k, v)| (k.0, map_storage_collection(v))) - .collect(); - - let (root, trans) = - trie.calc_root_if_changes(&main_storage_changes, &child_storage_changes); - trie.apply_changes(root, trans); - assert_eq!(format!("{:?}", trie.root()), roots[number + 1]); - } + let cache_dir = tempfile::tempdir().unwrap(); + kvdb::with_cache_dir(cache_dir.path().to_str().unwrap(), || { + let mut trie = load_genesis_trie(); + let changes = load_changes(); + let roots = load_roots(); + + for (number, change) in changes.into_iter().skip(1).take(30).enumerate() { + let main_storage_changes = map_storage_collection(change.main_storage_changes); + let child_storage_changes: Vec<_> = change + .child_storage_changes + .into_iter() + .map(|(k, v)| (k.0, map_storage_collection(v))) + .collect(); + + let (root, trans) = + trie.calc_root_if_changes(&main_storage_changes, &child_storage_changes); + trie.apply_changes(root, trans); + assert_eq!(format!("{:?}", trie.root()), roots[number + 1]); + } + }); } diff --git a/crates/pink/capi/src/v1/mod.rs b/crates/pink/capi/src/v1/mod.rs index 1381c95dd6..5f1327be58 100644 --- a/crates/pink/capi/src/v1/mod.rs +++ b/crates/pink/capi/src/v1/mod.rs @@ -48,6 +48,7 @@ pub mod ecall { pub gas_limit: Weight, pub gas_free: bool, pub storage_deposit_limit: Option, + pub deposit: Balance, } #[derive(Encode, Decode, Clone, Debug)] diff --git a/crates/pink/runner/Cargo.toml b/crates/pink/runner/Cargo.toml index c32811919d..f654c4470c 100644 --- a/crates/pink/runner/Cargo.toml +++ b/crates/pink/runner/Cargo.toml @@ -15,6 +15,7 @@ libc = "0.2" log = "0.4" tracing = "0.1" environmental = "1" +phala-trie-storage = { path = "../../phala-trie-storage" } [dev-dependencies] pink = { path = "../runtime" } diff --git a/crates/pink/runner/src/storage.rs b/crates/pink/runner/src/storage.rs index 40c5382360..bb1c5a87f3 100644 --- a/crates/pink/runner/src/storage.rs +++ b/crates/pink/runner/src/storage.rs @@ -1,14 +1,123 @@ use im::OrdMap; +use phala_trie_storage::{default_db_type, DBType, KvStorage, Redb, RocksDB}; use pink_capi::{types::Hash, v1::ocall::StorageChanges}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +enum StorageAdapter { + Redb(Redb), + RocksDB(RocksDB), + Memory(OrdMap, (i32, Vec)>), +} + +impl Default for StorageAdapter { + fn default() -> Self { + match default_db_type() { + DBType::Memory => StorageAdapter::Memory(Default::default()), + DBType::RocksDB => StorageAdapter::RocksDB(Default::default()), + DBType::Redb => StorageAdapter::Redb(Default::default()), + } + } +} + +impl Serialize for StorageAdapter { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + StorageAdapter::Memory(mdb) => mdb.serialize(serializer), + StorageAdapter::RocksDB(db) => db.serialize(serializer), + StorageAdapter::Redb(db) => db.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for StorageAdapter { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match default_db_type() { + DBType::Memory => Deserialize::deserialize(deserializer).map(StorageAdapter::Memory), + DBType::RocksDB => Deserialize::deserialize(deserializer).map(StorageAdapter::RocksDB), + DBType::Redb => Deserialize::deserialize(deserializer).map(StorageAdapter::Redb), + } + } +} + +impl Clone for StorageAdapter { + fn clone(&self) -> Self { + match self { + StorageAdapter::Memory(map) => StorageAdapter::Memory(map.clone()), + StorageAdapter::RocksDB(kvdb) => StorageAdapter::RocksDB(kvdb.snapshot()), + StorageAdapter::Redb(db) => StorageAdapter::Redb(db.snapshot()), + } + } +} + +impl StorageAdapter { + fn get(&self, key: &[u8]) -> Option<(Vec, i32)> { + match self { + StorageAdapter::Memory(mdb) => { + let (rc, v) = mdb.get(key).cloned()?; + Some((v, rc)) + } + StorageAdapter::RocksDB(kvdb) => kvdb.get_decoded(key), + StorageAdapter::Redb(db) => db.get_decoded(key), + } + } + + fn consolidate>(&mut self, other: impl Iterator, i32))>) { + match self { + StorageAdapter::Memory(mdb) => { + for (key, (value, rc)) in other { + if rc == 0 { + continue; + } + + let key = key.as_ref(); + + let pv = mdb.get(key).cloned(); + + let raw_value = match pv { + None => (rc, value), + Some((mut orc, mut d)) => { + if orc <= 0 { + d = value; + } + + orc += rc; + + if orc == 0 { + mdb.remove(key); + continue; + } + (orc, d) + } + }; + mdb.insert(key.to_vec(), raw_value); + } + } + StorageAdapter::RocksDB(kvdb) => kvdb.consolidate(other), + StorageAdapter::Redb(db) => db.consolidate(other), + } + } +} #[derive(Clone, Default, Serialize, Deserialize)] pub struct ClusterStorage { root: Option, - kv_store: OrdMap, (i32, Vec)>, + kv_store: StorageAdapter, } impl ClusterStorage { + pub fn default_memdb() -> Self { + Self { + root: None, + kv_store: StorageAdapter::Memory(Default::default()), + } + } + pub fn root(&self) -> Option { self.root } @@ -17,34 +126,13 @@ impl ClusterStorage { self.root = Some(root); } - pub fn get(&self, key: &[u8]) -> Option<&(i32, Vec)> { - self.kv_store.get(key) - } - - fn update(&mut self, key: Vec, value: Vec, rc: i32) { - if rc == 0 { - return; - } - match self.kv_store.get_mut(&key) { - Some((ref mut old_rc, ref mut old_value)) => { - *old_rc += rc; - if rc > 0 { - *old_value = value; - } - if *old_rc == 0 { - self.kv_store.remove(&key); - } - } - None => { - self.kv_store.insert(key, (rc, value)); - } - } + pub fn get(&self, key: &[u8]) -> Option<(i32, Vec)> { + let (value, rc) = self.kv_store.get(key)?; + Some((rc, value)) } pub fn commit(&mut self, root: Hash, changes: StorageChanges) { - for (key, (value, rc)) in changes { - self.update(key, value, rc); - } + self.kv_store.consolidate(changes.into_iter()); self.set_root(root); } } diff --git a/crates/pink/runner/tests/test_pink_contract.rs b/crates/pink/runner/tests/test_pink_contract.rs index d258ecad71..4d8bc51f9c 100644 --- a/crates/pink/runner/tests/test_pink_contract.rs +++ b/crates/pink/runner/tests/test_pink_contract.rs @@ -20,6 +20,7 @@ fn tx_args() -> TransactionArguments { gas_limit: Weight::MAX, gas_free: true, storage_deposit_limit: None, + deposit: 0, } } @@ -298,7 +299,7 @@ mod test_cluster { impl TestCluster { pub fn for_test() -> Self { - let storage = ClusterStorage::default(); + let storage = ClusterStorage::default_memdb(); let context = ExecContext { mode: ExecutionMode::Transaction, block_number: 1, @@ -415,7 +416,7 @@ mod test_cluster { } fn storage_get(&self, key: Vec) -> Option> { - self.storage.get(&key).map(|(_rc, val)| val.clone()) + self.storage.get(&key).map(|(_rc, val)| val) } fn storage_commit(&mut self, root: Hash, changes: StorageChanges) { diff --git a/crates/pink/runtime/src/capi/ecall_impl.rs b/crates/pink/runtime/src/capi/ecall_impl.rs index 6e9349b7fd..e8d35ddc01 100644 --- a/crates/pink/runtime/src/capi/ecall_impl.rs +++ b/crates/pink/runtime/src/capi/ecall_impl.rs @@ -89,6 +89,7 @@ impl ecall::ECalls for ECallImpl { gas_limit: Weight::MAX, gas_free: true, storage_deposit_limit: None, + deposit: 0, }; let result = crate::contract::instantiate( code_hash, @@ -191,6 +192,7 @@ impl ecall::ECalls for ECallImpl { tx_args: TransactionArguments, ) -> Vec { let tx_args = sanitize_args(tx_args, mode); + handle_deposit(&tx_args); let address = PalletPink::contract_address(&tx_args.origin, &code_hash, &input_data, &salt); let result = crate::contract::instantiate(code_hash, input_data, salt, mode, tx_args); if !result.debug_message.is_empty() { @@ -239,6 +241,7 @@ impl ecall::ECalls for ECallImpl { tx_args: TransactionArguments, ) -> Vec { let tx_args = sanitize_args(tx_args, mode); + handle_deposit(&tx_args); let result = crate::contract::bare_call(address.clone(), input_data, mode, tx_args); if !result.debug_message.is_empty() { let message = String::from_utf8_lossy(&result.debug_message).into_owned(); @@ -293,3 +296,9 @@ fn sanitize_args(mut args: TransactionArguments, mode: ExecutionMode) -> Transac args.gas_limit = args.gas_limit.min(gas_limit); args } + +fn handle_deposit(args: &TransactionArguments) { + if args.deposit > 0 { + let _ = PalletBalances::deposit_creating(&args.origin, args.deposit); + } +} diff --git a/crates/pink/runtime/src/contract.rs b/crates/pink/runtime/src/contract.rs index d59a2c2d7b..7af6b187b7 100644 --- a/crates/pink/runtime/src/contract.rs +++ b/crates/pink/runtime/src/contract.rs @@ -152,6 +152,7 @@ pub fn instantiate( gas_limit, storage_deposit_limit, gas_free, + deposit: _, } = args; let gas_limit = Weight::from_parts(gas_limit, 0).set_proof_size(u64::MAX); let result = contract_tx(origin.clone(), gas_limit, gas_free, move || { @@ -193,6 +194,7 @@ pub fn bare_call( gas_limit, gas_free, storage_deposit_limit, + deposit: _, } = tx_args; let gas_limit = Weight::from_parts(gas_limit, 0).set_proof_size(u64::MAX); let determinism = if mode.deterministic_required() { diff --git a/e2e/src/fullstack.js b/e2e/src/fullstack.js index 7ec6eff152..33a727dd66 100644 --- a/e2e/src/fullstack.js +++ b/e2e/src/fullstack.js @@ -59,6 +59,7 @@ describe('A full stack', function () { keyring = new Keyring({ type: 'sr25519', ss58Format: 30 }); alice = keyring.addFromUri('//Alice'); bob = keyring.addFromUri('//Bob'); + console.log(`The test datadir is at ${cluster.tmpPath}`); }); after(async function () { @@ -443,7 +444,7 @@ describe('A full stack', function () { assert.isTrue(await checkUntil(async () => { const info = await pruntime[2].getInfo(); return info.system?.gatekeeper.role == 2; // 2: GatekeeperRole.Active in protobuf - }, 1000)) + }, 3000)) // Step 3: wait a few more blocks and ensure there are no conflicts in gatekeepers' shared mq }); @@ -1333,6 +1334,7 @@ function newPRuntime(teePort, tmpPath, name = 'app') { '--cores=0', // Disable benchmark '--checkpoint-interval=5', '--port', teePort.toString(), + '--db=redb', ]; let bin = pRuntimeBin; if (inSgx) { diff --git a/standalone/pruntime/Cargo.lock b/standalone/pruntime/Cargo.lock index fe1bcc20a8..577aed7cca 100644 --- a/standalone/pruntime/Cargo.lock +++ b/standalone/pruntime/Cargo.lock @@ -99,6 +99,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "android_system_properties" version = "0.1.4" @@ -346,12 +352,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atomic-waker" @@ -453,6 +456,27 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "prettyplease 0.2.9", + "proc-macro2 1.0.56", + "quote 1.0.26", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.16", +] + [[package]] name = "bitcoin" version = "0.29.2" @@ -665,6 +689,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cache-padded" version = "1.2.0" @@ -676,6 +711,9 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] [[package]] name = "cexpr" @@ -789,7 +827,7 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2 1.0.56", "quote 1.0.26", "syn 2.0.16", @@ -1106,6 +1144,16 @@ dependencies = [ "syn 1.0.99", ] +[[package]] +name = "ctor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4056f63fce3b82d852c3da92b08ea59959890813a7f4ce9c0ff85b10cf301b" +dependencies = [ + "quote 1.0.26", + "syn 2.0.16", +] + [[package]] name = "curve25519-dalek" version = "2.1.3" @@ -2320,9 +2368,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -2548,6 +2596,13 @@ dependencies = [ "version_check", ] +[[package]] +name = "immutable_env" +version = "0.1.0" +dependencies = [ + "ctor 0.2.0", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -2623,7 +2678,7 @@ dependencies = [ "derive_more", "either", "env_logger", - "heck 0.4.0", + "heck 0.4.1", "impl-serde", "ink_ir", "ink_primitives", @@ -2855,6 +2910,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -2932,6 +2996,22 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" +[[package]] +name = "librocksdb-sys" +version = "0.11.0+8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" +dependencies = [ + "bindgen 0.65.1", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", + "zstd-sys", +] + [[package]] name = "libsecp256k1" version = "0.7.1" @@ -2980,6 +3060,17 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libz-sys" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linregress" version = "0.5.1" @@ -3050,6 +3141,16 @@ dependencies = [ "syn 1.0.99", ] +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "mach" version = "0.3.2" @@ -3440,6 +3541,30 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "ouroboros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d813b7b31a82efae94bd30ffaac09aec85efc18db2d5ec3aead1a220ee954351" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a56f651b4dd45ae3ac3d260ced32eaf0620cddaae5f26c69b554a9016594726" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2 1.0.56", + "quote 1.0.26", + "syn 2.0.16", +] + [[package]] name = "page_size" version = "0.4.2" @@ -4759,10 +4884,17 @@ dependencies = [ name = "phala-trie-storage" version = "0.1.0" dependencies = [ + "atomic", + "environmental", "hash-db", "im", + "immutable_env", + "librocksdb-sys", "log", + "ouroboros", "parity-scale-codec", + "redb", + "rocksdb", "scale-info", "serde", "sp-core", @@ -4859,7 +4991,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" name = "pink-capi" version = "0.1.0" dependencies = [ - "bindgen", + "bindgen 0.64.0", "parity-scale-codec", "pink-extension", "pink-macro", @@ -4884,7 +5016,7 @@ dependencies = [ name = "pink-extension-macro" version = "0.4.2" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "ink_ir", "proc-macro-crate", "proc-macro2 1.0.56", @@ -4915,7 +5047,7 @@ dependencies = [ name = "pink-macro" version = "0.1.1" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-crate", "proc-macro2 1.0.56", "syn 1.0.99", @@ -4931,6 +5063,7 @@ dependencies = [ "log", "once_cell", "phala-crypto", + "phala-trie-storage", "pink-capi", "pink-extension-runtime", "serde", @@ -4948,6 +5081,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "polling" version = "2.2.0" @@ -4977,6 +5116,16 @@ dependencies = [ "syn 1.0.99", ] +[[package]] +name = "prettyplease" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" +dependencies = [ + "proc-macro2 1.0.56", + "syn 2.0.16", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -5133,13 +5282,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c828f93f5ca4826f97fedcbd3f9a536c16b12cff3dbbb4a007f932bbad95b12" dependencies = [ "bytes", - "heck 0.4.0", + "heck 0.4.1", "itertools", "lazy_static", "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.21", "prost 0.11.8", "prost-types 0.11.8", "regex", @@ -5211,7 +5360,7 @@ version = "0.1.0" dependencies = [ "derive_wrapper", "either", - "heck 0.4.0", + "heck 0.4.1", "itertools", "log", "multimap", @@ -5244,7 +5393,9 @@ dependencies = [ "phala-git-revision", "phala-rocket-middleware", "phala-sanitized-logger", + "phala-trie-storage", "phala-types", + "pink-runner", "reqwest", "reqwest-env-proxy", "rocket", @@ -5297,6 +5448,16 @@ dependencies = [ "unicase", ] +[[package]] +name = "pyo3-build-config" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554db24f0b3c180a9c0b1268f91287ab3f17c162e15b54caaae5a6b3773396b0" +dependencies = [ + "once_cell", + "target-lexicon", +] + [[package]] name = "quote" version = "0.6.13" @@ -5474,6 +5635,16 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "redb" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b48b6e8001eaa7ac3793fbfc7444ade76fc51efa3629dee8c66629425d39595" +dependencies = [ + "libc", + "pyo3-build-config", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -5804,6 +5975,16 @@ dependencies = [ "uncased", ] +[[package]] +name = "rocksdb" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -6353,7 +6534,7 @@ dependencies = [ name = "sidevm-macro" version = "0.1.1" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-crate", "proc-macro2 1.0.56", "syn 1.0.99", @@ -7190,7 +7371,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2 1.0.56", "quote 1.0.26", "rustversion", @@ -7935,10 +8116,16 @@ version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ - "ctor", + "ctor 0.1.23", "version_check", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version" version = "3.0.0" @@ -8985,3 +9172,14 @@ dependencies = [ "syn 1.0.99", "synstructure 0.12.6", ] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/standalone/pruntime/Cargo.toml b/standalone/pruntime/Cargo.toml index 8d9a88038f..54169b6018 100644 --- a/standalone/pruntime/Cargo.toml +++ b/standalone/pruntime/Cargo.toml @@ -39,6 +39,8 @@ phala-rocket-middleware = { path = "../../crates/phala-rocket-middleware" } phala-types = { path = "../../crates/phala-types", features = ["enable_serde", "sgx"] } phala-git-revision = { path = "../../crates/phala-git-revision" } phala-clap-parsers = { path = "../../crates/phala-clap-parsers" } +phala-trie-storage = { path = "../../crates/phala-trie-storage", default-features = false } +pink-runner = { path = "../../crates/pink/runner", default-features = false } sgx-api-lite = { path = "../../crates/sgx-api-lite" } tracing = "0.1" hex_fmt = "0.3.0" diff --git a/standalone/pruntime/gramine-build/Makefile b/standalone/pruntime/gramine-build/Makefile index 0487c38304..a96aa927ad 100644 --- a/standalone/pruntime/gramine-build/Makefile +++ b/standalone/pruntime/gramine-build/Makefile @@ -93,6 +93,7 @@ ${LIBOS}: rsync -r --no-links ${GRAMINE_RUNTIME_DIR}/ ${RUNTIME_DIR}/lib ifeq ($(USE_MUSL),0) cp -rfL ${HOST_LIBC_DIR}/libgcc_s.so.1 ${RUNTIME_DIR}/lib/ + cp -rfL ${HOST_LIBC_DIR}/libstdc++.so.6 ${RUNTIME_DIR}/lib/ endif ifeq ($(SGX),1) cp -rfL ${GRAMINE_DIR}/sgx ${RUNTIME_DIR}/ diff --git a/standalone/pruntime/gramine-build/pruntime.manifest.template b/standalone/pruntime/gramine-build/pruntime.manifest.template index 60ff93c87f..d65c396b2d 100644 --- a/standalone/pruntime/gramine-build/pruntime.manifest.template +++ b/standalone/pruntime/gramine-build/pruntime.manifest.template @@ -1,4 +1,6 @@ {% set pink_runtime_mount_dir = "/pink-runtime" %} +{% set protected_dir = "/data/protected_files" %} +{% set trie_cache_dir = protected_dir + "/caches" %} [libos] entrypoint = "pruntime" @@ -30,6 +32,7 @@ serving the async tasks. #} ROCKET_WORKERS = "8" PINK_RUNTIME_PATH = "{{ pink_runtime_mount_dir }}" +PHACTORY_TRIE_CACHE_PATH = "{{ trie_cache_dir }}" {# When enable, the pruntime will only logs with targets in the hardcoded whitelist. So, log from thirdparty crates will be ignored to avoid unexpected information leaks. @@ -45,7 +48,7 @@ path = "/lib" uri = "file:{{ libdir }}" [[fs.mounts]] -path = "/data/protected_files" +path = "{{ protected_dir }}" uri = "file:{{ seal_dir }}" type = "encrypted" key_name = "_sgx_mrenclave" diff --git a/standalone/pruntime/pink-runtimes/libpink.so.1.0 b/standalone/pruntime/pink-runtimes/libpink.so.1.0 index cd61bf1b93..40c2e95abb 100755 Binary files a/standalone/pruntime/pink-runtimes/libpink.so.1.0 and b/standalone/pruntime/pink-runtimes/libpink.so.1.0 differ diff --git a/standalone/pruntime/src/main.rs b/standalone/pruntime/src/main.rs index 398d887dcd..32d5a7a85f 100644 --- a/standalone/pruntime/src/main.rs +++ b/standalone/pruntime/src/main.rs @@ -7,6 +7,7 @@ use std::{env, thread}; use std::time::Duration; use clap::Parser; +use phala_trie_storage::DBType; use tracing::{error, info, info_span, Instrument}; use phactory_api::ecall_args::InitArgs; @@ -91,6 +92,27 @@ struct Args { /// The max retry times of getting the attestation report. #[arg(long, default_value = "1")] ra_max_retries: u32, + + /// Where to put the chain and contract state. + #[arg(long, default_value = "memory")] + db: ParsedDBType, +} + +#[derive(clap::ValueEnum, Clone, Copy, Debug)] +enum ParsedDBType { + Memory, + Rocksdb, + Redb, +} + +impl From for DBType { + fn from(db: ParsedDBType) -> Self { + match db { + ParsedDBType::Memory => DBType::Memory, + ParsedDBType::Rocksdb => DBType::RocksDB, + ParsedDBType::Redb => DBType::Redb, + } + } } #[rocket::main] @@ -106,7 +128,9 @@ async fn main() -> Result<(), rocket::Error> { async fn serve(sgx: bool) -> Result<(), rocket::Error> { let args = Args::parse(); - info!(sgx, "Starting pruntime..."); + info!(sgx, ?args, "Starting pruntime..."); + + phala_trie_storage::set_default_db_type(args.db.into()); let sealing_path; let storage_path;