diff --git a/Cargo.lock b/Cargo.lock index d90f54cf96..4f70cd7782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3168,6 +3168,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" + [[package]] name = "infer" version = "0.7.0" @@ -5266,9 +5272,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -6510,9 +6516,9 @@ checksum = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -7175,11 +7181,23 @@ dependencies = [ "wasmer", ] +[[package]] +name = "tari_dan_storage_lmdb" +version = "0.1.0" +dependencies = [ + "borsh", + "lmdb-zero", + "tari_dan_engine", + "tari_storage", + "tempfile", +] + [[package]] name = "tari_dan_storage_sqlite" version = "0.1.0" dependencies = [ "async-trait", + "borsh", "diesel", "diesel_migrations", "log", @@ -7468,6 +7486,25 @@ dependencies = [ "borsh", ] +[[package]] +name = "tari_template_lib" +version = "0.1.0" +dependencies = [ + "tari_template_abi", +] + +[[package]] +name = "tari_template_macros" +version = "0.1.0" +dependencies = [ + "indoc", + "proc-macro2", + "quote", + "syn", + "tari_template_abi", + "tari_template_lib", +] + [[package]] name = "tari_test_utils" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 2e2f08a6c9..85b4ce91ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,11 @@ members = [ "comms/dht", "comms/rpc_macros", "dan_layer/core", - "dan_layer/template_abi", + "dan_layer/storage_lmdb", "dan_layer/storage_sqlite", + "dan_layer/template_abi", + "dan_layer/template_lib", + "dan_layer/template_macros", "common_sqlite", "infrastructure/libtor", "infrastructure/metrics", diff --git a/dan_layer/engine/src/lib.rs b/dan_layer/engine/src/lib.rs index 857ae58fc4..7026a48d92 100644 --- a/dan_layer/engine/src/lib.rs +++ b/dan_layer/engine/src/lib.rs @@ -12,4 +12,5 @@ pub mod crypto; pub mod instruction; pub mod packager; pub mod runtime; +pub mod state_store; pub mod traits; diff --git a/dan_layer/engine/src/state_store/memory.rs b/dan_layer/engine/src/state_store/memory.rs new file mode 100644 index 0000000000..e7bc07f2d1 --- /dev/null +++ b/dan_layer/engine/src/state_store/memory.rs @@ -0,0 +1,148 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{ + collections::HashMap, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, +}; + +use anyhow::anyhow; + +use crate::state_store::{AtomicDb, StateReader, StateStoreError, StateWriter}; + +type InnerKvMap = HashMap, Vec>; + +#[derive(Debug, Clone, Default)] +pub struct MemoryStateStore { + state: Arc>, +} + +pub struct MemoryTransaction { + pending: InnerKvMap, + guard: T, +} + +impl<'a> AtomicDb<'a> for MemoryStateStore { + type Error = anyhow::Error; + type ReadAccess = MemoryTransaction>; + type WriteAccess = MemoryTransaction>; + + fn read_access(&'a self) -> Result { + let guard = self.state.read().map_err(|_| anyhow!("Failed to read state"))?; + + Ok(MemoryTransaction { + pending: HashMap::default(), + guard, + }) + } + + fn write_access(&'a self) -> Result { + let guard = self.state.write().map_err(|_| anyhow!("Failed to write state"))?; + + Ok(MemoryTransaction { + pending: HashMap::default(), + guard, + }) + } + + fn commit(&self, mut tx: Self::WriteAccess) -> Result<(), Self::Error> { + tx.guard.extend(tx.pending.into_iter()); + Ok(()) + } +} + +impl<'a> StateReader for MemoryTransaction> { + fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { + Ok(self.pending.get(key).cloned().or_else(|| self.guard.get(key).cloned())) + } +} + +impl<'a> StateReader for MemoryTransaction> { + fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { + Ok(self.pending.get(key).cloned().or_else(|| self.guard.get(key).cloned())) + } +} + +impl<'a> StateWriter for MemoryTransaction> { + fn set_state_raw(&mut self, key: &[u8], value: Vec) -> Result<(), StateStoreError> { + self.pending.insert(key.to_vec(), value); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use tari_template_abi::{Decode, Encode}; + + use super::*; + + #[test] + fn read_write() { + let store = MemoryStateStore::default(); + let mut access = store.write_access().unwrap(); + access.set_state_raw(b"abc", vec![1, 2, 3]).unwrap(); + let res = access.get_state_raw(b"abc").unwrap(); + assert_eq!(res, Some(vec![1, 2, 3])); + let res = access.get_state_raw(b"def").unwrap(); + assert_eq!(res, None); + } + + #[test] + fn read_write_rollback_commit() { + #[derive(Debug, Encode, Decode, PartialEq, Eq, Clone)] + struct UserData { + name: String, + age: u8, + } + + let user_data = UserData { + name: "Foo".to_string(), + age: 99, + }; + + let store = MemoryStateStore::default(); + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data.clone())); + let res = access.get_state::<_, UserData>(b"def").unwrap(); + assert_eq!(res, None); + // Drop without commit rolls back + } + + { + let access = store.read_access().unwrap(); + let res = access.get_state::<_, UserData>(b"abc").unwrap(); + assert_eq!(res, None); + } + + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + store.commit(access).unwrap(); + } + + let access = store.read_access().unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data)); + } +} diff --git a/dan_layer/engine/src/state_store/mod.rs b/dan_layer/engine/src/state_store/mod.rs new file mode 100644 index 0000000000..4ac589dc2b --- /dev/null +++ b/dan_layer/engine/src/state_store/mod.rs @@ -0,0 +1,79 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod memory; + +use std::{error::Error, io}; + +use tari_template_abi::{encode, Decode, Encode}; + +/// Abstraction for any database that has atomic read/write semantics. +pub trait AtomicDb<'a> { + type Error; + type ReadAccess: 'a; + type WriteAccess: 'a; + + /// Obtain read access to the underlying database + fn read_access(&'a self) -> Result; + + /// Obtain write access to the underlying database + fn write_access(&'a self) -> Result; + + fn commit(&self, tx: Self::WriteAccess) -> Result<(), Self::Error>; +} + +pub trait StateReader { + fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError>; + + fn get_state(&self, key: &K) -> Result, StateStoreError> { + let value = self.get_state_raw(&encode(key)?)?; + let value = value.map(|v| V::deserialize(&mut v.as_slice())).transpose()?; + Ok(value) + } +} + +pub trait StateWriter: StateReader { + fn set_state_raw(&mut self, key: &[u8], value: Vec) -> Result<(), StateStoreError>; + fn set_state(&mut self, key: &K, value: V) -> Result<(), StateStoreError> { + self.set_state_raw(&encode(key)?, encode(&value)?) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum StateStoreError { + #[error("Encoding error: {0}")] + EncodingError(#[from] io::Error), + #[error(transparent)] + Custom(anyhow::Error), + #[error("Error: {0}")] + CustomStr(String), +} + +impl StateStoreError { + pub fn custom(e: E) -> Self { + StateStoreError::Custom(e.into()) + } + + pub fn custom_str(e: &str) -> Self { + StateStoreError::CustomStr(e.to_string()) + } +} diff --git a/dan_layer/engine/tests/state/src/lib.rs b/dan_layer/engine/tests/state/src/lib.rs index ccaefd84a6..a8ed6e3f17 100644 --- a/dan_layer/engine/tests/state/src/lib.rs +++ b/dan_layer/engine/tests/state/src/lib.rs @@ -41,5 +41,4 @@ mod state_template { self.value } } - -} \ No newline at end of file +} diff --git a/dan_layer/storage_lmdb/Cargo.toml b/dan_layer/storage_lmdb/Cargo.toml new file mode 100644 index 0000000000..374c75b39f --- /dev/null +++ b/dan_layer/storage_lmdb/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tari_dan_storage_lmdb" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_dan_engine = { path = "../engine" } +tari_storage = { path = "../../infrastructure/storage" } + +borsh = "0.9.3" +lmdb-zero = "0.4.4" + + +[dev-dependencies] +tempfile = "3.3.0" \ No newline at end of file diff --git a/dan_layer/storage_lmdb/src/engine_state_store.rs b/dan_layer/storage_lmdb/src/engine_state_store.rs new file mode 100644 index 0000000000..3799332383 --- /dev/null +++ b/dan_layer/storage_lmdb/src/engine_state_store.rs @@ -0,0 +1,154 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ops::Deref, path::Path, sync::Arc}; + +use lmdb_zero::{db, put, ConstTransaction, LmdbResultExt, ReadTransaction, WriteTransaction}; +use tari_dan_engine::state_store::{AtomicDb, StateReader, StateStoreError, StateWriter}; +use tari_storage::lmdb_store::{DatabaseRef, LMDBBuilder}; + +pub struct LmdbTransaction { + db: DatabaseRef, + tx: T, +} + +pub struct LmdbStateStore { + pub env: Arc, + pub db: DatabaseRef, +} + +impl LmdbStateStore { + pub fn new>(path: P) -> Self { + let flags = db::CREATE; + std::fs::create_dir_all(&path).unwrap(); + + let store = LMDBBuilder::new() + .set_path(path) + // .set_env_config(config) + .set_max_number_of_databases(1) + .add_database("test_db", flags ) + .build().unwrap(); + + let handle = store.get_handle("test_db").unwrap(); + let db = handle.db(); + Self { env: store.env(), db } + } +} + +impl<'a> AtomicDb<'a> for LmdbStateStore { + type Error = lmdb_zero::Error; + type ReadAccess = LmdbTransaction>; + type WriteAccess = LmdbTransaction>; + + fn read_access(&'a self) -> Result { + let tx = ReadTransaction::new(self.env.clone())?; + + Ok(LmdbTransaction { + db: self.db.clone(), + tx, + }) + } + + fn write_access(&'a self) -> Result { + let tx = WriteTransaction::new(self.env.clone())?; + + Ok(LmdbTransaction { + db: self.db.clone(), + tx, + }) + } + + fn commit(&self, tx: Self::WriteAccess) -> Result<(), Self::Error> { + tx.tx.commit() + } +} + +impl<'a, T: Deref>> StateReader for LmdbTransaction { + fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { + let access = self.tx.access(); + access + .get::<_, [u8]>(&*self.db, key) + .map(|data| data.to_vec()) + .to_opt() + .map_err(StateStoreError::custom) + } +} + +impl<'a> StateWriter for LmdbTransaction> { + fn set_state_raw(&mut self, key: &[u8], value: Vec) -> Result<(), StateStoreError> { + let mut access = self.tx.access(); + access + .put(&*self.db, key, &value, put::Flags::empty()) + .map_err(StateStoreError::custom) + } +} + +#[cfg(test)] +mod tests { + + use borsh::{BorshDeserialize, BorshSerialize}; + use tempfile::tempdir; + + use super::*; + + #[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone)] + struct UserData { + name: String, + age: u8, + } + + #[test] + fn read_write_rollback_commit() { + let user_data = UserData { + name: "Foo".to_string(), + age: 99, + }; + + let path = tempdir().unwrap(); + let store = LmdbStateStore::new(&path); + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data.clone())); + let res = access.get_state::<_, UserData>(b"def").unwrap(); + assert_eq!(res, None); + // Drop without commit rolls back + } + + { + let access = store.read_access().unwrap(); + let res = access.get_state::<_, UserData>(b"abc").unwrap(); + assert_eq!(res, None); + } + + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + store.commit(access).unwrap(); + } + + let access = store.read_access().unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data)); + } +} diff --git a/dan_layer/storage_lmdb/src/lib.rs b/dan_layer/storage_lmdb/src/lib.rs new file mode 100644 index 0000000000..25fc6bb782 --- /dev/null +++ b/dan_layer/storage_lmdb/src/lib.rs @@ -0,0 +1,23 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod engine_state_store; diff --git a/dan_layer/storage_sqlite/Cargo.toml b/dan_layer/storage_sqlite/Cargo.toml index 5b53278270..cc0034c3c2 100644 --- a/dan_layer/storage_sqlite/Cargo.toml +++ b/dan_layer/storage_sqlite/Cargo.toml @@ -11,6 +11,7 @@ tari_common_types = { path = "../../base_layer/common_types" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.5" } tari_dan_engine = { path = "../engine" } +borsh = "0.9.3" diesel = { version = "1.4.8", default-features = false, features = ["sqlite"] } diesel_migrations = "1.4.0" thiserror = "1.0.30" diff --git a/dan_layer/storage_sqlite/src/engine_state_store.rs b/dan_layer/storage_sqlite/src/engine_state_store.rs new file mode 100644 index 0000000000..d2af765eb4 --- /dev/null +++ b/dan_layer/storage_sqlite/src/engine_state_store.rs @@ -0,0 +1,201 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use diesel::{connection::TransactionManager, Connection, OptionalExtension, QueryDsl, RunQueryDsl, SqliteConnection}; +use tari_dan_engine::state_store::{AtomicDb, StateReader, StateStoreError, StateWriter}; + +use crate::{diesel::ExpressionMethods, error::SqliteStorageError, schema::metadata}; +pub struct SqliteStateStore { + conn: SqliteConnection, +} + +impl SqliteStateStore { + pub fn try_connect(url: &str) -> Result { + let conn = SqliteConnection::establish(url)?; + conn.execute("PRAGMA foreign_keys = ON;") + .map_err(|source| SqliteStorageError::DieselError { + source, + operation: "set pragma".to_string(), + })?; + Ok(Self { conn }) + } + + pub fn migrate(&self) -> Result<(), SqliteStorageError> { + embed_migrations!("./migrations"); + embedded_migrations::run(&self.conn)?; + Ok(()) + } + + fn access(&self) -> Result, SqliteStorageError> { + let manager = self.conn.transaction_manager(); + manager + .begin_transaction(&self.conn) + .map_err(|err| SqliteStorageError::DieselError { + source: err, + operation: "begin transaction".to_string(), + })?; + Ok(SqliteTransaction::new(&self.conn)) + } +} + +impl<'a> AtomicDb<'a> for SqliteStateStore { + type Error = SqliteStorageError; + type ReadAccess = SqliteTransaction<'a>; + type WriteAccess = SqliteTransaction<'a>; + + fn read_access(&'a self) -> Result { + self.access() + } + + fn write_access(&'a self) -> Result { + self.access() + } + + fn commit(&self, tx: Self::WriteAccess) -> Result<(), Self::Error> { + self.conn + .transaction_manager() + .commit_transaction(tx.conn) + .map_err(|err| SqliteStorageError::DieselError { + source: err, + operation: "commit transaction".to_string(), + })?; + + Ok(()) + } +} + +pub struct SqliteTransaction<'a> { + conn: &'a SqliteConnection, +} + +impl<'a> SqliteTransaction<'a> { + fn new(conn: &'a SqliteConnection) -> Self { + Self { conn } + } +} + +impl<'a> StateReader for SqliteTransaction<'a> { + fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { + use crate::schema::metadata::dsl; + let val = dsl::metadata + .select(metadata::value) + .filter(metadata::key.eq(key)) + .first::>(self.conn) + .optional() + .map_err(|source| { + StateStoreError::custom(SqliteStorageError::DieselError { + source, + operation: "get state".to_string(), + }) + })?; + + Ok(val) + } +} + +impl<'a> StateWriter for SqliteTransaction<'a> { + fn set_state_raw(&mut self, key: &[u8], value: Vec) -> Result<(), StateStoreError> { + use crate::schema::metadata::dsl; + + // TODO: Check key exists without getting the data + match self.get_state_raw(key) { + Ok(Some(_)) => diesel::update(dsl::metadata.filter(metadata::key.eq(key))) + .set(metadata::value.eq(value)) + .execute(self.conn) + .map_err(|source| { + StateStoreError::custom(SqliteStorageError::DieselError { + source, + operation: "update::metadata".to_string(), + }) + })?, + Ok(None) => diesel::insert_into(metadata::table) + .values((metadata::key.eq(key), metadata::value.eq(value))) + .execute(self.conn) + .map_err(|source| { + StateStoreError::custom(SqliteStorageError::DieselError { + source, + operation: "insert::metadata".to_string(), + }) + })?, + Err(e) => return Err(e), + }; + + Ok(()) + } +} + +impl Drop for SqliteTransaction<'_> { + fn drop(&mut self) { + if let Err(err) = self.conn.transaction_manager().rollback_transaction(self.conn) { + log::error!("Error rolling back transaction: {:?}", err); + } + } +} + +#[cfg(test)] +mod tests { + use borsh::{BorshDeserialize, BorshSerialize}; + + use super::*; + + #[test] + fn read_write_rollback_commit() { + #[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Clone)] + struct UserData { + name: String, + age: u8, + } + + let user_data = UserData { + name: "Foo".to_string(), + age: 99, + }; + + let store = SqliteStateStore::try_connect(":memory:").unwrap(); + store.migrate().unwrap(); + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data.clone())); + let res = access.get_state::<_, UserData>(b"def").unwrap(); + assert_eq!(res, None); + // Drop without commit rolls back + } + + { + let access = store.read_access().unwrap(); + let res = access.get_state::<_, UserData>(b"abc").unwrap(); + assert_eq!(res, None); + } + + { + let mut access = store.write_access().unwrap(); + access.set_state(b"abc", user_data.clone()).unwrap(); + store.commit(access).unwrap(); + } + + let access = store.read_access().unwrap(); + let res = access.get_state(b"abc").unwrap(); + assert_eq!(res, Some(user_data)); + } +} diff --git a/dan_layer/storage_sqlite/src/lib.rs b/dan_layer/storage_sqlite/src/lib.rs index 472fe2d5f8..e62b629e17 100644 --- a/dan_layer/storage_sqlite/src/lib.rs +++ b/dan_layer/storage_sqlite/src/lib.rs @@ -36,6 +36,7 @@ pub use sqlite_db_factory::SqliteDbFactory; mod models; mod sqlite_state_db_backend_adapter; pub use sqlite_state_db_backend_adapter::SqliteStateDbBackendAdapter; +pub mod engine_state_store; pub mod global; mod sqlite_storage_service; diff --git a/dan_layer/template_abi/src/encoding.rs b/dan_layer/template_abi/src/encoding.rs index d5723270ee..1c40191527 100644 --- a/dan_layer/template_abi/src/encoding.rs +++ b/dan_layer/template_abi/src/encoding.rs @@ -42,6 +42,12 @@ pub fn encode_into(val: &T, buf: &mut Vec) -> io::Result<()> { val.serialize(buf) } +pub fn encode(val: &T) -> io::Result> { + let mut buf = Vec::with_capacity(512); + encode_into(val, &mut buf)?; + Ok(buf) +} + pub fn decode(mut input: &[u8]) -> io::Result { T::deserialize(&mut input) } diff --git a/dan_layer/template_abi/src/lib.rs b/dan_layer/template_abi/src/lib.rs index 3037149407..ddb74cdea0 100644 --- a/dan_layer/template_abi/src/lib.rs +++ b/dan_layer/template_abi/src/lib.rs @@ -31,7 +31,7 @@ pub mod ops; use std::collections::HashMap; pub use borsh::{self, BorshDeserialize as Decode, BorshSerialize as Encode}; -pub use encoding::{decode, decode_len, encode_into, encode_with_len}; +pub use encoding::{decode, decode_len, encode, encode_into, encode_with_len}; #[derive(Debug, Clone, Encode, Decode)] pub struct TemplateDef { diff --git a/dan_layer/template_lib/Cargo.lock b/dan_layer/template_lib/Cargo.lock deleted file mode 100644 index d110a9aad9..0000000000 --- a/dan_layer/template_lib/Cargo.lock +++ /dev/null @@ -1,182 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "borsh" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" -dependencies = [ - "borsh-derive", - "hashbrown", -] - -[[package]] -name = "borsh-derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate", - "proc-macro2", - "syn", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "once_cell" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" - -[[package]] -name = "state" -version = "0.1.0" -dependencies = [ - "tari_template_abi", -] - -[[package]] -name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tari_template_abi" -version = "0.1.0" -dependencies = [ - "borsh", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "unicode-ident" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/dan_layer/template_lib/Cargo.toml b/dan_layer/template_lib/Cargo.toml index b986932b1c..41e823a992 100644 --- a/dan_layer/template_lib/Cargo.toml +++ b/dan_layer/template_lib/Cargo.toml @@ -1,4 +1,3 @@ -[workspace] [package] name = "tari_template_lib" version = "0.1.0" diff --git a/dan_layer/template_lib/src/lib.rs b/dan_layer/template_lib/src/lib.rs index d5263379a3..f178a71471 100644 --- a/dan_layer/template_lib/src/lib.rs +++ b/dan_layer/template_lib/src/lib.rs @@ -33,7 +33,7 @@ pub mod models; // TODO: we should only use stdlib if the template dev needs to include it e.g. use core::mem when stdlib is not // available -use std::{collections::HashMap, mem, slice}; +use std::{collections::HashMap, mem, ptr::copy, slice}; use tari_template_abi::{encode_with_len, Decode, Encode, FunctionDef, TemplateDef}; @@ -49,6 +49,7 @@ pub fn generate_abi(template_name: String, functions: Vec) -> *mut type FunctionImpl = Box>) -> Vec>; +#[derive(Default)] pub struct TemplateImpl(HashMap); impl TemplateImpl { @@ -57,17 +58,21 @@ impl TemplateImpl { } pub fn add_function(&mut self, name: String, implementation: FunctionImpl) { - self.0.insert(name.clone(), implementation); + self.0.insert(name, implementation); } } -pub fn generate_main(call_info: *mut u8, call_info_len: usize, template_impl: TemplateImpl) -> *mut u8 { +/// Generate main +/// +/// # Safety +/// The caller must provide a valid pointer and length. +pub unsafe fn generate_main(call_info: *mut u8, call_info_len: usize, template_impl: TemplateImpl) -> *mut u8 { use tari_template_abi::{decode, CallInfo}; if call_info.is_null() { panic!("call_info is null"); } - let call_data = unsafe { slice::from_raw_parts(call_info, call_info_len) }; + let call_data = slice::from_raw_parts(call_info, call_info_len); let call_info: CallInfo = decode(call_data).unwrap(); // get the function @@ -96,9 +101,9 @@ pub fn call_engine(op: i32, input: &T) - } let slice = unsafe { slice::from_raw_parts(ptr as *const _, 4) }; - let len = decode_len(&slice).unwrap(); + let len = decode_len(slice).unwrap(); let slice = unsafe { slice::from_raw_parts(ptr.offset(4), len) }; - let ret = decode(&slice).unwrap(); + let ret = decode(slice).unwrap(); Some(ret) } @@ -119,3 +124,28 @@ pub fn call_debug>(data: T) { unsafe { debug(ptr, len) } } +/// Allocates a block of memory of length `len` bytes. +#[no_mangle] +pub extern "C" fn tari_alloc(len: u32) -> *mut u8 { + let cap = (len + 4) as usize; + let mut buf = Vec::::with_capacity(cap); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + unsafe { + copy(len.to_le_bytes().as_ptr(), ptr, 4); + } + ptr +} + +/// Frees a block of memory allocated by `tari_alloc`. +/// +/// # Safety +/// Caller must ensure that ptr must be a valid pointer to a block of memory allocated by `tari_alloc`. +#[no_mangle] +pub unsafe extern "C" fn tari_free(ptr: *mut u8) { + let mut len = [0u8; 4]; + copy(ptr, len.as_mut_ptr(), 4); + + let cap = (u32::from_le_bytes(len) + 4) as usize; + drop(Vec::::from_raw_parts(ptr, cap, cap)); +} diff --git a/dan_layer/template_lib/src/models/component.rs b/dan_layer/template_lib/src/models/component.rs index 6b377bc74c..585f90fc8b 100644 --- a/dan_layer/template_lib/src/models/component.rs +++ b/dan_layer/template_lib/src/models/component.rs @@ -23,7 +23,7 @@ // TODO: use the actual component id type pub type ComponentId = ([u8; 32], u32); -use tari_template_abi::{Decode, Encode, encode_with_len, ops::OP_CREATE_COMPONENT, CreateComponentArg}; +use tari_template_abi::{encode_with_len, ops::OP_CREATE_COMPONENT, CreateComponentArg, Decode, Encode}; use crate::call_engine; diff --git a/dan_layer/template_macros/Cargo.toml b/dan_layer/template_macros/Cargo.toml index cde4d4cc4a..a3335cabe6 100644 --- a/dan_layer/template_macros/Cargo.toml +++ b/dan_layer/template_macros/Cargo.toml @@ -1,4 +1,3 @@ -[workspace] [package] name = "tari_template_macros" version = "0.1.0" diff --git a/dan_layer/template_macros/src/template/dependencies.rs b/dan_layer/template_macros/src/template/dependencies.rs index e997c4e0bf..fb81436f95 100644 --- a/dan_layer/template_macros/src/template/dependencies.rs +++ b/dan_layer/template_macros/src/template/dependencies.rs @@ -24,40 +24,42 @@ use proc_macro2::TokenStream; use quote::quote; pub fn generate_dependencies() -> TokenStream { + // TODO: these public abi functions are already declared in the common template lib/abi quote! { - extern "C" { - pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; - } - - pub fn wrap_ptr(mut v: Vec) -> *mut u8 { - use std::mem; - - let ptr = v.as_mut_ptr(); - mem::forget(v); - ptr - } - - #[no_mangle] - pub unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { - use std::{mem, intrinsics::copy}; - - let cap = (len + 4) as usize; - let mut buf = Vec::::with_capacity(cap); - let ptr = buf.as_mut_ptr(); - mem::forget(buf); - copy(len.to_le_bytes().as_ptr(), ptr, 4); - ptr - } - - #[no_mangle] - pub unsafe extern "C" fn tari_free(ptr: *mut u8) { - use std::intrinsics::copy; - - let mut len = [0u8; 4]; - copy(ptr, len.as_mut_ptr(), 4); - - let cap = (u32::from_le_bytes(len) + 4) as usize; - let _ = Vec::::from_raw_parts(ptr, cap, cap); - } + use tari_template_lib::wrap_ptr; + // extern "C" { + // pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; + // } + // + // pub fn wrap_ptr(mut v: Vec) -> *mut u8 { + // use std::mem; + // + // let ptr = v.as_mut_ptr(); + // mem::forget(v); + // ptr + // } + // + // #[no_mangle] + // pub unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { + // use std::{mem, intrinsics::copy}; + // + // let cap = (len + 4) as usize; + // let mut buf = Vec::::with_capacity(cap); + // let ptr = buf.as_mut_ptr(); + // mem::forget(buf); + // copy(len.to_le_bytes().as_ptr(), ptr, 4); + // ptr + // } + // + // #[no_mangle] + // pub unsafe extern "C" fn tari_free(ptr: *mut u8) { + // use std::intrinsics::copy; + // + // let mut len = [0u8; 4]; + // copy(ptr, len.as_mut_ptr(), 4); + // + // let cap = (u32::from_le_bytes(len) + 4) as usize; + // let _ = Vec::::from_raw_parts(ptr, cap, cap); + // } } } diff --git a/dan_layer/template_macros/src/template/mod.rs b/dan_layer/template_macros/src/template/mod.rs index e0bd5541d9..ceb8e149ca 100644 --- a/dan_layer/template_macros/src/template/mod.rs +++ b/dan_layer/template_macros/src/template/mod.rs @@ -183,40 +183,7 @@ mod tests { wrap_ptr(result) } - extern "C" { - pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; - } - - pub fn wrap_ptr(mut v: Vec) -> *mut u8 { - use std::mem; - - let ptr = v.as_mut_ptr(); - mem::forget(v); - ptr - } - - #[no_mangle] - pub unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { - use std::{mem, intrinsics::copy}; - - let cap = (len + 4) as usize; - let mut buf = Vec::::with_capacity(cap); - let ptr = buf.as_mut_ptr(); - mem::forget(buf); - copy(len.to_le_bytes().as_ptr(), ptr, 4); - ptr - } - - #[no_mangle] - pub unsafe extern "C" fn tari_free(ptr: *mut u8) { - use std::intrinsics::copy; - - let mut len = [0u8; 4]; - copy(ptr, len.as_mut_ptr(), 4); - - let cap = (u32::from_le_bytes(len) + 4) as usize; - let _ = Vec::::from_raw_parts(ptr, cap, cap); - } + use tari_template_lib::wrap_ptr; }); }