Skip to content
Snippets Groups Projects
Commit 373b04f2 authored by jm's avatar jm
Browse files

[feat] gva: init gva module

parent 30351d42
No related branches found
No related tags found
1 merge request!124WIP: Resolve "Implement Client API GVA : GraphQL Verification Api"
...@@ -14,13 +14,14 @@ edition = "2018" ...@@ -14,13 +14,14 @@ edition = "2018"
[dependencies] [dependencies]
durs-network = { path = "../../lib/core/network" } durs-network = { path = "../../lib/core/network" }
durs-core = { path = "../../lib/core/core" } durs-core = { path = "../../lib/core/core" }
durs-gva = { path = "../../lib/modules/gva" }
durs-module = { path = "../../lib/core/module" } durs-module = { path = "../../lib/core/module" }
#durs-skeleton = { path = "../../lib/modules/skeleton" } #durs-skeleton = { path = "../../lib/modules/skeleton" }
durs-ws2p = { path = "../../lib/modules/ws2p/ws2p" } durs-ws2p = { path = "../../lib/modules/ws2p/ws2p" }
durs-ws2p-v1-legacy = { path = "../../lib/modules/ws2p-v1-legacy" } durs-ws2p-v1-legacy = { path = "../../lib/modules/ws2p-v1-legacy" }
log = "0.4.*" human-panic = "1.0.1"
structopt = "0.2.*" log = "0.4.8"
human-panic = "1.0.*" structopt= "0.2.18"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
durs-tui = { path = "../../lib/modules/tui" } durs-tui = { path = "../../lib/modules/tui" }
......
...@@ -36,6 +36,7 @@ use durs_core::durs_plug; ...@@ -36,6 +36,7 @@ use durs_core::durs_plug;
use log::error; use log::error;
use structopt::StructOpt; use structopt::StructOpt;
pub use durs_gva::GvaModule;
#[cfg(unix)] #[cfg(unix)]
pub use durs_tui::TuiModule; pub use durs_tui::TuiModule;
//pub use durs_skeleton::SkeletonModule; //pub use durs_skeleton::SkeletonModule;
...@@ -63,7 +64,7 @@ macro_rules! durs_cli_main { ...@@ -63,7 +64,7 @@ macro_rules! durs_cli_main {
fn main() { fn main() {
durs_cli_main!(durs_plug!( durs_cli_main!(durs_plug!(
[WS2Pv1Module, WS2PModule], [WS2Pv1Module, WS2PModule],
[TuiModule /*, SkeletonModule ,DasaModule*/] [TuiModule, GvaModule /*, SkeletonModule ,DasaModule*/]
)) ))
} }
#[cfg(unix)] #[cfg(unix)]
......
[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]
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!
}
// 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");
}
}
}
// 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(())
}
}
// 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 {})
}
// 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()
}
...@@ -57,6 +57,7 @@ pub struct KvFileDbHandler { ...@@ -57,6 +57,7 @@ pub struct KvFileDbHandler {
} }
/// Key-value file Database read-only handler /// Key-value file Database read-only handler
#[derive(Debug)]
pub struct KvFileDbRoHandler(KvFileDbHandler); pub struct KvFileDbRoHandler(KvFileDbHandler);
impl KvFileDbRoHandler { impl KvFileDbRoHandler {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment