From 77a7fd19e5cfdaeb52a880dbe39f0f5b105248fd Mon Sep 17 00:00:00 2001 From: CodeSandwich Date: Fri, 18 Oct 2019 11:32:29 +0200 Subject: [PATCH 1/4] Simplify code around REST server being part of REST context --- jormungandr/src/main.rs | 24 ++++++++++++------------ jormungandr/src/rest/mod.rs | 14 +++++++------- jormungandr/src/rest/v0/handlers.rs | 6 ++---- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/jormungandr/src/main.rs b/jormungandr/src/main.rs index c7edb09244..67305c3896 100644 --- a/jormungandr/src/main.rs +++ b/jormungandr/src/main.rs @@ -101,7 +101,7 @@ pub struct BootstrappedNode { new_epoch_notifier: tokio::sync::mpsc::Receiver, logger: Logger, explorer_db: Option, - rest_server: Option<(rest::Server, rest::Context)>, + rest_context: Option, } const FRAGMENT_TASK_QUEUE_LEN: usize = 1024; @@ -263,8 +263,8 @@ fn start_services(bootstrapped_node: BootstrappedNode) -> Result<(), start_up::E }); } - let rest_server = match bootstrapped_node.rest_server { - Some((server, context)) => { + let rest_server = match bootstrapped_node.rest_context { + Some(rest_context) => { let logger = bootstrapped_node .logger .new(o!(::log::KEY_TASK => "rest")) @@ -281,8 +281,8 @@ fn start_services(bootstrapped_node: BootstrappedNode) -> Result<(), start_up::E enclave, explorer: explorer.as_ref().map(|(_msg_box, context)| context.clone()), }; - context.set_full(full_context); - Some(server) + rest_context.set_full(full_context); + Some(rest_context.server()) } None => None, }; @@ -314,7 +314,7 @@ fn bootstrap(initialized_node: InitializedNode) -> Result "bootstrap")); @@ -366,7 +366,7 @@ fn bootstrap(initialized_node: InitializedNode) -> Result, + pub rest_context: Option, } fn initialize_node() -> Result { @@ -402,11 +402,11 @@ fn initialize_node() -> Result { info!(init_logger, "Starting {}", env!("FULL_VERSION"),); let settings = raw_settings.try_into_settings(&init_logger)?; - let rest_server = match &settings.rest { + let rest_context = match &settings.rest { Some(rest) => { let context = rest::Context::new(); - let server = rest::start_rest_server(rest, settings.explorer, context.clone())?; - Some((server, context)) + rest::start_rest_server(rest, settings.explorer, &context)?; + Some(context) } None => None, }; @@ -426,7 +426,7 @@ fn initialize_node() -> Result { block0, storage, logger, - rest_server, + rest_context, }) } diff --git a/jormungandr/src/rest/mod.rs b/jormungandr/src/rest/mod.rs index 32bf03df5e..2e7a0ea566 100644 --- a/jormungandr/src/rest/mod.rs +++ b/jormungandr/src/rest/mod.rs @@ -8,7 +8,7 @@ pub mod v0; pub use self::server::{Error, Server}; use actix_web::dev::Resource; -use actix_web::error::{Error as ActixError, ErrorInternalServerError, ErrorServiceUnavailable}; +use actix_web::error::{Error as ActixError, ErrorServiceUnavailable}; use actix_web::middleware::cors::Cors; use actix_web::App; use futures::{Future, IntoFuture}; @@ -59,12 +59,12 @@ impl Context { *self.server.write().expect("Context server poisoned") = Some(Arc::new(server)); } - pub fn server(&self) -> Result, ActixError> { + pub fn server(&self) -> Arc { self.server .read() .expect("Context server poisoned") .clone() - .ok_or_else(|| ErrorInternalServerError("Server not set in context")) + .expect("Context server not set") } } @@ -85,8 +85,8 @@ pub struct FullContext { pub fn start_rest_server( config: &Rest, explorer_enabled: bool, - context: Context, -) -> Result { + context: &Context, +) -> Result<(), ConfigError> { let app_context = context.clone(); let cors_cfg = config.cors.clone(); let server = Server::start(config.pkcs12.clone(), config.listen.clone(), move || { @@ -108,8 +108,8 @@ pub fn start_rest_server( apps })?; - context.set_server(server.clone()); - Ok(server) + context.set_server(server); + Ok(()) } fn build_app(state: S, prefix: P, resources: R, cors_cfg: &Option) -> App diff --git a/jormungandr/src/rest/v0/handlers.rs b/jormungandr/src/rest/v0/handlers.rs index 68038bd78f..d375551c23 100644 --- a/jormungandr/src/rest/v0/handlers.rs +++ b/jormungandr/src/rest/v0/handlers.rs @@ -284,10 +284,8 @@ pub fn get_shutdown(context: State) -> Result { // Server finishes ongoing tasks before stopping, so user will get response to this request // Node should be shutdown automatically when server stopping is finished context.try_full()?; - context.server().map(|server| { - server.stop(); - HttpResponse::Ok().finish() - }) + context.server().stop(); + Ok(HttpResponse::Ok().finish()) } pub fn get_leaders(context: State) -> Result { From cf10f0fa9ccb6a6353b273c1a2c619fec45ae309 Mon Sep 17 00:00:00 2001 From: CodeSandwich Date: Fri, 18 Oct 2019 13:29:38 +0200 Subject: [PATCH 2/4] Set node state in REST context --- jormungandr/src/main.rs | 17 +++++++++++++++++ jormungandr/src/rest/mod.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/jormungandr/src/main.rs b/jormungandr/src/main.rs index 67305c3896..3e63b4fce2 100644 --- a/jormungandr/src/main.rs +++ b/jormungandr/src/main.rs @@ -55,6 +55,7 @@ extern crate tokio; use crate::{ blockcfg::{HeaderHash, Leader}, blockchain::Blockchain, + rest::NodeState, secure::enclave::Enclave, settings::start::Settings, utils::{async_msg, task::Services}, @@ -108,6 +109,10 @@ const FRAGMENT_TASK_QUEUE_LEN: usize = 1024; const NETWORK_TASK_QUEUE_LEN: usize = 32; fn start_services(bootstrapped_node: BootstrappedNode) -> Result<(), start_up::Error> { + if let Some(context) = bootstrapped_node.rest_context.as_ref() { + context.set_node_state(NodeState::StartingWorkers) + } + let mut services = Services::new(bootstrapped_node.logger.clone()); // initialize the network propagation channel @@ -282,6 +287,7 @@ fn start_services(bootstrapped_node: BootstrappedNode) -> Result<(), start_up::E explorer: explorer.as_ref().map(|(_msg_box, context)| context.clone()), }; rest_context.set_full(full_context); + rest_context.set_node_state(NodeState::Running); Some(rest_context.server()) } None => None, @@ -316,6 +322,11 @@ fn bootstrap(initialized_node: InitializedNode) -> Result "bootstrap")); let (new_epoch_announcements, new_epoch_notifier) = tokio::sync::mpsc::channel(100); @@ -411,10 +422,16 @@ fn initialize_node() -> Result { None => None, }; + if let Some(context) = rest_context.as_ref() { + context.set_node_state(NodeState::PreparingStorage) + } let storage = start_up::prepare_storage(&settings, &init_logger)?; // TODO: load network module here too (if needed) + if let Some(context) = rest_context.as_ref() { + context.set_node_state(NodeState::PreparingBlock0) + } let block0 = start_up::prepare_block_0( &settings, &storage, diff --git a/jormungandr/src/rest/mod.rs b/jormungandr/src/rest/mod.rs index 2e7a0ea566..a78de3239c 100644 --- a/jormungandr/src/rest/mod.rs +++ b/jormungandr/src/rest/mod.rs @@ -29,6 +29,7 @@ use crate::utils::async_msg::MessageBox; pub struct Context { full: Arc>>>, server: Arc>>>, + node_state: Arc>, } impl Context { @@ -36,6 +37,7 @@ impl Context { Context { full: Default::default(), server: Default::default(), + node_state: Arc::new(RwLock::new(NodeState::StartingRestServer)), } } @@ -66,6 +68,20 @@ impl Context { .clone() .expect("Context server not set") } + + pub fn set_node_state(&self, node_state: NodeState) { + *self + .node_state + .write() + .expect("Context node state poisoned") = node_state; + } + + pub fn node_state(&self) -> NodeState { + self.node_state + .read() + .expect("Context node state poisoned") + .clone() + } } #[derive(Clone)] @@ -82,6 +98,16 @@ pub struct FullContext { pub explorer: Option, } +#[derive(Clone, Debug)] +pub enum NodeState { + StartingRestServer, + PreparingStorage, + PreparingBlock0, + Bootstrapping, + StartingWorkers, + Running, +} + pub fn start_rest_server( config: &Rest, explorer_enabled: bool, From c60ad3c839a882823f7a22117ae35609beddae7d Mon Sep 17 00:00:00 2001 From: CodeSandwich Date: Fri, 18 Oct 2019 19:27:32 +0200 Subject: [PATCH 3/4] Add node state to REST node stats --- jormungandr/src/rest/mod.rs | 2 +- jormungandr/src/rest/v0/handlers.rs | 138 +++++++++++++++++----------- 2 files changed, 85 insertions(+), 55 deletions(-) diff --git a/jormungandr/src/rest/mod.rs b/jormungandr/src/rest/mod.rs index a78de3239c..8d42f0a4d7 100644 --- a/jormungandr/src/rest/mod.rs +++ b/jormungandr/src/rest/mod.rs @@ -98,7 +98,7 @@ pub struct FullContext { pub explorer: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub enum NodeState { StartingRestServer, PreparingStorage, diff --git a/jormungandr/src/rest/v0/handlers.rs b/jormungandr/src/rest/v0/handlers.rs index d375551c23..757b545328 100644 --- a/jormungandr/src/rest/v0/handlers.rs +++ b/jormungandr/src/rest/v0/handlers.rs @@ -16,11 +16,17 @@ use crate::blockchain::Ref; use crate::intercom::{self, NetworkMsg, TransactionMsg}; use crate::secure::NodeSecret; use bytes::{Bytes, IntoBuf}; -use futures::{Future, IntoFuture, Stream}; +use futures::{ + future::{ + self, + Either::{A, B}, + }, + Future, IntoFuture, Stream, +}; use std::str::FromStr; use std::sync::Arc; -pub use crate::rest::{Context, FullContext}; +pub use crate::rest::{Context, FullContext, NodeState}; macro_rules! ActixFuture { () => { impl Future + 'static> + 'static } @@ -96,61 +102,85 @@ pub fn get_tip(context: State) -> ActixFuture!() { chain_tip_fut(&context).map(|tip| tip.hash().to_string()) } +#[derive(Serialize)] +struct NodeStatsDto { + state: NodeState, + #[serde(flatten)] + stats: Option, +} + pub fn get_stats_counter(context: State) -> ActixFuture!() { - context - .try_full_fut() - .and_then(|context| chain_tip_fut_raw(&*context).map(|tip| (context, tip))) - .and_then(move |(context, tip)| { - let header = tip.header().clone(); - context - .blockchain - .storage() - .get(header.hash()) - .then(|res| match res { - Ok(Some(block)) => Ok(block.contents), - Ok(None) => Err(ErrorInternalServerError("Could not find block for tip")), - Err(e) => Err(ErrorInternalServerError(e)), + match context.try_full() { + Ok(context) => { + let stats_json_fut = chain_tip_fut_raw(&*context) + .map(|tip| (context, tip)) + .and_then(move |(context, tip)| { + let header = tip.header().clone(); + context + .blockchain + .storage() + .get(header.hash()) + .then(|res| match res { + Ok(Some(block)) => Ok(block.contents), + Ok(None) => { + Err(ErrorInternalServerError("Could not find block for tip")) + } + Err(e) => Err(ErrorInternalServerError(e)), + }) + .map(move |contents| (context, contents, header)) }) - .map(move |contents| (context, contents, header)) - }) - .and_then(move |(context, contents, tip_header)| { - let mut block_tx_count = 0; - let mut block_input_sum = Value::zero(); - let mut block_fee_sum = Value::zero(); - contents - .iter() - .filter_map(|fragment| match fragment { - Fragment::Transaction(tx) => Some(&tx.transaction), - _ => None, - }) - .map(|tx| { - let input_sum = Value::sum(tx.inputs.iter().map(|input| input.value))?; - let output_sum = Value::sum(tx.outputs.iter().map(|input| input.value))?; - // Input < output implies minting, so no fee - let fee = (input_sum - output_sum).unwrap_or(Value::zero()); - block_tx_count += 1; - block_input_sum = (block_input_sum + input_sum)?; - block_fee_sum = (block_fee_sum + fee)?; - Ok(()) - }) - .collect::>() - .map_err(|e| { - ErrorInternalServerError(format!("Block value calculation error: {}", e)) - })?; - let stats = &context.stats_counter; - Ok(Json(json!({ - "txRecvCnt": stats.tx_recv_cnt(), - "blockRecvCnt": stats.block_recv_cnt(), - "uptime": stats.uptime_sec(), - "lastBlockHash": tip_header.hash().to_string(), - "lastBlockHeight": tip_header.chain_length().to_string(), - "lastBlockDate": tip_header.block_date().to_string(), - "lastBlockTime": stats.slot_start_time().map(SystemTime::from), - "lastBlockTx": block_tx_count, - "lastBlockSum": block_input_sum.0, - "lastBlockFees": block_fee_sum.0, - }))) + .and_then(move |(context, contents, tip_header)| { + let mut block_tx_count = 0; + let mut block_input_sum = Value::zero(); + let mut block_fee_sum = Value::zero(); + contents + .iter() + .filter_map(|fragment| match fragment { + Fragment::Transaction(tx) => Some(&tx.transaction), + _ => None, + }) + .map(|tx| { + let input_sum = Value::sum(tx.inputs.iter().map(|input| input.value))?; + let output_sum = + Value::sum(tx.outputs.iter().map(|input| input.value))?; + // Input < output implies minting, so no fee + let fee = (input_sum - output_sum).unwrap_or(Value::zero()); + block_tx_count += 1; + block_input_sum = (block_input_sum + input_sum)?; + block_fee_sum = (block_fee_sum + fee)?; + Ok(()) + }) + .collect::>() + .map_err(|e| { + ErrorInternalServerError(format!( + "Block value calculation error: {}", + e + )) + })?; + let stats = &context.stats_counter; + Ok(Some(json!({ + "txRecvCnt": stats.tx_recv_cnt(), + "blockRecvCnt": stats.block_recv_cnt(), + "uptime": stats.uptime_sec(), + "lastBlockHash": tip_header.hash().to_string(), + "lastBlockHeight": tip_header.chain_length().to_string(), + "lastBlockDate": tip_header.block_date().to_string(), + "lastBlockTime": stats.slot_start_time().map(SystemTime::from), + "lastBlockTx": block_tx_count, + "lastBlockSum": block_input_sum.0, + "lastBlockFees": block_fee_sum.0, + }))) + }); + A(stats_json_fut) + } + Err(_) => B(future::ok(None)), + } + .map(move |stats| { + Json(NodeStatsDto { + state: context.node_state(), + stats, }) + }) } pub fn get_block_id(context: State, block_id_hex: Path) -> ActixFuture!() { From 17fe56badecf63cd68ac9c00d0ed77ac06a13b47 Mon Sep 17 00:00:00 2001 From: CodeSandwich Date: Fri, 18 Oct 2019 19:41:29 +0200 Subject: [PATCH 4/4] Add state and clean up to node stats REST docs --- doc/jcli/rest.md | 25 ++++++++++++++++++++++--- doc/openapi.yaml | 32 ++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/doc/jcli/rest.md b/doc/jcli/rest.md index a8f6e04cfa..06cff175ec 100644 --- a/doc/jcli/rest.md +++ b/doc/jcli/rest.md @@ -33,9 +33,28 @@ YAML printed on success ```yaml --- -blockRecvCnt: 7 # Blocks received by node -txRecvCnt: 90 # Transactions received by node -uptime: 2101 # Node uptitme in seconds +# Number of blocks received by node +blockRecvCnt: 1102 +# The Epoch and slot Number of the block (optional) +lastBlockDate: "20.29" +# Sum of all fee values in all transactions in last block +lastBlockFees: 534 +# The block hash, it's unique identifier in the blockchain (optional) +lastBlockHash: b9597b45a402451540e6aabb58f2ee4d65c67953b338e04c52c00aa0886bd1f0 +# The block number, in order, since the block0 (optional) +lastBlockHeight: 202901 +# Sum of all input values in all transactions in last block +lastBlockSum: 51604 +# When last block was created, not set if none was created yet (optional) +lastBlockTime: 2019-08-12T11:20:52.316544007+00:00 +# Number of transactions in last block +lastBlockTx: 2 +# State of the node +state: Running +# Number of transactions received by node +txRecvCnt: 5440 +# Node uptime in seconds +uptime: 20032 ``` ## Whole UTXO diff --git a/doc/openapi.yaml b/doc/openapi.yaml index 2e0ead168a..f731c1f874 100644 --- a/doc/openapi.yaml +++ b/doc/openapi.yaml @@ -422,16 +422,25 @@ paths: application/json: schema: type: object - required: [blockRecvCnt, lastBlockFees, lastBlockSum, lastBlockTx, txRecvCnt, uptime] + required: [blockRecvCnt, lastBlockFees, lastBlockSum, lastBlockTx, state, txRecvCnt, uptime] properties: blockRecvCnt: description: Number of blocks received by node type: integer minimum: 0 + lastBlockDate: + description: The Epoch and slot Number of the block + type: string lastBlockFees: description: Sum of all fee values in all transactions in last block type: integer minimum: 0 + lastBlockHash: + description: The block hash, it's unique identifier in the blockchain + type: string + lastBlockHeight: + description: The block number, in order, since the block0 + type: number lastBlockSum: description: Sum of all input values in all transactions in last block type: integer @@ -444,6 +453,10 @@ paths: description: Number of transactions in last block type: integer minimum: 0 + state: + description: State of the node + type: string + enum: [StartingRestServer, PreparingStorage, PreparingBlock0, Bootstrapping, StartingWorkers, Running] txRecvCnt: description: Number of transactions received by node type: integer @@ -452,26 +465,17 @@ paths: description: Node uptime in seconds type: integer minimum: 0 - lastBlockDate: - description: The Epoch and slot Number of the block. - type: string - lastBlockHeight: - description: The block number, in order, since the block0 - type: number - minimum: 0 - lastBlockHash: - description: The block hash, it's unique identifier in the blockchain. - type: string example: | { "blockRecvCnt": 1102, + "lastBlockDate": "20.29", "lastBlockFees": 534, + "lastBlockHash": "b9597b45a402451540e6aabb58f2ee4d65c67953b338e04c52c00aa0886bd1f0", + "lastBlockHeight": 202901, "lastBlockSum": 51604, "lastBlockTime": "2019-08-12T11:20:52.316544007+00:00", - "lastBlockDate": "20.29", - "lastBlockHeight": 202901, - "lastBlockHash": "b9597b45a402451540e6aabb58f2ee4d65c67953b338e04c52c00aa0886bd1f0", "lastBlockTx": 2, + "state": "Running", "txRecvCnt": 5440, "uptime": 20032 }