diff --git a/Cargo.lock b/Cargo.lock index 335797456d9a61fea4f92e07c37f298968ed9c84..6eeadc52a097c357c09b4ec4b399027fc34b6c3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,27 @@ dependencies = [ "serde", ] +[[package]] +name = "duniter-dal" +version = "0.1.0" +dependencies = [ + "bincode", + "crossbeam-utils", + "dubp-common", + "dubp-wot", + "duniter-dbs", + "flate2", + "log", + "mockall", + "once_cell", + "rusqlite", + "serde", + "serde_json", + "tempdir", + "thiserror", + "unwrap", +] + [[package]] name = "duniter-dbex" version = "0.1.0" @@ -855,6 +876,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.4.0" @@ -1185,6 +1218,22 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98" +[[package]] +name = "libsqlite3-sys" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a245984b1b06c291f46e27ebda9f369a94a1ab8461d0e845e23f9ced01f5db" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + [[package]] name = "lock_api" version = "0.3.4" @@ -1218,6 +1267,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0925aed5b12ed59857f438d25a910cf051dbcd4107907be1e7abf6c44ec903" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "maplit" version = "1.0.2" @@ -1624,6 +1682,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" + [[package]] name = "plotters" version = "0.2.15" @@ -1850,6 +1914,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "rusqlite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c78c3275d9d6eb684d2db4b2388546b32fdae0586c20a82f3905d21ea78b9ef" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "libsqlite3-sys", + "lru-cache", + "memchr", + "serde_json", + "smallvec", +] + [[package]] name = "rust-argon2" version = "0.8.2" @@ -2266,6 +2346,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + [[package]] name = "vec-arena" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 90a051b9c4c06f1dd6f307707e18fe04c1974128..c25d4cb771f81d0a2f19d58dad93aeaf4f1a3cc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "rust-bins/duniter-dbex", "rust-bins/xtask", "rust-libs/dubp-wot", + "rust-libs/duniter-dal", "rust-libs/duniter-dbs", "rust-libs/tools/kv_typed", "rust-libs/tools/kv_typed_code_gen" diff --git a/rust-libs/duniter-dal/Cargo.toml b/rust-libs/duniter-dal/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c2aaf99cad89820b9d14801f54557a84abfae4c6 --- /dev/null +++ b/rust-libs/duniter-dal/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "duniter-dal" +version = "0.1.0" +authors = ["elois <elois@duniter.org>"] +description = "Duniter Data Access Layer" +repository = "https://git.duniter.org/nodes/typescript/duniter" +keywords = ["duniter"] +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +bincode = "1.2.1" +crossbeam-utils = "0.7.2" +dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] } +dubp-wot = { path = "../dubp-wot" } +duniter-dbs = { path = "../duniter-dbs" } +flate2 = "1.0.16" +log = "0.4.8" +mockall = { version = "0.8.0", optional = true } +rusqlite = { version = "0.25.2", features = ["serde_json"] } +serde = { version = "1.0.105", features = ["derive"] } +serde_json = "1.0.53" +thiserror = "1.0.20" + +[dev-dependencies] +once_cell = "1.4.0" +tempdir = "0.3.7" +unwrap = "1.2.1" + +[features] +default = ["mock"] +#default = ["test_real"] + +mock = ["duniter-dbs/mock", "mockall"] +test_real = [] diff --git a/rust-libs/duniter-dal/src/conf_dbs.rs b/rust-libs/duniter-dal/src/conf_dbs.rs new file mode 100644 index 0000000000000000000000000000000000000000..09020976b2a77480a525edb2a9d9137e419bb46a --- /dev/null +++ b/rust-libs/duniter-dal/src/conf_dbs.rs @@ -0,0 +1,39 @@ +use crate::*; + +use duniter_dbs::kv_typed::backend::sled; + +pub trait GenBackendConf<B: Backend> { + fn gen_backend_conf(db_name: &'static str, data_path_opt: Option<&Path>) -> B::Conf; +} + +impl GenBackendConf<Mem> for Mem { + #[inline(always)] + fn gen_backend_conf(_db_name: &'static str, _data_path_opt: Option<&Path>) -> MemConf { + MemConf::default() + } +} + +impl GenBackendConf<Sled> for Sled { + #[inline(always)] + fn gen_backend_conf(db_name: &'static str, data_path_opt: Option<&Path>) -> sled::Config { + if let Some(data_path) = data_path_opt { + sled::Config::default().path(data_path.join(format!("{}_sled", db_name))) + } else { + sled::Config::default().temporary(true) + } + } +} + +impl GenBackendConf<LevelDb> for LevelDb { + #[inline(always)] + fn gen_backend_conf(_db_name: &'static str, data_path_opt: Option<&Path>) -> LevelDbConf { + if let Some(data_path) = data_path_opt { + LevelDbConf { + db_path: data_path.join("leveldb"), + ..Default::default() + } + } else { + panic!("LevelDb backend not supported memory mode") + } + } +} diff --git a/rust-libs/duniter-dal/src/databases.rs b/rust-libs/duniter-dal/src/databases.rs new file mode 100644 index 0000000000000000000000000000000000000000..68e759ec8701af6dceb381d4da63dde932600492 --- /dev/null +++ b/rust-libs/duniter-dal/src/databases.rs @@ -0,0 +1,131 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter databases + +use crate::*; + +/// Blockchain database +pub type BcDb<B> = BcV1Db<B>; + +/// Blockchain database on read-only mode +pub type BcDbRo<B> = BcV1DbRo<B>; + +#[derive(Clone)] +pub struct DatabasesRo<B: Backend> { + pub(crate) bc_db: BcDbRo<B>, + pub(crate) peers_db: Arc<rusqlite::Connection>, + pub(crate) txs_mp_db: Arc<rusqlite::Connection>, + pub(crate) wot_mp_db: Arc<rusqlite::Connection>, +} + +impl<B: Backend> Debug for DatabasesRo<B> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DatabasesRo {{ ... }}") + } +} + +#[derive(Clone)] +pub struct Databases<B: Backend> { + pub(crate) bc_db: BcDb<B>, + pub(crate) peers_db: Arc<rusqlite::Connection>, + pub(crate) txs_mp_db: Arc<rusqlite::Connection>, + pub(crate) wot_mp_db: Arc<rusqlite::Connection>, + ro: DatabasesRo<B>, +} + +impl<B: Backend> Debug for Databases<B> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Databases {{ ... }}") + } +} + +impl<B: Backend> Databases<B> { + pub(crate) fn to_ro(&self) -> &DatabasesRo<B> { + &self.ro + } +} + +impl<B: Backend + GenBackendConf<B>> Databases<B> { + pub(crate) fn open(data_path_opt: Option<&Path>) -> DalResult<Databases<B>> { + let bc_db_conf = B::gen_backend_conf(BC_DB_NAME, data_path_opt); + let bc_db = BcV1Db::<B>::open(bc_db_conf)?; + let ro = DatabasesRo { + bc_db: bc_db.get_ro_handler(), + peers_db: Arc::new(open_sqlite_db(data_path_opt, PEERS_DB)?), + txs_mp_db: Arc::new(open_sqlite_db(data_path_opt, TX_MP_DB)?), + wot_mp_db: Arc::new(open_sqlite_db(data_path_opt, WOT_MP_SQLITE_DB)?), + }; + Ok(Databases { + bc_db, + peers_db: ro.peers_db.clone(), + txs_mp_db: ro.txs_mp_db.clone(), + wot_mp_db: ro.wot_mp_db.clone(), + ro, + }) + } +} + +// TMP - Sqlite will be removed on the future ! + +#[inline(always)] +fn open_sqlite_db( + data_path_opt: Option<&Path>, + db_file_name: &str, +) -> DalResult<rusqlite::Connection> { + Ok(if let Some(data_path) = data_path_opt.as_deref() { + let mut sqlite_db_path = data_path.to_owned(); + sqlite_db_path.pop(); + sqlite_db_path.push(db_file_name); + rusqlite::Connection::open(sqlite_db_path)? + } else { + rusqlite::Connection::open(":memory:")? + }) +} + +/// Vector of string deserialized from JSON string +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct JsonVecStr(pub Vec<String>); + +impl rusqlite::types::FromSql for JsonVecStr { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { + let json_value = serde_json::Value::column_result(value)?; + + if let serde_json::Value::Array(array) = json_value { + let mut vec_string = Vec::with_capacity(array.len()); + for json_value in array { + if let serde_json::Value::String(string) = json_value { + vec_string.push(string); + } else { + return Err(rusqlite::types::FromSqlError::InvalidType); + } + } + Ok(JsonVecStr(vec_string)) + } else { + Err(rusqlite::types::FromSqlError::InvalidType) + } + } +} + +impl rusqlite::types::ToSql for JsonVecStr { + fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> { + let json_string = serde_json::to_string(&self.0) + .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; + + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(json_string), + )) + } +} diff --git a/rust-libs/duniter-dal/src/entities.rs b/rust-libs/duniter-dal/src/entities.rs new file mode 100644 index 0000000000000000000000000000000000000000..d5d5bede9f6237cd1b094e43e5f2157561e9ef5b --- /dev/null +++ b/rust-libs/duniter-dal/src/entities.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter DAL entities + +pub mod peer; + +use crate::*; + +/// DUBP Block in database +pub type BlockDb = BlockDbV1; diff --git a/rust-libs/duniter-dal/src/entities/peer.rs b/rust-libs/duniter-dal/src/entities/peer.rs new file mode 100644 index 0000000000000000000000000000000000000000..ded980a4e14910dd0bc091b62a12ff6ea3346622 --- /dev/null +++ b/rust-libs/duniter-dal/src/entities/peer.rs @@ -0,0 +1,61 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define peer entity + +use crate::*; + +/// DUNP Peer card in database +#[allow(missing_docs)] +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct PeerCardDb { + pub version: i64, + pub currency: String, + pub status: String, + pub status_ts: i64, + pub hash: String, + pub first_down: Option<i64>, + pub last_try: Option<i64>, + pub last_contact: i64, + pub pubkey: String, + pub block: String, + pub signature: String, + pub endpoints: JsonVecStr, + pub raw: String, + pub non_wot: bool, +} + +impl<'stmt> TryFrom<&rusqlite::Row<'stmt>> for PeerCardDb { + type Error = rusqlite::Error; + + fn try_from(row: &rusqlite::Row<'stmt>) -> Result<Self, Self::Error> { + Ok(PeerCardDb { + version: row.get(0)?, + currency: row.get(1)?, + status: row.get(2)?, + status_ts: row.get(3)?, + hash: row.get(4)?, + first_down: row.get(5)?, + last_try: row.get(6)?, + last_contact: row.get(7)?, + pubkey: row.get(8)?, + block: row.get(9)?, + signature: row.get(10)?, + endpoints: row.get(11)?, + raw: row.get(12)?, + non_wot: row.get(13)?, + }) + } +} diff --git a/rust-libs/duniter-dal/src/errors.rs b/rust-libs/duniter-dal/src/errors.rs new file mode 100644 index 0000000000000000000000000000000000000000..842a2b0aeeae04d804d2acfaa42b70d9ba9880fb --- /dev/null +++ b/rust-libs/duniter-dal/src/errors.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define DAL errors + +use crate::*; + +/// Duniter DAL Result +pub type DalResult<T> = Result<T, DalError>; + +#[derive(Debug, Error)] +/// Dal error +pub enum DalError { + /// Database error + #[error("{0}")] + DbError(ErrorDb), + /// Fail to create folder + #[error("Fail to create folder: {0}")] + FailToCreateFolder(std::io::Error), + /// Fail to open wot file + #[error("Fail to open wot file: {0}")] + FailToOpenWotFile(bincode::Error), + /// Fail to save wot file + #[error("Fail to save wot file: {0}")] + FailToSaveWotFile(bincode::Error), + /// Fail to get currency parameters from genesis block + #[error("Fail to get genesis parameters: {0}")] + GenesisParamsError(String), + /// No blockchain + #[error("No blockchain")] + NoBlockchain, + /// Sqlite error + #[error("Sqlite error: {0}")] + SqLiteError(rusqlite::Error), + /// Wrong backend + #[error("Database already exist with {exist_backend} bakend, please use them.")] + WrongBackend { + /// Existing bakend + exist_backend: &'static str, + }, +} + +impl From<KvError> for DalError { + fn from(e: KvError) -> Self { + Self::DbError(ErrorDb::DbError(format!("{}", e))) + } +} + +impl From<ErrorDb> for DalError { + fn from(e: ErrorDb) -> Self { + Self::DbError(e) + } +} + +impl From<rusqlite::Error> for DalError { + fn from(e: rusqlite::Error) -> Self { + Self::SqLiteError(e) + } +} diff --git a/rust-libs/duniter-dal/src/file_dal.rs b/rust-libs/duniter-dal/src/file_dal.rs new file mode 100644 index 0000000000000000000000000000000000000000..4066f949933b7a746056602d7755946f7976bf2d --- /dev/null +++ b/rust-libs/duniter-dal/src/file_dal.rs @@ -0,0 +1,166 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter file data access layer + +use crate::*; + +#[derive(Clone, Debug)] +/// Duniter Data Access Layer on read-only mode that read data from filesystem +pub struct FileDalRo<B: Backend> { + pub(crate) databases: DatabasesRo<B>, + pub(crate) currency_params: CurrencyParameters, + pub(crate) wot: Wot, +} + +#[derive(Clone, Debug)] +/// Duniter Data Access Layer that store data on filesystem +pub struct FileDal<B: Backend> { + pub(crate) databases: Databases<B>, + pub(crate) currency_params: CurrencyParameters, + pub(crate) wot: Wot, +} + +impl<B: Backend + GenBackendConf<B>> FileDal<B> { + #[inline(always)] + /// Initialise file DAL + pub fn init<P: AsRef<Path>>(profile_path: P) -> DalResult<Self> { + let data_path = profile_path.as_ref().join(DATA_DIR); + verify_data_path(&data_path, B::NAME)?; + let databases = Databases::open(Some(data_path.as_path()))?; + let currency_params = get_currency_params(&databases.bc_db, data_path.as_path())?; + let wot = Wot::open(Some(data_path.as_path()), currency_params.sig_stock) + .map_err(DalError::FailToOpenWotFile)?; + + Ok(FileDal { + currency_params, + databases, + wot, + }) + } +} + +impl<B: Backend> ToDalRo for FileDal<B> { + type DalRo = FileDalRo<B>; + + fn to_dal_ro(&self) -> Self::DalRo { + FileDalRo { + databases: self.databases.to_ro().clone(), + currency_params: self.currency_params, + wot: self.wot.clone(), + } + } +} + +fn get_currency_params<B: Backend>( + bc_db: &BcDb<B>, + data_path: &Path, +) -> DalResult<CurrencyParameters> { + Ok(if let Some(currency_params) = get_genesis_params(&bc_db)? { + currency_params + } else { + let mut file = fs::File::open(data_path.join(GENESIS_PARAMS_FILE)) + .map_err(|e| DalError::GenesisParamsError(format!("{}", e)))?; + let mut file_contents = String::new(); + file.read_to_string(&mut file_contents) + .map_err(|e| DalError::GenesisParamsError(format!("{}", e)))?; + serde_json::from_str::<CurrencyNameAndGenesisBlockParams>(&file_contents) + .map_err(|e| DalError::GenesisParamsError(format!("{}", e)))? + .into() + }) +} + +fn get_genesis_params<B: Backend>(bc_db: &BcDb<B>) -> DalResult<Option<CurrencyParameters>> { + let genesis_opt = bc_db + .main_blocks() + .get(&duniter_dbs::BlockNumberKeyV1(BlockNumber(0)))?; + + Ok(if let Some(genesis) = genesis_opt { + Some(CurrencyParameters::from(( + &CurrencyName(genesis.currency), + dubp_common::currency_params::BlockV10Parameters::from_str(&genesis.parameters) + .map_err(|e| DalError::GenesisParamsError(format!("{}", e)))?, + ))) + } else { + None + }) +} + +fn verify_data_path(data_path: &PathBuf, backend_name: &'static str) -> DalResult<()> { + if !data_path.exists() { + fs::create_dir_all(data_path.as_path()).map_err(DalError::FailToCreateFolder)?; + } else { + match backend_name { + "leveldb" => { + if data_path.as_path().join("bc_v1_sled").exists() { + return Err(DalError::WrongBackend { + exist_backend: "sled", + }); + } + } + "sled" => { + if data_path.as_path().join("leveldb").exists() { + return Err(DalError::WrongBackend { + exist_backend: "leveldb", + }); + } + } + _ => unreachable!(), + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use dubp_common::currency_params::{BlockV10Parameters, GenesisBlockParams}; + use tempdir::TempDir; + use unwrap::unwrap; + + #[test] + fn test_file_dal() -> DalResult<()> { + let tmp_dir = unwrap!(TempDir::new("duniter_test_file_dal")); + let tmp_dir_path = tmp_dir.path(); + let data_path = tmp_dir_path.join(DATA_DIR); + unwrap!(fs::create_dir(data_path)); + + /*if let Err(DalError::GenesisParamsError(_)) = FileDal::init(None, tmp_dir_path.to_owned()) { + } else { + panic!("FileDal::init must be fail with error GenesisParamsError !"); + }*/ + + // Create currency params file + let params = CurrencyNameAndGenesisBlockParams { + genesis_block_params: GenesisBlockParams::V10(BlockV10Parameters { + sig_stock: 40, + ..Default::default() + }), + ..Default::default() + }; + let mut file = unwrap!(fs::File::create( + tmp_dir_path.join(DATA_DIR).join(GENESIS_PARAMS_FILE) + )); + unwrap!(file.write_all(unwrap!(serde_json::to_string(¶ms)).as_bytes())); + + let dal = FileDal::<Sled>::init(tmp_dir_path)?; + + assert_eq!(40, dal.wot().get_max_link()); + + unwrap!(tmp_dir.close().map_err(DalError::FailToCreateFolder)); + Ok(()) + } +} diff --git a/rust-libs/duniter-dal/src/inner.rs b/rust-libs/duniter-dal/src/inner.rs new file mode 100644 index 0000000000000000000000000000000000000000..998373019bcccd301221b89a7703b5d20c3a9e0a --- /dev/null +++ b/rust-libs/duniter-dal/src/inner.rs @@ -0,0 +1,106 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter DAL inner + +use crate::*; + +pub trait InnerDalRo: Clone { + type Backend: Backend; + + fn currency_params(&self) -> CurrencyParameters; + fn databases(&self) -> &DatabasesRo<Self::Backend>; + fn wot(&self) -> &Wot; +} + +impl InnerDalRo for MemDalRo { + type Backend = Mem; + + #[inline(always)] + fn currency_params(&self) -> CurrencyParameters { + self.currency_params + } + #[inline(always)] + fn databases(&self) -> &DatabasesRo<Mem> { + &self.databases + } + #[inline(always)] + fn wot(&self) -> &Wot { + &self.wot + } +} +impl InnerDalRo for MemDal { + type Backend = Mem; + #[inline(always)] + fn currency_params(&self) -> CurrencyParameters { + self.currency_params + } + #[inline(always)] + fn databases(&self) -> &DatabasesRo<Self::Backend> { + self.databases.to_ro() + } + #[inline(always)] + fn wot(&self) -> &Wot { + &self.wot + } +} +impl<B: Backend> InnerDalRo for FileDalRo<B> { + type Backend = B; + #[inline(always)] + fn currency_params(&self) -> CurrencyParameters { + self.currency_params + } + #[inline(always)] + fn databases(&self) -> &DatabasesRo<Self::Backend> { + &self.databases + } + #[inline(always)] + fn wot(&self) -> &Wot { + &self.wot + } +} +impl<B: Backend> InnerDalRo for FileDal<B> { + type Backend = B; + #[inline(always)] + fn currency_params(&self) -> CurrencyParameters { + self.currency_params + } + #[inline(always)] + fn databases(&self) -> &DatabasesRo<Self::Backend> { + self.databases.to_ro() + } + #[inline(always)] + fn wot(&self) -> &Wot { + &self.wot + } +} + +pub trait InnerDalRw: InnerDalRo + ToDalRo { + fn databases_rw(&self) -> &Databases<<Self as InnerDalRo>::Backend>; +} + +impl<B: Backend> InnerDalRw for FileDal<B> { + #[inline(always)] + fn databases_rw(&self) -> &Databases<B> { + &self.databases + } +} + +impl InnerDalRw for MemDal { + #[inline(always)] + fn databases_rw(&self) -> &Databases<Mem> { + &self.databases + } +} diff --git a/rust-libs/duniter-dal/src/lib.rs b/rust-libs/duniter-dal/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e87802cff343f92e19c0907dda6f94422e7795c6 --- /dev/null +++ b/rust-libs/duniter-dal/src/lib.rs @@ -0,0 +1,133 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer + +#![deny( + clippy::expect_used, + clippy::unwrap_used, + missing_docs, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces, + unused_qualifications +)] + +mod conf_dbs; +mod databases; +mod entities; +mod errors; +mod file_dal; +mod inner; +mod mem_dal; +#[cfg(feature = "mock")] +mod mock_dal; +mod read; +mod wot; +mod write; + +/// Prelude +pub mod prelude { + pub use crate::databases::JsonVecStr; + pub use crate::databases::{BcDb, BcDbRo}; + pub use crate::entities::peer::PeerCardDb; + pub use crate::entities::BlockDb; + pub use crate::errors::{DalError, DalResult}; + pub use crate::file_dal::FileDal; + pub use crate::mem_dal::MemDal; + #[cfg(feature = "mock")] + pub use crate::mock_dal::{MockDal, MockDalRo}; + pub use crate::read::bc_db::DalReadBc; + pub use crate::read::peers::DalReadPeers; + pub use crate::read::DalReadable; + pub use crate::write::peers::DalWritePeers; + pub use crate::write::{DalWritable, ToDalRo}; + pub use crate::Dal; +} +// Technical types +pub use duniter_dbs::kv_typed::backend::leveldb::LevelDb; +#[cfg(feature = "mock")] +pub use duniter_dbs::kv_typed::backend::mock::MockBackend; +pub use duniter_dbs::kv_typed::backend::sled::Sled; + +// Crate imports +pub(crate) use crate::conf_dbs::GenBackendConf; +pub(crate) use crate::databases::{Databases, DatabasesRo}; +pub(crate) use crate::errors::{DalError, DalResult}; +pub(crate) use crate::file_dal::FileDalRo; +pub(crate) use crate::inner::{InnerDalRo, InnerDalRw}; +pub(crate) use crate::mem_dal::MemDalRo; +pub(crate) use crate::prelude::*; +pub(crate) use crate::wot::Wot; +pub(crate) use dubp_common::currency_params::{ + CurrencyNameAndGenesisBlockParams, CurrencyParameters, +}; +pub(crate) use dubp_common::prelude::*; +pub(crate) use duniter_dbs::kv_typed::prelude::*; +pub(crate) use duniter_dbs::prelude::*; +pub(crate) use duniter_dbs::{ + BcV1Db, BcV1DbReadable as BcDbReadable, BcV1DbRo, BcV1DbWritable as BcDbWritable, BlockDbV1, +}; +pub(crate) use serde::{Deserialize, Serialize}; +pub(crate) use std::{ + convert::TryFrom, + fmt::Debug, + fs::{self, File}, + io::prelude::*, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; +pub(crate) use thiserror::Error; + +// Crate internal constants +const BC_DB_NAME: &str = "bc_v1"; +const DATA_DIR: &str = "data"; +const GENESIS_PARAMS_FILE: &str = "genesis_parameters.json"; +const WOT_FILE: &str = "wotb.bin.gz"; +const PEERS_DB: &str = "peers.db"; +const TX_MP_DB: &str = "txs.db"; +const WOT_MP_SQLITE_DB: &str = "duniter.db"; + +/// Duniter Data Access Layer +pub trait Dal<B: Backend>: DalWritable { + /// Get read only handler to blockchain database + fn get_bc_db_ro(&self) -> BcDbRo<B>; + + /// Get read only DAL + fn get_dal_ro(&self) -> <Self as ToDalRo>::DalRo; +} + +impl<B: Backend, T> Dal<B> for T +where + T: InnerDalRo<Backend = B> + InnerDalRw + DalWritable + NotMock, +{ + fn get_bc_db_ro(&self) -> BcDbRo<B> { + self.databases_rw().bc_db.get_ro_handler() + } + fn get_dal_ro(&self) -> Self::DalRo { + self.to_dal_ro() + } +} + +#[doc(hidden)] +pub trait NotMock {} + +impl<B: Backend> NotMock for FileDal<B> {} +impl<B: Backend> NotMock for FileDalRo<B> {} +impl NotMock for MemDal {} +impl NotMock for MemDalRo {} diff --git a/rust-libs/duniter-dal/src/mem_dal.rs b/rust-libs/duniter-dal/src/mem_dal.rs new file mode 100644 index 0000000000000000000000000000000000000000..6fdab11e82262fe9b736c254250115ffb6a874eb --- /dev/null +++ b/rust-libs/duniter-dal/src/mem_dal.rs @@ -0,0 +1,82 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter memory data access layer + +use crate::*; + +#[derive(Clone, Debug)] +/// Duniter Data Access Layer on read-only mode that read data from memory +pub struct MemDalRo { + pub(crate) databases: DatabasesRo<Mem>, + pub(crate) currency_params: CurrencyParameters, + pub(crate) wot: Wot, +} + +#[derive(Clone, Debug)] +/// Duniter Data Access Layer that store data on memory +pub struct MemDal { + pub(crate) databases: Databases<Mem>, + pub(crate) currency_params: CurrencyParameters, + pub(crate) wot: Wot, +} + +impl MemDal { + #[inline(always)] + /// Initialise memory DAL + pub fn init(currency_params_opt: Option<CurrencyParameters>) -> DalResult<Self> { + let currency_params = currency_params_opt.unwrap_or_default(); + // Memory mode, force sled backend + let databases = Databases::open(None)?; + let wot = + Wot::open(None, currency_params.sig_stock).map_err(DalError::FailToOpenWotFile)?; + Ok(MemDal { + currency_params, + databases, + wot, + }) + } +} + +impl ToDalRo for MemDal { + type DalRo = MemDalRo; + + fn to_dal_ro(&self) -> Self::DalRo { + MemDalRo { + databases: self.databases.to_ro().clone(), + currency_params: self.currency_params, + wot: self.wot.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mem_dal() -> DalResult<()> { + let currency_params = CurrencyParameters { + sig_stock: 42, + ..Default::default() + }; + let dal = MemDal::init(Some(currency_params))?; + + assert_eq!(currency_params, dal.currency_params()); + assert_eq!(42, dal.wot().get_max_link()); + + Ok(()) + } +} diff --git a/rust-libs/duniter-dal/src/mock_dal.rs b/rust-libs/duniter-dal/src/mock_dal.rs new file mode 100644 index 0000000000000000000000000000000000000000..056d8d2d8207c75c8a02b8da8e1644f57b859158 --- /dev/null +++ b/rust-libs/duniter-dal/src/mock_dal.rs @@ -0,0 +1,74 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter mocked data access layer + +#![allow(clippy::ptr_arg)] + +use crate::*; + +mockall::mock! { + pub DalRo<B: Backend> {} + trait DalReadBc { + fn get_block(&self, number: BlockNumber) -> DalResult<Option<BlockDb>>; + fn get_current_block(&self) -> DalResult<Option<BlockDb>>; + } + trait DalReadPeers { + fn count_mirror_peers(&self) -> DalResult<u32>; + fn get_peer_by_pubkey(&self, pubkey: &str) -> DalResult<Option<PeerCardDb>>; + fn get_peers_with_endpoints_like(&self, pattern: &str) -> DalResult<Vec<PeerCardDb>>; + fn get_all_peers(&self) -> DalResult<Vec<PeerCardDb>>; + fn get_up_peers(&self) -> DalResult<Vec<PeerCardDb>>; + } + trait DalReadable { + fn get_currency_params(&self) -> CurrencyParameters; + } +} + +mockall::mock! { + pub Dal<B: Backend> {} + trait DalReadBc { + fn get_block(&self, number: BlockNumber) -> DalResult<Option<BlockDb>>; + fn get_current_block(&self) -> DalResult<Option<BlockDb>>; + } + trait DalReadPeers { + fn count_mirror_peers(&self) -> DalResult<u32>; + fn get_peer_by_pubkey(&self, pubkey: &str) -> DalResult<Option<PeerCardDb>>; + fn get_peers_with_endpoints_like(&self, pattern: &str) -> DalResult<Vec<PeerCardDb>>; + fn get_all_peers(&self) -> DalResult<Vec<PeerCardDb>>; + fn get_up_peers(&self) -> DalResult<Vec<PeerCardDb>>; + } + trait DalReadable { + fn get_currency_params(&self) -> CurrencyParameters; + } + trait ToDalRo { + type DalRo = MockDalRo<B>; + + fn to_dal_ro(&self) -> MockDalRo<B>; + } + trait DalWritePeers { + fn delete_mirrror_peers_whose_last_contact_is_above(&self, threshold: i64) -> DalResult<()>; + fn remove_all(&self) -> DalResult<()>; + fn remove_peer_by_pubkey(&self, pubkey: String) -> DalResult<()>; + fn save_peer(&self, peer: PeerCardDb) -> DalResult<()>; + } + trait DalWritable { + fn save_wot(&self) -> DalResult<()>; + } + trait Dal<B>: DalReadable + DalWritable<B> { + fn get_bc_db_ro(&self) -> BcDbRo<B>; + fn get_dal_ro(&self) -> MockDalRo<B>; + } +} diff --git a/rust-libs/duniter-dal/src/read.rs b/rust-libs/duniter-dal/src/read.rs new file mode 100644 index 0000000000000000000000000000000000000000..8c85358e194c51f55ab11443c6709634eab48dc2 --- /dev/null +++ b/rust-libs/duniter-dal/src/read.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer read operations + +pub mod bc_db; +pub mod peers; + +use crate::*; +use bc_db::*; + +/// DAL read operations +pub trait DalReadable: DalReadBc + DalReadPeers { + /// Get currency parameters + fn get_currency_params(&self) -> CurrencyParameters; +} + +impl<T> DalReadable for T +where + T: DalReadBc + DalReadPeers + InnerDalRo + NotMock, +{ + #[inline(always)] + fn get_currency_params(&self) -> CurrencyParameters { + self.currency_params() + } +} diff --git a/rust-libs/duniter-dal/src/read/bc_db.rs b/rust-libs/duniter-dal/src/read/bc_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..5f500cceba433c49ceabff74af7a95bc047d0242 --- /dev/null +++ b/rust-libs/duniter-dal/src/read/bc_db.rs @@ -0,0 +1,52 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Define blockchain database read operations + +use crate::*; +use duniter_dbs::BlockNumberKeyV1; + +/// DAL read operations for blockcnain db +pub trait DalReadBc { + /// Get a block by number + fn get_block(&self, number: BlockNumber) -> DalResult<Option<BlockDb>>; + /// Get the current block (=the last block) + fn get_current_block(&self) -> DalResult<Option<BlockDb>>; +} + +impl<T> DalReadBc for T +where + T: InnerDalRo + NotMock, +{ + fn get_current_block(&self) -> DalResult<Option<BlockDb>> { + Ok(self + .databases() + .bc_db + .main_blocks() + .iter(..) + .values() + .reverse() + .next() + .transpose()?) + } + #[inline(always)] + fn get_block(&self, number: BlockNumber) -> DalResult<Option<BlockDb>> { + Ok(self + .databases() + .bc_db + .main_blocks() + .get(&BlockNumberKeyV1(number))?) + } +} diff --git a/rust-libs/duniter-dal/src/read/peers.rs b/rust-libs/duniter-dal/src/read/peers.rs new file mode 100644 index 0000000000000000000000000000000000000000..22d3eadb6e5ef35c4a90490f5b6573d73ffadca1 --- /dev/null +++ b/rust-libs/duniter-dal/src/read/peers.rs @@ -0,0 +1,82 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer read operations for peers db + +use crate::*; + +/// DAL read operations for peers db +pub trait DalReadPeers { + /// Count mirror peers + fn count_mirror_peers(&self) -> DalResult<u32>; + /// Get one peer card by this pub-key + fn get_peer_by_pubkey(&self, pubkey: &str) -> DalResult<Option<PeerCardDb>>; + /// Get peers cards with endpoints like + fn get_peers_with_endpoints_like(&self, pattern: &str) -> DalResult<Vec<PeerCardDb>>; + /// Get all peer cards + fn get_all_peers(&self) -> DalResult<Vec<PeerCardDb>>; + /// Get UP peers + fn get_up_peers(&self) -> DalResult<Vec<PeerCardDb>>; +} + +#[allow(clippy::redundant_closure)] +impl<T> DalReadPeers for T +where + T: InnerDalRo + NotMock, +{ + fn count_mirror_peers(&self) -> DalResult<u32> { + Ok(self + .databases() + .peers_db + .prepare("SELECT COUNT(*) as _count FROM peers WHERE nonWoT")? + .query_map(rusqlite::params![], |row| row.get(0))? + .next() + .transpose()? + .unwrap_or_default()) + } + fn get_peer_by_pubkey(&self, pubkey: &str) -> DalResult<Option<PeerCardDb>> { + Ok(self + .databases() + .peers_db + .prepare("SELECT * FROM peers WHERE pubkey = ?")? + .query_map(&[pubkey], |row| PeerCardDb::try_from(row))? + .next() + .transpose()?) + } + fn get_peers_with_endpoints_like(&self, pattern: &str) -> DalResult<Vec<PeerCardDb>> { + Ok(self + .databases() + .peers_db + .prepare("SELECT * FROM peers WHERE endpoints LIKE ?")? + .query_map(&[pattern], |row| PeerCardDb::try_from(row))? + .collect::<Result<Vec<PeerCardDb>, rusqlite::Error>>()?) + } + fn get_all_peers(&self) -> DalResult<Vec<PeerCardDb>> { + Ok(self + .databases() + .peers_db + .prepare("SELECT * FROM peers")? + .query_map(rusqlite::params![], |row| PeerCardDb::try_from(row))? + .collect::<Result<Vec<PeerCardDb>, rusqlite::Error>>()?) + } + fn get_up_peers(&self) -> DalResult<Vec<PeerCardDb>> { + Ok(self + .databases() + .peers_db + .prepare("SELECT * FROM peers WHERE status = ?")? + .query_map(&["UP"], |row| PeerCardDb::try_from(row))? + .collect::<Result<Vec<PeerCardDb>, rusqlite::Error>>()?) + } +} diff --git a/rust-libs/duniter-dal/src/wot.rs b/rust-libs/duniter-dal/src/wot.rs new file mode 100644 index 0000000000000000000000000000000000000000..4ba90b9e5b4e84cf84d770a12148d61daef81b37 --- /dev/null +++ b/rust-libs/duniter-dal/src/wot.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer: wot + +mod read_from_file; +mod write_in_file; + +use crate::*; +use crossbeam_utils::sync::ShardedLock; +use dubp_wot::{data::rusty::RustyWebOfTrust, data::NewLinkResult, WebOfTrust, WotId}; + +#[derive(Clone, Debug)] +pub struct Wot { + pub(crate) wot: Arc<ShardedLock<RustyWebOfTrust>>, + file_path_opt: Option<PathBuf>, +} + +#[allow(dead_code)] +impl Wot { + pub(crate) fn open( + data_path_opt: Option<&Path>, + max_links: usize, + ) -> Result<Self, bincode::Error> { + Ok(if let Some(data_path) = data_path_opt { + let wot_file_path = data_path.join(WOT_FILE); + Wot { + wot: Arc::new(ShardedLock::new(read_from_file::wot_from_file( + wot_file_path.as_path(), + max_links, + )?)), + file_path_opt: Some(wot_file_path), + } + } else { + Wot { + wot: Arc::new(ShardedLock::new(RustyWebOfTrust::new(max_links))), + file_path_opt: None, + } + }) + } + #[allow(clippy::expect_used)] + #[inline(always)] + pub(crate) fn get_max_link(&self) -> usize { + let wot_reader = self.wot.read().expect("poisoned"); + wot_reader.get_max_link() + } + #[allow(clippy::expect_used)] + pub(crate) fn add_links(&self, links: &[(WotId, WotId)]) -> Result<(), NewLinkResult> { + let mut wot_writer = self.wot.write().expect("poisoned"); + for (source, target) in links { + match wot_writer.add_link(*source, *target) { + NewLinkResult::Ok(_) => continue, + r => return Err(r), + } + } + + Ok(()) + } + #[allow(clippy::expect_used)] + pub(crate) fn save(&self) -> Result<(), bincode::Error> { + if let Some(ref wot_file_path) = self.file_path_opt { + let wot_reader = self.wot.read().expect("poisoned"); + write_in_file::wot_in_file(wot_file_path.as_path(), &wot_reader) + } else { + Ok(()) + } + } +} diff --git a/rust-libs/duniter-dal/src/wot/read_from_file.rs b/rust-libs/duniter-dal/src/wot/read_from_file.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b9fb2acdf5c58b34f12fbb58701aedf90491e3b --- /dev/null +++ b/rust-libs/duniter-dal/src/wot/read_from_file.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use super::*; +use crate::*; +use flate2::read::ZlibDecoder; + +pub(crate) fn wot_from_file( + file_path: &Path, + max_links: usize, +) -> Result<RustyWebOfTrust, bincode::Error> { + let bytes = read_and_decompress_bytes_from_file(file_path)?; + if bytes.is_empty() { + Ok(RustyWebOfTrust::new(max_links)) + } else { + Ok(bincode::deserialize::<RustyWebOfTrust>(&bytes)?) + } +} + +/// Read and decompress bytes from file +fn read_and_decompress_bytes_from_file(file_path: &Path) -> Result<Vec<u8>, std::io::Error> { + if !file_path.exists() { + if let Some(parent) = file_path.parent() { + std::fs::create_dir_all(parent)? + } + File::create(file_path)?; + } + if std::fs::metadata(file_path)?.len() > 0 { + let file = File::open(file_path)?; + let mut z = ZlibDecoder::new(file); + let mut decompressed_bytes = Vec::new(); + z.read_to_end(&mut decompressed_bytes)?; + + Ok(decompressed_bytes) + } else { + Ok(vec![]) + } +} diff --git a/rust-libs/duniter-dal/src/wot/write_in_file.rs b/rust-libs/duniter-dal/src/wot/write_in_file.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b88d958a01e25a0d5cc3788b46b15514776894e --- /dev/null +++ b/rust-libs/duniter-dal/src/wot/write_in_file.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use super::*; +use crate::*; +use flate2::write::ZlibEncoder; +use flate2::Compression; + +pub(crate) fn wot_in_file(file_path: &Path, wot: &RustyWebOfTrust) -> Result<(), bincode::Error> { + let bytes = bincode::serialize(wot)?; + write_and_compress_bytes_in_file(file_path, &bytes, flate2::Compression::default())?; + + Ok(()) +} + +/// Write and compress bytes in file +fn write_and_compress_bytes_in_file( + file_path: &Path, + datas: &[u8], + compression: Compression, +) -> Result<(), std::io::Error> { + let file = File::create(file_path)?; + let mut e = ZlibEncoder::new(file, compression); + e.write_all(&datas[..])?; + e.finish()?; + + Ok(()) +} diff --git a/rust-libs/duniter-dal/src/write.rs b/rust-libs/duniter-dal/src/write.rs new file mode 100644 index 0000000000000000000000000000000000000000..2cf0abaeceb9842231c4ead1af465ce44c1e2c2d --- /dev/null +++ b/rust-libs/duniter-dal/src/write.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer write operations + +pub mod peers; + +use crate::*; + +/// To read only DAL +pub trait ToDalRo { + /// Read-only DAL + type DalRo: DalReadable; + + /// To read-only DAL + fn to_dal_ro(&self) -> Self::DalRo; +} + +/// DAL write operations +pub trait DalWritable: DalReadable + DalWritePeers + ToDalRo { + /// save web of trust + fn save_wot(&self) -> DalResult<()>; +} + +impl<T> DalWritable for T +where + T: DalWritePeers + InnerDalRw + NotMock, +{ + #[inline(always)] + fn save_wot(&self) -> DalResult<()> { + self.wot().save().map_err(DalError::FailToSaveWotFile)?; + Ok(()) + } +} diff --git a/rust-libs/duniter-dal/src/write/peers.rs b/rust-libs/duniter-dal/src/write/peers.rs new file mode 100644 index 0000000000000000000000000000000000000000..4cbab204ad382d1684d6bc59924709437567d9a0 --- /dev/null +++ b/rust-libs/duniter-dal/src/write/peers.rs @@ -0,0 +1,85 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Duniter Data Access Layer write operations for peers db + +use crate::*; + +/// DAL write operations for peers db +pub trait DalWritePeers { + /// Count mirror peers + fn delete_mirrror_peers_whose_last_contact_is_above(&self, threshold: i64) -> DalResult<()>; + /// Remove all peers + fn remove_all(&self) -> DalResult<()>; + /// Remove peer by pubkey + fn remove_peer_by_pubkey(&self, pubkey: String) -> DalResult<()>; + /// Save peer + fn save_peer(&self, peer: PeerCardDb) -> DalResult<()>; +} + +impl<T> DalWritePeers for T +where + T: InnerDalRw + NotMock, +{ + fn delete_mirrror_peers_whose_last_contact_is_above(&self, threshold: i64) -> DalResult<()> { + self.databases() + .peers_db + .prepare("DELETE FROM peers WHERE (nonWoT OR nonWoT IS NULL) AND lastContact <= ?")? + .execute(&[threshold])?; + Ok(()) + } + + fn remove_all(&self) -> DalResult<()> { + self.databases() + .peers_db + .prepare("DELETE FROM peers")? + .execute(rusqlite::params![])?; + Ok(()) + } + + fn remove_peer_by_pubkey(&self, pubkey: String) -> DalResult<()> { + self.databases() + .peers_db + .prepare("DELETE FROM peers WHERE pubkey = ?")? + .execute(&[pubkey])?; + Ok(()) + } + + fn save_peer(&self, peer: PeerCardDb) -> DalResult<()> { + let sql = "INSERT INTO peers (version, currency, status, statusTS, hash, + first_down, last_try, lastContact, pubkey, block, signature, endpoints, + raw, nonWoT) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + self.databases() + .peers_db + .prepare(sql)? + .execute(rusqlite::params![ + peer.version, + peer.currency, + peer.status, + peer.status_ts, + peer.hash, + peer.first_down, + peer.last_try, + peer.last_contact, + peer.pubkey, + peer.block, + peer.signature, + peer.endpoints, + peer.raw, + peer.non_wot + ])?; + Ok(()) + } +} diff --git a/rust-libs/duniter-dal/tests/main_blocks.rs b/rust-libs/duniter-dal/tests/main_blocks.rs new file mode 100644 index 0000000000000000000000000000000000000000..a58de8fc58ecaf06596e1c2ea96f41eece413dfd --- /dev/null +++ b/rust-libs/duniter-dal/tests/main_blocks.rs @@ -0,0 +1,53 @@ +use dubp_common::currency_params::BlockV10Parameters; +use dubp_common::prelude::*; +use duniter_dal::prelude::*; +use duniter_dbs::kv_typed::prelude::*; +use duniter_dbs::{BcV1DbWritable, BlockDbV1, BlockNumberKeyV1}; +use std::fs::create_dir; +use tempdir::TempDir; +use unwrap::unwrap; + +const DATA_DIR: &str = "data"; + +#[test] +fn test_dal_main_blocks() -> DalResult<()> { + let tmp_dir = unwrap!(TempDir::new("duniter_test_file_dal")); + let data_path = tmp_dir.path().join(DATA_DIR); + unwrap!(create_dir(data_path.as_path())); + let db_path = data_path.as_path().join("bc_v1_sled"); + + { + let bc_db = unwrap!(BcDb::<Sled>::open(SledConf::default().path(db_path))); + + let b0 = BlockDbV1 { + parameters: BlockV10Parameters { + sig_stock: 42, + ..Default::default() + } + .to_string(), + ..Default::default() + }; + let b1 = BlockDbV1 { + number: 1, + ..Default::default() + }; + + unwrap!(bc_db + .main_blocks_write() + .upsert(BlockNumberKeyV1(BlockNumber(0)), b0)); + unwrap!(bc_db + .main_blocks_write() + .upsert(BlockNumberKeyV1(BlockNumber(1)), b1)); + } + + let dal = FileDal::<Sled>::init(tmp_dir.path().to_owned())?; + + assert_eq!(42, dal.get_currency_params().sig_stock); + + let current_block = unwrap!(dal.get_current_block()?); + assert_eq!(1, current_block.number); + + unwrap!(tmp_dir.close()); + + Ok(()) +} diff --git a/rust-libs/duniter-dal/tests/test_dal_mock.rs b/rust-libs/duniter-dal/tests/test_dal_mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..f349eb10ccb5f73f0ae92cd89554dcc324ede890 --- /dev/null +++ b/rust-libs/duniter-dal/tests/test_dal_mock.rs @@ -0,0 +1,33 @@ +#[cfg(feature = "mock")] +mod test_mock { + use dubp_common::prelude::*; + use duniter_dal::prelude::*; + use duniter_dal::MockBackend; + + fn get_genesis_block<DAL: DalReadable>(dal: &DAL) -> DalResult<Option<BlockDb>> { + dal.get_block(BlockNumber(0)) + } + + #[test] + fn test_mock_dal() -> DalResult<()> { + let mut mock_dal = MockDal::<MockBackend>::new(); + + mock_dal.expect_get_block().returning(|_| Ok(None)); + mock_dal.expect_get_dal_ro().returning(MockDalRo::new); + + let genesis_block_opt = get_genesis_block(&mock_dal)?; + + assert_eq!(None, genesis_block_opt); + + let mut mock_dal_ro = mock_dal.get_dal_ro(); + mock_dal_ro + .expect_get_block() + .returning(|_| Ok(Some(BlockDb::default()))); + + let genesis_block_opt = get_genesis_block(&mock_dal_ro)?; + + assert_eq!(Some(BlockDb::default()), genesis_block_opt); + + Ok(()) + } +} diff --git a/rust-libs/duniter-dal/tests/test_dal_real.rs b/rust-libs/duniter-dal/tests/test_dal_real.rs new file mode 100644 index 0000000000000000000000000000000000000000..a7d77c267837c74d68749d9b729269aa3cae0dc0 --- /dev/null +++ b/rust-libs/duniter-dal/tests/test_dal_real.rs @@ -0,0 +1,54 @@ +#[cfg(feature = "test_real")] +mod tests { + + use duniter_dal::prelude::*; + use duniter_dal::LevelDb; + use once_cell::sync::Lazy; + use std::sync::Mutex; + use unwrap::unwrap; + + // Empty mutex used to ensure that only one test runs at a time + static MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(())); + + const PROFILE_PATH: &str = "/home/elois/.config/duniter/duniter_default"; + + #[test] + fn blocks() -> DalResult<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + let dal = FileDal::<LevelDb>::init(PROFILE_PATH)?.to_dal_ro(); + + let current_block = unwrap!(dal.get_current_block()?); + + assert_eq!(317_499, current_block.number); + + Ok(()) + } + + #[test] + fn peers() -> DalResult<()> { + let _lock = MUTEX.lock().expect("MUTEX poisoned"); + let dal = FileDal::<LevelDb>::init(PROFILE_PATH)?; + + let _bma_peers = dal.get_peers_with_endpoints_like("%BMAS%")?; + + //println!("{:?}", bma_peers); + + let _elois_peer = dal.get_peer_by_pubkey("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx")?; + + //println!("{:?}", elois_peer); + + println!("count_mirror_peers={:?}", dal.count_mirror_peers()?); + + let up_peers = dal.get_up_peers()?; + println!("up_peers.len()={}", up_peers.len()); + + let new_peer = PeerCardDb { + pubkey: "11111111111111111111111111111111111111111111".to_owned(), + ..Default::default() + }; + + dal.save_peer(new_peer)?; + + Ok(()) + } +}