diff --git a/bin/dunitrust-server/Cargo.toml b/bin/dunitrust-server/Cargo.toml index 0049af47dea273aa313973387c861cb084cdd7ae..9d670032ced6c86224465c8ec4e439042dc2d9fe 100644 --- a/bin/dunitrust-server/Cargo.toml +++ b/bin/dunitrust-server/Cargo.toml @@ -14,13 +14,14 @@ edition = "2018" [dependencies] durs-network = { path = "../../lib/core/network" } durs-core = { path = "../../lib/core/core" } +durs-gva = { path = "../../lib/modules/gva" } durs-module = { path = "../../lib/core/module" } #durs-skeleton = { path = "../../lib/modules/skeleton" } durs-ws2p = { path = "../../lib/modules/ws2p/ws2p" } durs-ws2p-v1-legacy = { path = "../../lib/modules/ws2p-v1-legacy" } -log = "0.4.*" -structopt = "0.2.*" -human-panic = "1.0.*" +human-panic = "1.0.1" +log = "0.4.8" +structopt= "0.2.18" [target.'cfg(unix)'.dependencies] durs-tui = { path = "../../lib/modules/tui" } diff --git a/bin/dunitrust-server/src/main.rs b/bin/dunitrust-server/src/main.rs index b0f123bf61d381c11ee851b5a5467a2b880efe79..00c91cef8a93a6e4a4c78121a0fd2d3f61ed0b22 100644 --- a/bin/dunitrust-server/src/main.rs +++ b/bin/dunitrust-server/src/main.rs @@ -36,6 +36,7 @@ use durs_core::durs_plug; use log::error; use structopt::StructOpt; +pub use durs_gva::GvaModule; #[cfg(unix)] pub use durs_tui::TuiModule; //pub use durs_skeleton::SkeletonModule; @@ -63,7 +64,7 @@ macro_rules! durs_cli_main { fn main() { durs_cli_main!(durs_plug!( [WS2Pv1Module, WS2PModule], - [TuiModule /*, SkeletonModule ,DasaModule*/] + [TuiModule, GvaModule /*, SkeletonModule ,DasaModule*/] )) } #[cfg(unix)] diff --git a/lib/modules/gva/Cargo.toml b/lib/modules/gva/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b6f91e4c9ffd266e8b37f62fffeff0d6ea4900b6 --- /dev/null +++ b/lib/modules/gva/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "durs-gva" +version = "0.1.0" +authors = ["name <jm81@tuta.io>"] +description = "Web client api" +license = "AGPL-3.0" +edition = "2018" + +[lib] +path = "src/lib.rs" + +[dependencies] +proc-macro2 = "1.0.6" +actix-web = "1.0.8" +dubp-block-doc = { path = "../../dubp/block-doc"} #, version = "0.1.0" } +durs-bc-db-reader = { path = "../../modules-lib/bc-db-reader" } +durs-conf = { path = "../../core/conf" } +durs-message = { path = "../../core/message" } +durs-module = { path = "../../core/module" } +durs-network = { path = "../../core/network" } +dubp-common-doc = { path = "../../dubp/common-doc"} #, version = "0.1.0" } +durs-common-tools = { path = "../../tools/common-tools" } +dubp-currency-params = { path = "../../dubp/currency-params" } +failure = "0.1.5" +futures = "0.1" +futures-cpupool = "0.1" +juniper = "0.14.0" + +juniper-from-schema = "0.5.0" +log = "0.4.8" +serde = "1.0.102" +serde_derive = "1.0.102" +serde_json = "1.0.41" +structopt= "0.2.18" + +[features] diff --git a/lib/modules/gva/resources/schema.gql b/lib/modules/gva/resources/schema.gql new file mode 100644 index 0000000000000000000000000000000000000000..8115701e38909dfbad1d2fcd78f3dce48e9d062a --- /dev/null +++ b/lib/modules/gva/resources/schema.gql @@ -0,0 +1,20 @@ + +schema { + query: Query + mutation: Mutation +} + +type Query { + current: Block @juniper(ownership: "owned") +} + +type Mutation { + noop: Boolean! +} + +type Block { + version: Int! + currency: String! + issuer: String! + number: Int! +} diff --git a/lib/modules/gva/src/context.rs b/lib/modules/gva/src/context.rs new file mode 100644 index 0000000000000000000000000000000000000000..29955f759cc604acec7fc24ce7c8fbd28c86aa55 --- /dev/null +++ b/lib/modules/gva/src/context.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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 durs_bc_db_reader::BcDbRo; +use durs_common_tools::fatal_error; + +/// GVA context (access to database) +static mut CONTEXT: Option<Context> = None; + +#[derive(Debug)] +pub struct Context { + db: BcDbRo, +} + +impl juniper::Context for Context {} + +impl Context { + pub fn new(db: BcDbRo) -> Self { + Context { db } + } + + pub fn get_db(&self) -> &BcDbRo { + &self.db + } +} + +pub fn init(db: BcDbRo) { + unsafe { + CONTEXT.replace(Context::new(db)); + } +} + +pub fn get_context() -> &'static Context { + unsafe { + if let Some(ref context) = CONTEXT { + context + } else { + fatal_error!("GVA: no context"); + } + } +} diff --git a/lib/modules/gva/src/lib.rs b/lib/modules/gva/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..f044741b3ba3fdac3bb1598b6918f4f7da127bb7 --- /dev/null +++ b/lib/modules/gva/src/lib.rs @@ -0,0 +1,345 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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/>. + +//! Gva Module +// This module provides a graphql API implementation of the 0003 RFC +// +// /src/schema.gql contains schema description +// /src/schema.rs contains model and resolvers implementation +// /src/webserver.rs contains web server implementaion based on actix-web +// +// Graphiql web client is accessible at +// http://127.0.0.1:3000/graphiql + +#![deny( + missing_docs, + missing_debug_implementations, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces, + unused_qualifications +)] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate structopt; + +extern crate juniper; + +mod context; +mod schema; +mod webserver; + +use dubp_currency_params::CurrencyName; +use durs_common_tools::fatal_error; +use durs_common_tools::traits::merge::Merge; +use durs_conf::DuRsConf; +use durs_message::events::*; +use durs_message::*; +use durs_module::*; +use durs_network::events::NetworkEvent; + +use std::ops::Deref; +use std::sync::mpsc; +use std::thread; +use std::time::{Duration, SystemTime}; + +static MODULE_NAME: &str = "gva"; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Gva Module Configuration +pub struct GvaConf { + test_fake_conf_field: String, +} + +impl Default for GvaConf { + fn default() -> Self { + GvaConf { + test_fake_conf_field: String::from("default value"), + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Gva user Configuration +pub struct GvaUserConf { + test_fake_conf_field: Option<String>, +} + +impl Merge for GvaUserConf { + fn merge(self, other: Self) -> Self { + GvaUserConf { + test_fake_conf_field: self.test_fake_conf_field.or(other.test_fake_conf_field), + } + } +} + +#[derive(Debug, Copy, Clone)] +/// Message from others thread of gva module +pub enum GvaThreadMsg {} + +#[derive(Debug, Clone)] +/// Format of messages received by the gva module +pub enum GvaMsg { + /// Message from another module + DursMsg(Box<DursMsg>), + /// Message from others thread of gva module + GvaThreadMsg(GvaThreadMsg), +} + +#[derive(StructOpt, Debug, Clone)] +#[structopt( + name = "gva", + raw(setting = "structopt::clap::AppSettings::ColoredHelp") +)] +/// Gva subcommand options +pub struct GvaOpt { + /// Change test conf fake field + pub new_conf_field: String, +} + +#[derive(Debug, Clone)] +/// Data that the Gva module needs to cache +pub struct GvaModuleDatas { + /// Sender of all child threads (except the proxy thread) + pub child_threads: Vec<mpsc::Sender<GvaMsg>>, + /// Any data + pub field: usize, +} + +#[derive(Debug, Copy, Clone)] +/// Gva module +pub struct GvaModule {} + +impl Default for GvaModule { + fn default() -> GvaModule { + GvaModule {} + } +} + +impl DursModule<DuRsConf, DursMsg> for GvaModule { + type ModuleConf = GvaConf; + type ModuleUserConf = GvaUserConf; + type ModuleOpt = GvaOpt; + + fn name() -> ModuleStaticName { + ModuleStaticName(MODULE_NAME) + } + fn priority() -> ModulePriority { + ModulePriority::Recommended() + } + fn ask_required_keys() -> RequiredKeys { + RequiredKeys::None() + } + fn have_subcommand() -> bool { + false + } + fn generate_module_conf( + _currency_name: Option<&CurrencyName>, + _global_conf: &<DuRsConf as DursConfTrait>::GlobalConf, + module_user_conf: Option<Self::ModuleUserConf>, + ) -> Result<(Self::ModuleConf, Option<Self::ModuleUserConf>), ModuleConfError> { + let mut conf = GvaConf::default(); + + if let Some(ref module_user_conf) = module_user_conf { + if let Some(ref test_fake_conf_field) = module_user_conf.test_fake_conf_field { + conf.test_fake_conf_field = test_fake_conf_field.to_owned(); + } + } + + Ok((conf, module_user_conf)) + } + fn exec_subcommand( + _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + _keys: RequiredKeysContent, + module_conf: Self::ModuleConf, + _module_user_conf: Option<Self::ModuleUserConf>, + subcommand_args: Self::ModuleOpt, + ) -> Option<Self::ModuleUserConf> { + let new_gva_conf = GvaUserConf { + test_fake_conf_field: Some(subcommand_args.new_conf_field.to_owned()), + }; + info!( + "Succesfully exec skeleton subcommand whit terminal name : {} and conf={:?}!", + subcommand_args.new_conf_field, module_conf + ); + Some(new_gva_conf) + } + fn start( + soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + _keys: RequiredKeysContent, + _conf: Self::ModuleConf, + router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, + ) -> Result<(), failure::Error> { + let _start_time = SystemTime::now(); + + // Instanciate Gva module datas + let datas = GvaModuleDatas { + child_threads: Vec::new(), + field: 3, + }; + + // Create gva main thread channel + let (gva_sender, gva_receiver): (mpsc::Sender<GvaMsg>, mpsc::Receiver<GvaMsg>) = + mpsc::channel(); + + // Create proxy channel + let (proxy_sender, proxy_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) = + mpsc::channel(); + + // Launch a proxy thread that transform DursMsgContent() to GvaMsg::DursMsgContent(DursMsgContent()) + let router_sender_clone = router_sender.clone(); + let gva_sender_clone = gva_sender.clone(); + thread::spawn(move || { + // Send gva module registration to router thread + router_sender_clone + .send(RouterThreadMessage::ModuleRegistration { + static_name: ModuleStaticName(MODULE_NAME), + sender: proxy_sender, // Messages sent by the router will be received by your proxy thread + roles: vec![ModuleRole::UserInterface], // Roles assigned to your module + events_subscription: vec![ModuleEvent::NewValidBlock], // Events to which your module subscribes + reserved_apis_parts: vec![], + endpoints: vec![], + }) + .expect("Fatal error : gva module fail to register to router !"); // The registration of your module must be successful, in case of failure the program must be interrupted. + + // If we are here it means that your module has successfully registered, we indicate it in the debug level log, it can be helpful. + debug!("Send gva module registration to router thread."); + + /* + * Main loop of your proxy thread + */ + loop { + match proxy_receiver.recv() { + Ok(message) => { + let stop = if let DursMsg::Stop = message { + true + } else { + false + }; + if gva_sender_clone + .send(GvaMsg::DursMsg(Box::new(message))) + .is_err() + { + // Log error + warn!("Gva proxy : fail to relay DursMsg to gva main thread !") + } + if stop { + break; + } + } + Err(e) => { + // Log error + warn!("{}", e); + break; + } + } + } + }); + + let smd: SoftwareMetaDatas<DuRsConf> = soft_meta_datas.clone(); + let router_sender_clone = router_sender.clone(); + thread::spawn(move || { + match webserver::start_web_server(&smd) { + Ok(_) => { + info!("GVA http web server stop."); + } + Err(e) => { + error!("GVA http web server error : {} ", e); + } + } + let _result = + router_sender_clone.send(RouterThreadMessage::ModuleMessage(DursMsg::Stop)); + }); + + /* + * Main loop of your module + */ + loop { + // Get messages + match gva_receiver.recv_timeout(Duration::from_millis(250)) { + Ok(ref message) => match *message { + GvaMsg::DursMsg(ref durs_message) => { + match durs_message.deref() { + DursMsg::Stop => { + // Relay stop signal to all child threads + let _result_stop_propagation: Result<(), mpsc::SendError<GvaMsg>> = + datas + .child_threads + .iter() + .map(|t| t.send(GvaMsg::DursMsg(Box::new(DursMsg::Stop)))) + .collect(); + // Relay stop signal to router + let _result = router_sender + .send(RouterThreadMessage::ModuleMessage(DursMsg::Stop)); + // Break main loop + break; + } + DursMsg::Event { + ref event_content, .. + } => match *event_content { + DursEvent::BlockchainEvent(ref blockchain_event) => { + match *blockchain_event.deref() { + BlockchainEvent::StackUpValidBlock(ref _block) => { + // Do something when the node has stacked a new block at its local blockchain + } + BlockchainEvent::RevertBlocks(ref _blocks) => { + // Do something when the node has destacked blocks from its local blockchain (roll back) + } + _ => {} // Do nothing for events that don't concern your module. + } + } + DursEvent::NetworkEvent(ref network_event_box) => { + match *network_event_box.deref() { + NetworkEvent::ReceivePeers(ref _peers) => { + // Do something when the node receive peers cards from network + } + NetworkEvent::ReceiveDocuments(ref _bc_documents) => { + // Do something when the node receive blockchain documents from network + } + _ => {} // Do nothing for events that don't concern your module. + } + } + _ => {} // Do nothing for DursEvent variants that don't concern your module. + }, + _ => {} // Do nothing for DursMsgContent variants that don't concern your module. + } + } + GvaMsg::GvaThreadMsg(ref _child_thread_msg) => { + // Do something when receive a message from child thread. + } + }, + Err(e) => match e { + mpsc::RecvTimeoutError::Disconnected => { + fatal_error!("Disconnected gva module !"); + } + mpsc::RecvTimeoutError::Timeout => { + // If you arrive here it's because your main thread did not receive anything at the end of the timeout. + // This is quite normal and happens regularly when there is little activity, there is nothing particular to do. + } + }, + } + // If you want your module's main thread to do things even when it doesn't receive any messages, this is the place where it can do them. + // ... + } + // If we reach this point it means that the module has stopped correctly, so we return OK. + Ok(()) + } +} diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs new file mode 100644 index 0000000000000000000000000000000000000000..678769e2bfca9ab6b8a248e64afa80168f727269 --- /dev/null +++ b/lib/modules/gva/src/schema.rs @@ -0,0 +1,96 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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::context::Context; +use dubp_block_doc::block::BlockDocumentTrait; +use dubp_common_doc::traits::Document; +use durs_bc_db_reader::BcDbRo; +use juniper::Executor; +use juniper::FieldResult; +use juniper_from_schema::graphql_schema_from_file; + +graphql_schema_from_file!("resources/schema.gql"); + +pub struct Query; + +pub struct Block { + version: i32, + currency: String, + issuer: String, + number: i32, +} + +impl QueryFields for Query { + fn field_current( + &self, + _executor: &Executor<'_, Context>, + _trail: &QueryTrail<'_, Block, Walked>, + ) -> FieldResult<Option<Block>> { + let db: &BcDbRo = &_executor.context().get_db(); + let current_blockstamp = durs_bc_db_reader::current_meta_datas::get_current_blockstamp(db); + + match current_blockstamp { + Ok(option) => match option { + Some(v) => { + let current_block = durs_bc_db_reader::blocks::get_block(db, v); + match current_block { + Ok(current_block_option) => match current_block_option { + Some(block) => Ok(Some(Block { + version: block.block.version() as i32, + currency: block.block.currency().to_string(), + issuer: block.block.issuers()[0].to_string(), + number: block.block.number().0 as i32, + })), + None => Ok(None), + }, + Err(_e) => Err(juniper::FieldError::from("No current block available")), + } + } + None => Ok(None), + }, + Err(_e) => Err(juniper::FieldError::from("No current block available")), + } + } +} + +impl BlockFields for Block { + fn field_version(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> { + Ok(&self.version) + } + + fn field_currency(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> { + Ok(&self.currency) + } + + fn field_issuer(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> { + Ok(&self.issuer) + } + + fn field_number(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> { + Ok(&self.number) + } +} + +pub struct Mutation; + +impl MutationFields for Mutation { + fn field_noop(&self, _executor: &Executor<'_, Context>) -> FieldResult<&bool> { + Ok(&true) + } +} + +pub fn create_schema() -> Schema { + Schema::new(Query {}, Mutation {}) +} diff --git a/lib/modules/gva/src/webserver.rs b/lib/modules/gva/src/webserver.rs new file mode 100644 index 0000000000000000000000000000000000000000..3a7a7b89961733566afe6b45c225d0bb709d99a8 --- /dev/null +++ b/lib/modules/gva/src/webserver.rs @@ -0,0 +1,77 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// 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::context; +use crate::schema::*; +use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; +use durs_common_tools::fatal_error; +use durs_conf::DuRsConf; +use durs_module::*; +use futures::future::Future; +use juniper::http::graphiql::graphiql_source; +use juniper::http::GraphQLRequest; +use std::net::SocketAddr; +use std::sync::Arc; + +fn graphiql() -> HttpResponse { + let html = graphiql_source("http://127.0.0.1:3000/graphql"); + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html) +} + +fn graphql( + schema: web::Data<Arc<Schema>>, + data: web::Json<GraphQLRequest>, +) -> impl Future<Item = HttpResponse, Error = Error> { + let context = crate::context::get_context(); + web::block(move || { + let result = data.execute(&schema, context); + Ok::<_, serde_json::error::Error>(serde_json::to_string(&result)?) + }) + .map_err(Error::from) + .and_then(|user| { + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(user)) + }) +} + +pub fn start_web_server(soft_meta_datas: &SoftwareMetaDatas<DuRsConf>) -> std::io::Result<()> { + info!("GVA web server start."); + let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); + + // Create Juniper schema + let schema = std::sync::Arc::new(create_schema()); + + // Instanciate the context + let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone()); + if let Ok(db) = durs_bc_db_reader::open_db_ro(&std::path::Path::new(&db_path)) { + context::init(db); + } else { + fatal_error!("GVA: fail to open DB."); + }; + + // Start http server + HttpServer::new(move || { + App::new() + .data(schema.clone()) + .wrap(middleware::Logger::default()) + .service(web::resource("/graphql").route(web::post().to_async(graphql))) + .service(web::resource("/graphiql").route(web::get().to(graphiql))) + }) + .bind(addr)? + .run() +} diff --git a/lib/tools/dbs-tools/src/kv_db/file.rs b/lib/tools/dbs-tools/src/kv_db/file.rs index 9b8020715e166e076088fee3c01c464f2a61e22a..b76ebc325cfa12334041376b5507f9106fa7235d 100644 --- a/lib/tools/dbs-tools/src/kv_db/file.rs +++ b/lib/tools/dbs-tools/src/kv_db/file.rs @@ -57,6 +57,7 @@ pub struct KvFileDbHandler { } /// Key-value file Database read-only handler +#[derive(Debug)] pub struct KvFileDbRoHandler(KvFileDbHandler); impl KvFileDbRoHandler {