diff --git a/Cargo.lock b/Cargo.lock index ac57f44232606356f4a1fff74c71684a9f5f7ed1..8e70bf2a0c3826498535a64d81f0d07a5874d3e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" [[package]] name = "arc-swap" @@ -1051,6 +1051,14 @@ dependencies = [ "serde", ] +[[package]] +name = "duniter-conf" +version = "0.1.0" +dependencies = [ + "dubp", + "serde", +] + [[package]] name = "duniter-dbex" version = "0.1.0" @@ -1123,10 +1131,13 @@ version = "0.1.0" dependencies = [ "anyhow", "async-graphql", + "async-trait", "dubp", + "duniter-conf", "duniter-dbs", "duniter-dbs-read-ops", "duniter-mempools", + "duniter-module", "fast-threadpool", "flume", "futures", @@ -1168,20 +1179,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "duniter-module" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "dubp", + "duniter-conf", + "duniter-dbs", + "duniter-mempools", + "fast-threadpool", + "paste", + "tokio", +] + [[package]] name = "duniter-server" version = "1.8.1" dependencies = [ + "anyhow", "dubp", + "duniter-conf", "duniter-dbs", "duniter-dbs-read-ops", "duniter-dbs-write-ops", "duniter-gva", "duniter-mempools", + "duniter-module", "fast-threadpool", "flume", "log", + "paste", "resiter", + "tokio", "unwrap", ] @@ -3463,6 +3494,18 @@ dependencies = [ "num_cpus", "pin-project-lite", "slab", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 006d7cf5993f1243c805e8191b9a406b722b96c3..57b22316f6bbbf3e9285006bd68595cd47ded856 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,14 @@ members = [ "rust-bins/duniter-dbex", "rust-bins/xtask", "rust-libs/dubp-wot", + "rust-libs/duniter-conf", "rust-libs/duniter-dbs", "rust-libs/duniter-dbs-read-ops", "rust-libs/duniter-dbs-write-ops", - "rust-libs/duniter-gva", "rust-libs/duniter-mempools", + "rust-libs/duniter-module", "rust-libs/duniter-server", + "rust-libs/modules/duniter-gva", "rust-libs/tools/kv_typed" ] diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 0353b71176cc52226190ec4101cf0aa0c7d7468f..bf7f13b95da0c03e7401a0679ace65ab60b115ad 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -234,12 +234,12 @@ export class FileDAL implements ServerDAO { } initRustServer(conf: ConfDTO, commandName: string | null = null) { - let selfPubkey = conf.pair ? conf.pair.pub : null; + let selfKeypair = conf.pair ? conf.pair.sec : null; let rustServerConf = { command: commandName, currency: conf.currency || "", gva: conf.gva, - selfPubkey, + selfKeypair, txsMempoolSize: conf.txsMempoolSize || constants.SANDBOX_SIZE_TRANSACTIONS, }; @@ -250,6 +250,10 @@ export class FileDAL implements ServerDAO { } } + getRustEndpoints(): string[] { + return this.rustServer.getSelfEndpoints(); + } + getDBVersion() { return this.metaDAL.getVersion(); } diff --git a/neon/native/server.d.ts b/neon/native/server.d.ts index 0e194c0951b9632e7294788fa44e1485e3f66ec3..4f001ec48a7b60051330429e30d766b3c1b9d142 100644 --- a/neon/native/server.d.ts +++ b/neon/native/server.d.ts @@ -6,7 +6,7 @@ export class RustServerConf { command: string | null currency: string gva: GvaConf | undefined - selfPubkey: string | null + selfKeypair: string | null txsMempoolSize: number } @@ -15,6 +15,11 @@ export class GvaConf { port?: number path?: string; subscriptionsPath?: string; + remoteHost?: string + remotePort?: number + remotePath?: string; + remoteSubscriptionsPath?: string; + remoteTls?: boolean; } export class PeerCard { @@ -90,6 +95,7 @@ export class RustServer { addPendingTx(tx: TransactionDTOV10): void; getMempoolTxsFreeRooms(): number; getNewPendingTxs(): TransactionDTOV10[]; + getSelfEndpoints(): string[]; getTransactionsHistory(pubkey: string): TxsHistory; getTransactionsPending(versionMin: number, medianTime: number): TransactionDTOV10[]; getTxByHash(hash: string): TransactionDTOV10 | null; diff --git a/neon/native/src/crypto.rs b/neon/native/src/crypto.rs index 079916b1a73dd041800c85d78fddc6fbe10a6af6..9430b118eea21148391f359adf2869dd4c68675b 100644 --- a/neon/native/src/crypto.rs +++ b/neon/native/src/crypto.rs @@ -92,7 +92,7 @@ declare_types! { .downcast::<JsString>() .or_throw(&mut cx)? .value(); - into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key)) + into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key).map(|kp| kp.generate_signator())) } else if arg0.is_a::<JsBuffer>() { let seed_js_buffer = arg0 .downcast::<JsBuffer>() @@ -135,9 +135,9 @@ declare_types! { } } -fn keypair_from_expanded_base58_secret_key( +pub(crate) fn keypair_from_expanded_base58_secret_key( expanded_base58_secret_key: &str, -) -> Result<Ed25519Signator, &'static str> { +) -> Result<Ed25519KeyPair, &'static str> { let bytes = bs58::decode(expanded_base58_secret_key) .into_vec() .map_err(|_| "fail to decode b58")?; @@ -152,7 +152,7 @@ fn keypair_from_expanded_base58_secret_key( //let expected_pubkey = Ed25519PublicKey::try_from(pubkey_bytes.as_ref()); if keypair.public_key().as_ref()[..32] == pubkey_bytes { - Ok(keypair.generate_signator()) + Ok(keypair) } else { Err("corrupted keypair") } diff --git a/neon/native/src/server.rs b/neon/native/src/server.rs index 5bf99530dd818821501e6ef85c98765f80c8671d..769318dd08b48386215fef45bd5d9c95dddb867f 100644 --- a/neon/native/src/server.rs +++ b/neon/native/src/server.rs @@ -14,14 +14,14 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use crate::into_neon_res; -use dubp::common::crypto::hashs::Hash; use dubp::common::crypto::keys::{ed25519::PublicKey, PublicKey as _}; use dubp::documents::{ prelude::*, transaction::{TransactionDocumentV10, TransactionDocumentV10Stringified}, }; use dubp::documents_parser::prelude::*; -use duniter_server::{DuniterServer, DuniterServerConf, GvaConf, PeerCardStringified}; +use dubp::{common::crypto::hashs::Hash, crypto::keys::ed25519::Ed25519KeyPair}; +use duniter_server::{DuniterConf, DuniterServer, GvaConf, PeerCardStringified}; use neon::declare_types; use neon::prelude::*; use serde::{Deserialize, Serialize}; @@ -39,35 +39,18 @@ declare_types! { let rust_server_conf_stringified: RustServerConfStringified = neon_serde::from_value(&mut cx, rust_server_conf_js)?; - let gva_conf = if let Some(gva_conf_stringified) = rust_server_conf_stringified.gva { - let mut gva_conf = GvaConf::default(); - if let Some(host) = gva_conf_stringified.host { - gva_conf.host(host); - } - if let Some(port) = gva_conf_stringified.port { - gva_conf.port(port); - } - if let Some(path) = gva_conf_stringified.path { - gva_conf.path(path); - } - if let Some(subscriptions_path) = gva_conf_stringified.subscriptions_path { - gva_conf.subscriptions_path(subscriptions_path); - } - Some(gva_conf) - } else { - None - }; + let gva_conf = rust_server_conf_stringified.gva; let command_name = rust_server_conf_stringified.command_name; let currency = rust_server_conf_stringified.currency; - let server_pubkey = if let Some(self_pubkey_str) = rust_server_conf_stringified.self_pubkey { - into_neon_res(&mut cx, PublicKey::from_base58(&self_pubkey_str))? + let server_pubkey = if let Some(self_keypair_str) = rust_server_conf_stringified.self_keypair { + into_neon_res(&mut cx, crate::crypto::keypair_from_expanded_base58_secret_key(&self_keypair_str))? } else { - PublicKey::default() + Ed25519KeyPair::generate_random().expect("fail to gen random keyypair") }; let txs_mempool_size = rust_server_conf_stringified.txs_mempool_size as usize; - let conf = DuniterServerConf { + let conf = DuniterConf { gva: gva_conf, - self_pubkey: server_pubkey, + self_key_pair: server_pubkey, txs_mempool_size }; @@ -90,13 +73,14 @@ declare_types! { } else { None }; - if let Some(home_path) = home_path_opt { - let server = DuniterServer::start(command_name, conf, currency, Some(home_path.as_path()), std::env!("CARGO_PKG_VERSION")); - Ok(RustServer { server }) - } else { - let server = DuniterServer::start(command_name, conf, currency, None, std::env!("CARGO_PKG_VERSION")); - Ok(RustServer { server }) - } + into_neon_res( + &mut cx, + if let Some(home_path) = home_path_opt { + DuniterServer::start(command_name, conf, currency, Some(home_path.as_path()), std::env!("CARGO_PKG_VERSION")) + } else { + DuniterServer::start(command_name, conf, currency, None, std::env!("CARGO_PKG_VERSION")) + }.map(|server| RustServer { server }) + ) } method acceptNewTx(mut cx) { let tx_js = cx.argument::<JsValue>(0)?; @@ -114,6 +98,22 @@ declare_types! { }.map(|accepted| cx.boolean(accepted).upcast()); into_neon_res(&mut cx, res) } + method getSelfEndpoints(mut cx) { + let this = cx.this(); + let res = { + let guard = cx.lock(); + let server = this.borrow(&guard); + server.server.get_self_endpoints() + }.map(|endpoints| { + let js_array = JsArray::new(&mut cx, endpoints.len() as u32); + for (i, ep) in endpoints.iter().enumerate() { + let js_string = cx.string(ep); + js_array.set(&mut cx, i as u32, js_string).expect("fail to convert Vec<String> to JsArray"); + } + js_array.upcast() + }); + into_neon_res(&mut cx, res) + } method getTxByHash(mut cx) { let hash_str = cx.argument::<JsString>(0)?.value(); let hash = into_neon_res(&mut cx, Hash::from_hex(&hash_str))?; @@ -320,20 +320,11 @@ declare_types! { struct RustServerConfStringified { command_name: Option<String>, currency: String, - gva: Option<GvaConfStringified>, - self_pubkey: Option<String>, + gva: Option<GvaConf>, + self_keypair: Option<String>, txs_mempool_size: u32, } -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct GvaConfStringified { - host: Option<String>, - port: Option<u16>, - path: Option<String>, - subscriptions_path: Option<String>, -} - #[derive(Deserialize, Serialize)] struct TxsHistoryStringified { sent: Vec<DbTx>, diff --git a/rust-libs/duniter-conf/Cargo.toml b/rust-libs/duniter-conf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..71826bc2406aebc7c5365c0322fa921e610a1bf5 --- /dev/null +++ b/rust-libs/duniter-conf/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "duniter-conf" +version = "0.1.0" +authors = ["librelois <elois@duniter.org>"] +license = "AGPL-3.0" +edition = "2018" + +[dependencies] +dubp = { version = "0.30.0" } +serde = { version = "1.0.105", features = ["derive"] } diff --git a/rust-libs/duniter-conf/src/gva_conf.rs b/rust-libs/duniter-conf/src/gva_conf.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d83d311927055b56dbf7c787f8d59cc51f2b390 --- /dev/null +++ b/rust-libs/duniter-conf/src/gva_conf.rs @@ -0,0 +1,96 @@ +// 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 crate::*; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GvaConf { + host: Option<String>, + port: Option<u16>, + path: Option<String>, + subscriptions_path: Option<String>, + remote_host: Option<String>, + remote_port: Option<u16>, + remote_path: Option<String>, + remote_subscriptions_path: Option<String>, + remote_tls: Option<bool>, +} + +impl GvaConf { + pub fn get_host(&self) -> String { + self.host + .to_owned() + .unwrap_or_else(|| "localhost".to_owned()) + } + pub fn get_port(&self) -> u16 { + self.port.unwrap_or(30901) + } + pub fn get_path(&self) -> String { + if let Some(mut path) = self.path.clone() { + if path.starts_with('/') { + path.remove(0); + path + } else { + path + } + } else { + "localhost".to_owned() + } + } + pub fn get_subscriptions_path(&self) -> String { + if let Some(mut subscriptions_path) = self.subscriptions_path.clone() { + if subscriptions_path.starts_with('/') { + subscriptions_path.remove(0); + subscriptions_path + } else { + subscriptions_path + } + } else { + "localhost".to_owned() + } + } + pub fn get_remote_host(&self) -> String { + if let Some(ref remote_host) = self.remote_host { + remote_host.to_owned() + } else { + self.get_host() + } + } + pub fn get_remote_port(&self) -> u16 { + if let Some(remote_port) = self.remote_port { + remote_port + } else { + self.get_port() + } + } + pub fn get_remote_path(&self) -> String { + if let Some(ref remote_path) = self.remote_path { + remote_path.to_owned() + } else { + self.get_path() + } + } + pub fn get_remote_subscriptions_path(&self) -> String { + if let Some(ref remote_subscriptions_path) = self.remote_subscriptions_path { + remote_subscriptions_path.to_owned() + } else { + self.get_subscriptions_path() + } + } + pub fn get_remote_tls(&self) -> bool { + self.remote_tls.unwrap_or(false) + } +} diff --git a/rust-libs/duniter-conf/src/lib.rs b/rust-libs/duniter-conf/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a966b2ab8b169588cc98fca9cba60747877e84b8 --- /dev/null +++ b/rust-libs/duniter-conf/src/lib.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/>. + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces +)] + +pub mod gva_conf; + +use crate::gva_conf::GvaConf; +use dubp::crypto::keys::ed25519::Ed25519KeyPair; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug)] +pub struct DuniterConf { + pub gva: Option<GvaConf>, + pub self_key_pair: Ed25519KeyPair, + pub txs_mempool_size: usize, +} + +impl Default for DuniterConf { + fn default() -> Self { + DuniterConf { + gva: None, + self_key_pair: Ed25519KeyPair::generate_random().expect("fail to gen random keypair"), + txs_mempool_size: 0, + } + } +} diff --git a/rust-libs/duniter-dbs/src/cm_v1.rs b/rust-libs/duniter-dbs/src/cm_v1.rs index 7e1eb4e1e60c8671402cceb93c4f0eb8b96377fd..f94dc21764c4be1fb9d3a0060f6737dd17f28e61 100644 --- a/rust-libs/duniter-dbs/src/cm_v1.rs +++ b/rust-libs/duniter-dbs/src/cm_v1.rs @@ -19,6 +19,6 @@ db_schema!( CmV1, [ //["self_pubkey", self_pubkey, (), PubKeyValV2,], - ["self_peer_card", SelfPeerCard, (), PeerCardDbV1], + ["self_peer_old", SelfPeerOld, (), PeerCardDbV1], ] ); diff --git a/rust-libs/duniter-dbs/src/lib.rs b/rust-libs/duniter-dbs/src/lib.rs index b43c04d5669aff02e85e73ffd74dca59048a29f0..b6c88e9a4f13a9ba1778ebda34b6233fade73fd4 100644 --- a/rust-libs/duniter-dbs/src/lib.rs +++ b/rust-libs/duniter-dbs/src/lib.rs @@ -131,3 +131,17 @@ pub struct DuniterDbs { pub gva_db: GvaV1Db<DbsBackend>, pub txs_mp_db: TxsMpV2Db<DbsBackend>, } + +#[cfg(feature = "mem")] +impl DuniterDbs { + pub fn mem() -> KvResult<Self> { + use bc_v2::BcV2DbWritable as _; + use cm_v1::CmV1DbWritable as _; + Ok(DuniterDbs { + bc_db: bc_v2::BcV2Db::<Mem>::open(MemConf::default())?, + cm_db: cm_v1::CmV1Db::<MemSingleton>::open(MemSingletonConf::default())?, + gva_db: GvaV1Db::<Mem>::open(MemConf::default())?, + txs_mp_db: TxsMpV2Db::<Mem>::open(MemConf::default())?, + }) + } +} diff --git a/rust-libs/duniter-gva/src/lib.rs b/rust-libs/duniter-gva/src/lib.rs deleted file mode 100644 index 7d542244d4f120cec2bab0ad5e72075e4d40b09e..0000000000000000000000000000000000000000 --- a/rust-libs/duniter-gva/src/lib.rs +++ /dev/null @@ -1,253 +0,0 @@ -// 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/>. - -#![deny( - clippy::unwrap_used, - missing_copy_implementations, - trivial_casts, - trivial_numeric_casts, - unstable_features, - unused_import_braces -)] - -mod entities; -mod mutations; -mod queries; -mod schema; -mod subscriptions; -mod warp_; - -use crate::entities::{ - tx_gva::TxGva, - ud_gva::{CurrentUdGva, RevalUdGva, UdGva}, - TxsHistoryGva, UtxoGva, -}; -use crate::schema::{GraphQlSchema, SchemaData}; -use async_graphql::http::GraphQLPlaygroundConfig; -use dubp::common::crypto::keys::{ed25519::PublicKey, PublicKey as _}; -use dubp::common::prelude::*; -use dubp::documents::prelude::*; -use dubp::documents::transaction::{ - TransactionDocumentTrait, TransactionDocumentV10, TransactionDocumentV10Builder, - TransactionInputUnlocksV10, TransactionInputV10, TransactionOutputV10, UTXOConditions, -}; -use dubp::documents_parser::prelude::*; -use dubp::wallet::prelude::*; -use duniter_dbs::prelude::*; -use duniter_dbs::{kv_typed::prelude::*, TxDbV2, TxsMpV2DbReadable}; -use duniter_mempools::TxsMempool; -use futures::{StreamExt, TryStreamExt}; -use resiter::map::Map; -use smallvec::smallvec as svec; -use std::convert::Infallible; -use std::ops::Deref; -use warp::{http::Response as HttpResponse, Filter as _, Rejection, Stream}; - -#[derive(Clone, Debug)] -pub struct GvaConf { - host: String, - port: u16, - path: String, - subscriptions_path: String, -} - -impl Default for GvaConf { - fn default() -> Self { - GvaConf { - host: "localhost".to_owned(), - port: 30901, - path: "gva".to_owned(), - subscriptions_path: "gva-sub".to_owned(), - } - } -} - -impl GvaConf { - pub fn host(&mut self, host: String) { - self.host = host; - } - pub fn port(&mut self, port: u16) { - self.port = port; - } - pub fn path(&mut self, mut path: String) { - if path.starts_with('/') { - path.remove(0); - self.path = path; - } else { - self.path = path; - } - } - pub fn subscriptions_path(&mut self, mut subscriptions_path: String) { - if subscriptions_path.starts_with('/') { - subscriptions_path.remove(0); - self.subscriptions_path = subscriptions_path; - } else { - self.subscriptions_path = subscriptions_path; - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct GvaServer; - -#[derive(Debug, Default)] -pub struct ServerMetaData { - pub currency: String, - pub self_pubkey: PublicKey, - pub software_version: &'static str, -} - -impl GvaServer { - pub fn start( - conf: GvaConf, - dbs: DuniterDbs, - dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, - server_meta_data: ServerMetaData, - txs_mempool: TxsMempool, - ) -> Result<(), tokio::io::Error> { - println!("TMP GvaServer::start: conf={:?}", conf); - let mut runtime = tokio::runtime::Builder::new() - .threaded_scheduler() - .enable_all() - .build()?; - std::thread::spawn(move || { - runtime.block_on(async { - let schema = async_graphql::Schema::build( - queries::QueryRoot::default(), - mutations::MutationRoot::default(), - subscriptions::SubscriptionRoot::default(), - ) - .data(schema::SchemaData { - dbs, - dbs_pool, - server_meta_data, - txs_mempool, - }) - .extension(async_graphql::extensions::Logger) - .finish(); - - let graphql_post = warp_::graphql( - &conf, - schema.clone(), - async_graphql::http::MultipartOptions::default(), - ); - - let conf_clone = conf.clone(); - let graphql_playground = - warp::path::path(conf.path.clone()) - .and(warp::get()) - .map(move || { - HttpResponse::builder() - .header("content-type", "text/html") - .body(async_graphql::http::playground_source( - GraphQLPlaygroundConfig::new(&format!("/{}", &conf_clone.path)) - .subscription_endpoint(&format!( - "/{}", - &conf_clone.subscriptions_path, - )), - )) - }); - - let routes = graphql_playground - .or(graphql_post) - .or(warp_::graphql_ws(&conf, schema.clone())) - .recover(|err: Rejection| async move { - if let Some(warp_::BadRequest(err)) = err.find() { - return Ok::<_, Infallible>(warp::reply::with_status( - err.to_string(), - http::StatusCode::BAD_REQUEST, - )); - } - - Ok(warp::reply::with_status( - "INTERNAL_SERVER_ERROR".to_string(), - http::StatusCode::INTERNAL_SERVER_ERROR, - )) - }); - - log::info!( - "Start GVA server at http://localhost:{}/{}", - conf.port, - &conf.path - ); - warp::serve(routes).run(([0, 0, 0, 0], conf.port)).await; - }); - log::warn!("GVA server stopped"); - }); - Ok(()) - } -} - -#[derive( - async_graphql::SimpleObject, Clone, Debug, Default, serde::Deserialize, serde::Serialize, -)] -#[serde(rename_all = "camelCase")] -#[graphql(name = "PeerCard")] -pub struct PeerCardStringified { - pub version: u32, - pub currency: String, - pub pubkey: String, - pub blockstamp: String, - pub endpoints: Vec<String>, - pub status: String, - pub signature: String, -} -impl From<duniter_dbs::PeerCardDbV1> for PeerCardStringified { - fn from(peer: duniter_dbs::PeerCardDbV1) -> Self { - Self { - version: peer.version, - currency: peer.currency, - pubkey: peer.pubkey, - blockstamp: peer.blockstamp, - endpoints: peer.endpoints, - status: peer.status, - signature: peer.signature, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use duniter_dbs::bc_v2::{BcV2Db, BcV2DbWritable}; - use duniter_dbs::cm_v1::{CmV1Db, CmV1DbWritable}; - use duniter_dbs::kv_typed::backend::memory::{Mem, MemConf}; - use duniter_dbs::{GvaV1Db, GvaV1DbWritable, TxsMpV2Db, TxsMpV2DbWritable}; - use fast_threadpool::ThreadPoolConfig; - use unwrap::unwrap; - - #[test] - #[ignore] - fn launch_mem_gva() { - let dbs = DuniterDbs { - bc_db: unwrap!(BcV2Db::<Mem>::open(MemConf::default())), - cm_db: unwrap!(CmV1Db::<MemSingleton>::open(MemSingletonConf::default())), - gva_db: unwrap!(GvaV1Db::<Mem>::open(MemConf::default())), - txs_mp_db: unwrap!(TxsMpV2Db::<Mem>::open(MemConf::default())), - }; - let threadpool = - fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs.clone()); - - unwrap!(GvaServer::start( - GvaConf::default(), - dbs, - threadpool.into_async_handler(), - ServerMetaData::default(), - TxsMempool::new(10) - )); - - std::thread::sleep(std::time::Duration::from_secs(120)); - } -} diff --git a/rust-libs/duniter-mempools/src/lib.rs b/rust-libs/duniter-mempools/src/lib.rs index c132ff1165e9c25324f474ce67ec970e55ae1db0..9ebbc0f8c8939ab9bb8cb19531ddffa477f18da0 100644 --- a/rust-libs/duniter-mempools/src/lib.rs +++ b/rust-libs/duniter-mempools/src/lib.rs @@ -31,6 +31,11 @@ use duniter_dbs::kv_typed::prelude::*; use duniter_dbs::{GvaV1DbReadable, TxsMpV2Db, TxsMpV2DbReadable}; use thiserror::Error; +#[derive(Clone, Copy, Debug, Default)] +pub struct Mempools { + pub txs: TxsMempool, +} + #[derive(Debug, Error)] pub enum TxMpError { #[error("{0}")] @@ -47,7 +52,7 @@ impl From<KvError> for TxMpError { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct TxsMempool { max_size: usize, } diff --git a/rust-libs/duniter-module/Cargo.toml b/rust-libs/duniter-module/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..8d2b5550472f4c314519396e3d5ea7a178ce27d3 --- /dev/null +++ b/rust-libs/duniter-module/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "duniter-module" +version = "0.1.0" +authors = ["librelois <elois@duniter.org>"] +license = "AGPL-3.0" +edition = "2018" + +[dependencies] +anyhow = "1.0.34" +async-trait = "0.1.41" +dubp = { version = "0.30.0" } +duniter-conf = { path = "../duniter-conf" } +duniter-dbs = { path = "../duniter-dbs" } +duniter-mempools = { path = "../duniter-mempools" } +fast-threadpool = "0.2.1" + +[dev-dependencies] +duniter-dbs = { path = "../duniter-dbs", features = ["mem"] } +paste = "1.0.2" +tokio = { version = "0.2.22", features = ["macros", "rt-core"] } diff --git a/rust-libs/duniter-module/src/lib.rs b/rust-libs/duniter-module/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..28f49c53bfe5e1552957b1e675c2606a00111d23 --- /dev/null +++ b/rust-libs/duniter-module/src/lib.rs @@ -0,0 +1,165 @@ +// 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/>. + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces +)] + +use duniter_conf::DuniterConf; +use duniter_dbs::DuniterDbs; +use duniter_mempools::Mempools; +use std::path::Path; + +pub type Endpoint = String; + +#[async_trait::async_trait] +pub trait DuniterModule: 'static + Sized { + fn init( + conf: &DuniterConf, + currency: &str, + dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + mempools: Mempools, + profile_path_opt: Option<&Path>, + software_version: &'static str, + ) -> anyhow::Result<(Self, Vec<Endpoint>)>; + + async fn start(self) -> anyhow::Result<()>; +} + +#[macro_export] +macro_rules! plug_duniter_modules { + ([$($M:ty),*]) => { + paste::paste! { + use anyhow::Context as _; + async fn start_duniter_modules( + conf: &DuniterConf, + currency: String, + dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + mempools: duniter_mempools::Mempools, + profile_path_opt: Option<std::path::PathBuf>, + software_version: &'static str, + ) -> anyhow::Result<()> { + let mut all_endpoints = Vec::<String>::new(); + $( + let ([<$M:snake>], mut endpoints) =<$M>::init(conf, ¤cy, &dbs_pool, mempools, profile_path_opt.as_deref(), software_version) + .with_context(|| format!("Fail to init module '{}'", stringify!($M)))?; + all_endpoints.append(&mut endpoints); + )* + + let self_peer = duniter_dbs::PeerCardDbV1 { + version: 10, + currency, + endpoints: all_endpoints, + ..Default::default() + }; + + use duniter_dbs::cm_v1::CmV1DbWritable as _; + use duniter_dbs::kv_typed::prelude::DbCollectionRw as _; + dbs_pool.execute(|dbs| dbs.cm_db.self_peer_old_write().upsert((), self_peer)).await?.context("fail to save self peer card")?; + + $( + let [<$M:snake _handle>] = tokio::spawn([<$M:snake>].start()); + )* + + $( + [<$M:snake _handle>].await.map_err(|e| if e.is_cancelled() { + anyhow::Error::msg(format!("Module '{}' cancelled", stringify!($M))) + } else { + anyhow::Error::msg(format!("Module '{}' panic", stringify!($M))) + })? + .with_context(|| format!("Error on execution of module '{}'", stringify!($M)))?; + )* + + Ok(()) + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use duniter_mempools::TxsMempool; + + struct TestMod1; + + #[async_trait::async_trait] + impl DuniterModule for TestMod1 { + fn init( + _conf: &DuniterConf, + _currency: &str, + _dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + _mempools: Mempools, + profile_path_opt: Option<&Path>, + _software_version: &'static str, + ) -> anyhow::Result<(Self, Vec<Endpoint>)> { + if let Some(profile_path) = profile_path_opt { + let _file_path = profile_path.join("test_mod1.json"); + } + Ok((TestMod1, vec![])) + } + + async fn start(self) -> anyhow::Result<()> { + Ok(()) + } + } + + struct TestMod2; + + #[async_trait::async_trait] + impl DuniterModule for TestMod2 { + fn init( + _conf: &DuniterConf, + _currency: &str, + _dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + _mempools: Mempools, + _profile_path_opt: Option<&Path>, + _software_version: &'static str, + ) -> anyhow::Result<(Self, Vec<Endpoint>)> { + Ok((TestMod2, vec![])) + } + + async fn start(self) -> anyhow::Result<()> { + Ok(()) + } + } + + #[tokio::test] + async fn test_macro_plug_duniter_modules() -> anyhow::Result<()> { + plug_duniter_modules!([TestMod1, TestMod2]); + + let dbs = DuniterDbs::mem()?; + let threadpool = + fast_threadpool::ThreadPool::start(fast_threadpool::ThreadPoolConfig::default(), dbs); + + start_duniter_modules( + &DuniterConf::default(), + "test".to_owned(), + threadpool.into_async_handler(), + Mempools { + txs: TxsMempool::new(0), + }, + None, + "", + ) + .await?; + Ok(()) + } +} diff --git a/rust-libs/duniter-server/Cargo.toml b/rust-libs/duniter-server/Cargo.toml index a33c8b3768f4711da0848166f2d7db7c219722b1..ff7017c6608bdc0dea079c4b5c201ed7dfdc259a 100644 --- a/rust-libs/duniter-server/Cargo.toml +++ b/rust-libs/duniter-server/Cargo.toml @@ -6,16 +6,21 @@ license = "AGPL-3.0" edition = "2018" [dependencies] +anyhow = "1.0.34" dubp = { version = "0.30.0" } +duniter-conf = { path = "../duniter-conf" } duniter-dbs = { path = "../duniter-dbs" } duniter-dbs-read-ops = { path = "../duniter-dbs-read-ops" } duniter-dbs-write-ops = { path = "../duniter-dbs-write-ops" } -duniter-gva = { path = "../duniter-gva" } +duniter-gva = { path = "../modules/duniter-gva" } duniter-mempools = { path = "../duniter-mempools" } +duniter-module = { path = "../duniter-module" } fast-threadpool = "0.2.1" flume = "0.9.1" log = "0.4.11" +paste = "1.0.2" resiter = "0.4.0" +tokio = { version = "0.2.22", features = ["io-util", "rt-threaded"] } [dev-dependencies] unwrap = "1.2.1" diff --git a/rust-libs/duniter-server/src/lib.rs b/rust-libs/duniter-server/src/lib.rs index 77bdad24b892da97e3d7a7eeed5d0544881d5237..c1cf343d735becdf915c52913409e2a317adfd06 100644 --- a/rust-libs/duniter-server/src/lib.rs +++ b/rust-libs/duniter-server/src/lib.rs @@ -22,25 +22,26 @@ unused_import_braces )] +pub use duniter_conf::DuniterConf; pub use duniter_dbs::smallvec; -use duniter_gva::ServerMetaData; -use duniter_mempools::{TxMpError, TxsMempool}; -use fast_threadpool::ThreadPoolConfig; - -pub use duniter_gva::{GvaConf, PeerCardStringified}; +pub use duniter_gva::{GvaConf, GvaModule, PeerCardStringified}; +use anyhow::Context; use dubp::common::crypto::keys::ed25519::PublicKey; use dubp::common::prelude::*; use dubp::documents::{prelude::*, transaction::TransactionDocumentV10}; use dubp::{ block::prelude::*, common::crypto::hashs::Hash, documents_parser::prelude::FromStringObject, }; -use duniter_dbs::cm_v1::CmV1DbWritable; +use duniter_dbs::cm_v1::{CmV1DbReadable, CmV1DbWritable}; use duniter_dbs::{ kv_typed::prelude::*, GvaV1DbReadable, HashKeyV2, PendingTxDbV2, TxsMpV2DbReadable, }; use duniter_dbs::{prelude::*, BlockMetaV2}; use duniter_dbs_read_ops::txs_history::TxsHistory; +use duniter_mempools::{Mempools, TxMpError, TxsMempool}; +use duniter_module::{plug_duniter_modules, DuniterModule as _, Endpoint}; +use fast_threadpool::ThreadPoolConfig; use resiter::filter::Filter; use std::{collections::BTreeMap, path::Path}; @@ -50,29 +51,24 @@ pub enum DuniterCommand { Start, } -#[derive(Clone, Debug)] -pub struct DuniterServerConf { - pub gva: Option<GvaConf>, - pub self_pubkey: PublicKey, - pub txs_mempool_size: usize, -} - pub struct DuniterServer { - conf: DuniterServerConf, + conf: DuniterConf, current: Option<BlockMetaV2>, dbs_pool: fast_threadpool::ThreadPoolSyncHandler<DuniterDbs>, pending_txs_subscriber: flume::Receiver<Arc<Events<duniter_dbs::txs_mp_v2::TxsEvent>>>, txs_mempool: TxsMempool, } +plug_duniter_modules!([GvaModule]); + impl DuniterServer { pub fn start( command_name: Option<String>, - conf: DuniterServerConf, + conf: DuniterConf, currency: String, home_path_opt: Option<&Path>, software_version: &'static str, - ) -> Self { + ) -> anyhow::Result<Self> { let command = match command_name.unwrap_or_default().as_str() { "sync" => DuniterCommand::Sync, _ => DuniterCommand::Start, @@ -83,8 +79,8 @@ impl DuniterServer { log::info!("open duniter databases..."); let dbs = duniter_dbs::open_dbs(home_path_opt); log::info!("Databases successfully opened."); - let current = - duniter_dbs_read_ops::get_current_block_meta(&dbs.bc_db).expect("Fail to get current"); + let current = duniter_dbs_read_ops::get_current_block_meta(&dbs.bc_db) + .context("Fail to get current")?; if let Some(current) = current { log::info!("Current block: #{}-{}", current.number, current.hash); } else { @@ -95,46 +91,60 @@ impl DuniterServer { dbs.txs_mp_db .txs() .subscribe(s) - .expect("Fail to subscribe to txs col"); + .context("Fail to subscribe to txs col")?; let threadpool = if home_path_opt.is_some() { log::info!("start dbs threadpool..."); - let threadpool = - fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs.clone()); + let threadpool = fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs); - if command != DuniterCommand::Sync { - if let Some(gva_conf) = conf.gva.clone() { - duniter_gva::GvaServer::start( - gva_conf, - dbs, - threadpool.async_handler(), - ServerMetaData { + if command != DuniterCommand::Sync && conf.gva.is_some() { + let mut runtime = tokio::runtime::Builder::new() + .threaded_scheduler() + .enable_all() + .build()?; + let conf_clone = conf.clone(); + let threadpool_async_handler = threadpool.async_handler(); + std::thread::spawn(move || { + runtime + .block_on(start_duniter_modules( + &conf_clone, currency, - self_pubkey: conf.self_pubkey, + threadpool_async_handler, + Mempools { txs: txs_mempool }, + None, software_version, - }, - txs_mempool, - ) - .expect("Fail to start GVA server"); - } + )) + .context("Fail to start duniter modules") + }); } threadpool } else { fast_threadpool::ThreadPool::start(ThreadPoolConfig::low(), dbs) }; - DuniterServer { + Ok(DuniterServer { conf, current, dbs_pool: threadpool.into_sync_handler(), pending_txs_subscriber, txs_mempool, - } + }) } /* * READ FUNCTIONS FOR DUNITER JS ONLY */ + pub fn get_self_endpoints(&self) -> anyhow::Result<Vec<Endpoint>> { + if let Some(self_peer) = self + .dbs_pool + .execute(|dbs| dbs.cm_db.self_peer_old().get(&()))? + .context("fail to get self endpoints")? + { + Ok(self_peer.endpoints) + } else { + Ok(vec![]) + } + } pub fn accept_new_tx( &self, tx: TransactionDocumentV10, @@ -305,7 +315,7 @@ impl DuniterServer { self.dbs_pool .execute(move |dbs| { dbs.cm_db - .self_peer_card_write() + .self_peer_old_write() .upsert( (), duniter_dbs::PeerCardDbV1 { @@ -327,22 +337,23 @@ impl DuniterServer { #[cfg(test)] mod tests { use super::*; - use dubp::documents::smallvec::smallvec; use dubp::documents::transaction::TransactionDocumentV10Builder; + use dubp::{crypto::keys::ed25519::Ed25519KeyPair, documents::smallvec::smallvec}; #[test] - fn test_txs_history() -> KvResult<()> { + fn test_txs_history() -> anyhow::Result<()> { let server = DuniterServer::start( None, - DuniterServerConf { + DuniterConf { gva: None, - self_pubkey: PublicKey::default(), + self_key_pair: Ed25519KeyPair::generate_random() + .expect("fail to gen random keypair"), txs_mempool_size: 200, }, "currency_test".to_owned(), None, "test", - ); + )?; let tx = TransactionDocumentV10Builder { currency: "duniter_unit_test_currency", diff --git a/rust-libs/duniter-gva/Cargo.toml b/rust-libs/modules/duniter-gva/Cargo.toml similarity index 58% rename from rust-libs/duniter-gva/Cargo.toml rename to rust-libs/modules/duniter-gva/Cargo.toml index 93c56d4099841561d10626e564ef3e3ce353634d..d4e502c5018be35548558fa016abee091cf105e6 100644 --- a/rust-libs/duniter-gva/Cargo.toml +++ b/rust-libs/modules/duniter-gva/Cargo.toml @@ -8,10 +8,13 @@ edition = "2018" [dependencies] anyhow = "1.0.33" async-graphql = "2.0.0" +async-trait = "0.1.41" dubp = { version = "0.30.0" } -duniter-dbs = { path = "../duniter-dbs" } -duniter-dbs-read-ops = { path = "../duniter-dbs-read-ops" } -duniter-mempools = { path = "../duniter-mempools" } +duniter-conf = { path = "../../duniter-conf" } +duniter-dbs = { path = "../../duniter-dbs" } +duniter-dbs-read-ops = { path = "../../duniter-dbs-read-ops" } +duniter-mempools = { path = "../../duniter-mempools" } +duniter-module = { path = "../../duniter-module" } fast-threadpool = "0.2.1" flume = "0.9.1" futures = "0.3.6" @@ -21,9 +24,10 @@ resiter = "0.4.0" serde = { version = "1.0.105", features = ["derive"] } serde_urlencoded = "0.7.0" smallvec = { version = "1.4.0", features = ["serde", "write"] } -tokio = { version = "0.2.22", features = ["io-util", "rt-threaded", "stream"] } +tokio = { version = "0.2.22", features = ["io-util", "rt-threaded"] } warp = "0.2" [dev-dependencies] -duniter-dbs = { path = "../duniter-dbs", features = ["mem"] } +duniter-dbs = { path = "../../duniter-dbs", features = ["mem"] } +tokio = { version = "0.2.22", features = ["macros", "rt-threaded"] } unwrap = "1.2.1" diff --git a/rust-libs/duniter-gva/src/entities.rs b/rust-libs/modules/duniter-gva/src/entities.rs similarity index 100% rename from rust-libs/duniter-gva/src/entities.rs rename to rust-libs/modules/duniter-gva/src/entities.rs diff --git a/rust-libs/duniter-gva/src/entities/tx_gva.rs b/rust-libs/modules/duniter-gva/src/entities/tx_gva.rs similarity index 100% rename from rust-libs/duniter-gva/src/entities/tx_gva.rs rename to rust-libs/modules/duniter-gva/src/entities/tx_gva.rs diff --git a/rust-libs/duniter-gva/src/entities/ud_gva.rs b/rust-libs/modules/duniter-gva/src/entities/ud_gva.rs similarity index 100% rename from rust-libs/duniter-gva/src/entities/ud_gva.rs rename to rust-libs/modules/duniter-gva/src/entities/ud_gva.rs diff --git a/rust-libs/duniter-gva/src/inputs_validators.rs b/rust-libs/modules/duniter-gva/src/inputs_validators.rs similarity index 100% rename from rust-libs/duniter-gva/src/inputs_validators.rs rename to rust-libs/modules/duniter-gva/src/inputs_validators.rs diff --git a/rust-libs/modules/duniter-gva/src/lib.rs b/rust-libs/modules/duniter-gva/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e680ee61689e22b1b7923ec21759363a468b8de2 --- /dev/null +++ b/rust-libs/modules/duniter-gva/src/lib.rs @@ -0,0 +1,287 @@ +// 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/>. + +#![deny( + clippy::unwrap_used, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces +)] + +pub use duniter_conf::gva_conf::GvaConf; + +mod entities; +mod mutations; +mod queries; +mod schema; +mod subscriptions; +mod warp_; + +use crate::entities::{ + tx_gva::TxGva, + ud_gva::{CurrentUdGva, RevalUdGva, UdGva}, + TxsHistoryGva, UtxoGva, +}; +use crate::schema::{GraphQlSchema, SchemaData}; +use async_graphql::http::GraphQLPlaygroundConfig; +use dubp::common::crypto::keys::{ed25519::PublicKey, KeyPair as _, PublicKey as _}; +use dubp::common::prelude::*; +use dubp::documents::prelude::*; +use dubp::documents::transaction::{ + TransactionDocumentTrait, TransactionDocumentV10, TransactionDocumentV10Builder, + TransactionInputUnlocksV10, TransactionInputV10, TransactionOutputV10, UTXOConditions, +}; +use dubp::documents_parser::prelude::*; +use dubp::wallet::prelude::*; +use duniter_dbs::prelude::*; +use duniter_dbs::{kv_typed::prelude::*, TxDbV2, TxsMpV2DbReadable}; +use duniter_mempools::{Mempools, TxsMempool}; +use futures::{StreamExt, TryStreamExt}; +use resiter::map::Map; +use smallvec::smallvec as svec; +use std::convert::Infallible; +use std::ops::Deref; +use warp::{http::Response as HttpResponse, Filter as _, Rejection, Stream}; + +#[derive(Debug)] +pub struct GvaModule { + conf: Option<GvaConf>, + currency: String, + dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + mempools: Mempools, + self_pubkey: PublicKey, + software_version: &'static str, +} + +#[async_trait::async_trait] +impl duniter_module::DuniterModule for GvaModule { + fn init( + conf: &duniter_conf::DuniterConf, + currency: &str, + dbs_pool: &fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + mempools: Mempools, + _profile_path_opt: Option<&std::path::Path>, + software_version: &'static str, + ) -> anyhow::Result<(Self, Vec<duniter_module::Endpoint>)> { + let mut endpoints = Vec::new(); + if let Some(conf) = conf.gva.clone() { + let remote_port = conf.get_remote_port(); + endpoints.push(format!( + "GVA {}{} {} {}", + if remote_port == 443 || conf.get_remote_tls() { + "S " + } else { + "" + }, + conf.get_remote_host(), + remote_port, + conf.get_remote_path(), + )); + endpoints.push(format!( + "GVASUB {}{} {} {}", + if remote_port == 443 || conf.get_remote_tls() { + "S " + } else { + "" + }, + conf.get_remote_host(), + remote_port, + conf.get_remote_subscriptions_path(), + )); + }; + Ok(( + GvaModule { + conf: conf.gva.to_owned(), + currency: currency.to_owned(), + dbs_pool: dbs_pool.to_owned(), + mempools, + self_pubkey: conf.self_key_pair.public_key(), + software_version, + }, + endpoints, + )) + } + + async fn start(self) -> anyhow::Result<()> { + let GvaModule { + conf, + currency, + dbs_pool, + mempools, + self_pubkey, + software_version, + } = self; + + if let Some(conf) = conf { + GvaModule::start_inner( + conf, + currency, + dbs_pool, + mempools, + self_pubkey, + software_version, + ) + .await + } + Ok(()) + } +} + +impl GvaModule { + async fn start_inner( + conf: GvaConf, + currency: String, + dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, + mempools: Mempools, + self_pubkey: PublicKey, + software_version: &'static str, + ) { + log::info!("GvaServer::start: conf={:?}", conf); + let schema = async_graphql::Schema::build( + queries::QueryRoot::default(), + mutations::MutationRoot::default(), + subscriptions::SubscriptionRoot::default(), + ) + .data(schema::SchemaData { + dbs_pool, + server_meta_data: ServerMetaData { + currency, + self_pubkey, + software_version, + }, + txs_mempool: mempools.txs, + }) + .extension(async_graphql::extensions::Logger) + .finish(); + + let graphql_post = warp_::graphql( + &conf, + schema.clone(), + async_graphql::http::MultipartOptions::default(), + ); + + let conf_clone = conf.clone(); + let graphql_playground = + warp::path::path(conf.get_path()) + .and(warp::get()) + .map(move || { + HttpResponse::builder() + .header("content-type", "text/html") + .body(async_graphql::http::playground_source( + GraphQLPlaygroundConfig::new(&format!("/{}", &conf_clone.get_path())) + .subscription_endpoint(&format!( + "/{}", + &conf_clone.get_subscriptions_path(), + )), + )) + }); + + let routes = graphql_playground + .or(graphql_post) + .or(warp_::graphql_ws(&conf, schema.clone())) + .recover(|err: Rejection| async move { + if let Some(warp_::BadRequest(err)) = err.find() { + return Ok::<_, Infallible>(warp::reply::with_status( + err.to_string(), + http::StatusCode::BAD_REQUEST, + )); + } + + Ok(warp::reply::with_status( + "INTERNAL_SERVER_ERROR".to_string(), + http::StatusCode::INTERNAL_SERVER_ERROR, + )) + }); + + log::info!( + "GVA server listen on http://{}:{}/{}", + &conf.get_host(), + conf.get_port(), + &conf.get_path() + ); + warp::serve(routes) + .run(([0, 0, 0, 0], conf.get_port())) + .await; + log::warn!("GVA server stopped"); + } +} + +#[derive(Debug, Default)] +pub struct ServerMetaData { + pub currency: String, + pub self_pubkey: PublicKey, + pub software_version: &'static str, +} + +#[derive( + async_graphql::SimpleObject, Clone, Debug, Default, serde::Deserialize, serde::Serialize, +)] +#[serde(rename_all = "camelCase")] +#[graphql(name = "PeerCard")] +pub struct PeerCardStringified { + pub version: u32, + pub currency: String, + pub pubkey: String, + pub blockstamp: String, + pub endpoints: Vec<String>, + pub status: String, + pub signature: String, +} +impl From<duniter_dbs::PeerCardDbV1> for PeerCardStringified { + fn from(peer: duniter_dbs::PeerCardDbV1) -> Self { + Self { + version: peer.version, + currency: peer.currency, + pubkey: peer.pubkey, + blockstamp: peer.blockstamp, + endpoints: peer.endpoints, + status: peer.status, + signature: peer.signature, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use duniter_conf::DuniterConf; + use duniter_mempools::Mempools; + use duniter_module::DuniterModule; + use fast_threadpool::ThreadPoolConfig; + use unwrap::unwrap; + + #[tokio::test] + #[ignore] + async fn launch_mem_gva() -> anyhow::Result<()> { + let dbs = unwrap!(DuniterDbs::mem()); + let threadpool = fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs); + + GvaModule::init( + &DuniterConf::default(), + "", + &threadpool.into_async_handler(), + Mempools::default(), + None, + "test", + )? + .0 + .start() + .await?; + + Ok(()) + } +} diff --git a/rust-libs/duniter-gva/src/mutations.rs b/rust-libs/modules/duniter-gva/src/mutations.rs similarity index 100% rename from rust-libs/duniter-gva/src/mutations.rs rename to rust-libs/modules/duniter-gva/src/mutations.rs diff --git a/rust-libs/duniter-gva/src/queries.rs b/rust-libs/modules/duniter-gva/src/queries.rs similarity index 96% rename from rust-libs/duniter-gva/src/queries.rs rename to rust-libs/modules/duniter-gva/src/queries.rs index 744263987f573a49526b4fd749c5c1ee78b5ba8e..11d8e29c5c6320362aeebaa0f0210c6a04bab827 100644 --- a/rust-libs/duniter-gva/src/queries.rs +++ b/rust-libs/modules/duniter-gva/src/queries.rs @@ -49,7 +49,7 @@ impl Node { Ok(data .dbs_pool - .execute(move |dbs| dbs.cm_db.self_peer_card().get(&())) + .execute(move |dbs| dbs.cm_db.self_peer_old().get(&())) .await?? .map(Into::into)) } diff --git a/rust-libs/duniter-gva/src/queries/gen_txs.rs b/rust-libs/modules/duniter-gva/src/queries/gen_txs.rs similarity index 100% rename from rust-libs/duniter-gva/src/queries/gen_txs.rs rename to rust-libs/modules/duniter-gva/src/queries/gen_txs.rs diff --git a/rust-libs/duniter-gva/src/queries/txs_history.rs b/rust-libs/modules/duniter-gva/src/queries/txs_history.rs similarity index 100% rename from rust-libs/duniter-gva/src/queries/txs_history.rs rename to rust-libs/modules/duniter-gva/src/queries/txs_history.rs diff --git a/rust-libs/duniter-gva/src/queries/uds.rs b/rust-libs/modules/duniter-gva/src/queries/uds.rs similarity index 100% rename from rust-libs/duniter-gva/src/queries/uds.rs rename to rust-libs/modules/duniter-gva/src/queries/uds.rs diff --git a/rust-libs/duniter-gva/src/queries/utxos.rs b/rust-libs/modules/duniter-gva/src/queries/utxos.rs similarity index 100% rename from rust-libs/duniter-gva/src/queries/utxos.rs rename to rust-libs/modules/duniter-gva/src/queries/utxos.rs diff --git a/rust-libs/duniter-gva/src/schema.rs b/rust-libs/modules/duniter-gva/src/schema.rs similarity index 97% rename from rust-libs/duniter-gva/src/schema.rs rename to rust-libs/modules/duniter-gva/src/schema.rs index 9618f6ee785bd8677fb54d5b73933c58c94e4cd7..c830bb85ba6715882c372d48061db5ef7e225214 100644 --- a/rust-libs/duniter-gva/src/schema.rs +++ b/rust-libs/modules/duniter-gva/src/schema.rs @@ -21,7 +21,6 @@ pub(crate) type GraphQlSchema = async_graphql::Schema< crate::subscriptions::SubscriptionRoot, >; pub(crate) struct SchemaData { - pub(crate) dbs: DuniterDbs, pub(crate) dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<DuniterDbs>, pub(crate) server_meta_data: ServerMetaData, pub(crate) txs_mempool: TxsMempool, diff --git a/rust-libs/duniter-gva/src/subscriptions.rs b/rust-libs/modules/duniter-gva/src/subscriptions.rs similarity index 90% rename from rust-libs/duniter-gva/src/subscriptions.rs rename to rust-libs/modules/duniter-gva/src/subscriptions.rs index 5be753736d7d068cfd68a7e57c28150963fbd076..7dd697b27a26cd9453803553aebf19713732a1ab 100644 --- a/rust-libs/duniter-gva/src/subscriptions.rs +++ b/rust-libs/modules/duniter-gva/src/subscriptions.rs @@ -28,11 +28,10 @@ impl SubscriptionRoot { let (s, r) = flume::unbounded(); - data.dbs - .txs_mp_db - .txs() - .subscribe(s) - .expect("fail to access db"); + data.dbs_pool + .execute(|dbs| dbs.txs_mp_db.txs().subscribe(s).expect("fail to access db")) + .await + .expect("dbs pool disconnected"); r.into_stream().filter_map(|events| { let mut txs = Vec::new(); diff --git a/rust-libs/duniter-gva/src/warp_.rs b/rust-libs/modules/duniter-gva/src/warp_.rs similarity index 98% rename from rust-libs/duniter-gva/src/warp_.rs rename to rust-libs/modules/duniter-gva/src/warp_.rs index bc8aba0c7f28f8fa9ac5d2745f2fd9596c9cb40d..5bf78f67a81a230ae88d8a03bd82d466cc1975a8 100644 --- a/rust-libs/duniter-gva/src/warp_.rs +++ b/rust-libs/modules/duniter-gva/src/warp_.rs @@ -55,7 +55,7 @@ pub(crate) fn graphql( opts: async_graphql::http::MultipartOptions, ) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone { let opts = Arc::new(opts); - warp::path::path(conf.path.clone()) + warp::path::path(conf.get_path()) .and(warp::method()) .and(warp::query::raw().or(warp::any().map(String::new)).unify()) .and(warp::header::optional::<String>("content-type")) @@ -100,7 +100,7 @@ pub(crate) fn graphql_ws( conf: &GvaConf, schema: GraphQlSchema, ) -> impl warp::Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone { - warp::path::path(conf.subscriptions_path.clone()) + warp::path::path(conf.get_subscriptions_path()) .and(warp::ws()) .and(warp::any().map(move || schema.clone())) .map(|ws: warp::ws::Ws, schema: GraphQlSchema| { diff --git a/server.ts b/server.ts index ca8e26559aa8b9f20b4b0184f4a750031b7be4bb..2387e4802db1e90b6bcbf560a5fb1d23f10e3b46 100644 --- a/server.ts +++ b/server.ts @@ -353,8 +353,13 @@ export class Server extends stream.Duplex implements HookableServer { } async initDAL(conf:ConfDTO|null = null, commandName: string|null = null) { - this.genGvaEndpoints(this.conf); + // Init DAL await this.dal.init(this.conf, commandName); + // Get rust endpoints + for (let endpoint of this.dal.getRustEndpoints()) { + logger.info("TMP: rustEndpoint: %s", endpoint); + this.addEndpointsDefinitions(async () => endpoint); + } // Maintenance let head_1 = await this.dal.bindexDAL.head(1); if (head_1) { @@ -383,36 +388,6 @@ export class Server extends stream.Duplex implements HookableServer { await this.BlockchainService.forkResolution() } - genGvaEndpoint(conf: ConfDTO): string { - return "" - } - - genGvaSubscriptionsEndpoint(conf: ConfDTO): string { - return "" - } - - genGvaEndpoints(conf:ConfDTO) { - if (conf.gva) { - let gva = conf.gva; - this.addEndpointsDefinitions(async () => format( - "GVA %s%s %i %s", - (gva.remotePort === 443 || gva.remoteTls === true) ? 'S ':'', - gva.remoteHost || gva.host || "localhost", - gva.remotePort || gva.port || 80, - gva.remotePath || gva.path || "" - )); - if (gva.remoteSubscriptionsPath || gva.subscriptionsPath) { - this.addEndpointsDefinitions(async () => format( - "GVASUB %s%s %i %s", - (gva.remotePort === 443 || gva.remoteTls === true) ? 'S ':'', - gva.remoteHost || gva.host || "localhost", - gva.remotePort || gva.port || 80, - gva.remoteSubscriptionsPath || gva.subscriptionsPath || "" - )); - } - } - } - recomputeSelfPeer(): Promise<DBPeer | null> { return this.PeeringService.generateSelfPeer(this.conf) }