diff --git a/README.md b/README.md index c2cc644..8c53a46 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,10 @@ Grin is the very first, simple and fair MimbleWimble blockchain implementation. You will see the following output: ``` - [2024-06-19T13:12:34Z INFO grin_explorer] starting up. - [2024-06-19T13:12:34Z WARN rocket::launch] 🚀 Rocket has launched from http://127.0.0.1:8000 - [2024-06-19T13:12:34Z INFO grin_explorer] ready. + [2024-09-30T13:30:02Z INFO grin_explorer] starting up. + [2024-09-30T13:30:02Z WARN rocket::launch] 🚀 Rocket has launched from http://127.0.0.1:8000 + [2024-09-30T13:30:03Z INFO grin_explorer] worker::data ready. + [2024-09-30T13:30:10Z INFO grin_explorer] worker::stats ready. ``` 5. Open explorer in your browser: http://127.0.0.1:8000 diff --git a/src/data.rs b/src/data.rs index 71dc5b4..46d5b45 100644 --- a/src/data.rs +++ b/src/data.rs @@ -47,6 +47,8 @@ pub struct Dashboard { // mempool pub txns: String, pub stem: String, + // utxo + pub utxo_count: String, } impl Dashboard { @@ -76,6 +78,7 @@ impl Dashboard { breakeven_cost: String::new(), txns: String::new(), stem: String::new(), + utxo_count: String::new(), } } } @@ -241,6 +244,8 @@ pub struct Statistics { // Transactions & fees pub txns: Vec, pub fees: Vec, + //UTXOs + pub utxo_count: Vec, } impl Statistics { @@ -253,6 +258,7 @@ impl Statistics { hashrate: Vec::new(), txns: Vec::new(), fees: Vec::new(), + utxo_count: Vec::new(), } } } diff --git a/src/main.rs b/src/main.rs index 898749a..02a2fe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #[macro_use] extern crate rocket; +use chrono::Utc; use either::Either; use rocket_dyn_templates::{Template, context}; use rocket::fs::FileServer; @@ -9,18 +10,13 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use serde_json::Value; +use crate::data::{Block, Dashboard, Kernel, Output, Statistics, Transactions, OUTPUT_SIZE}; +use crate::requests::CONFIG; + mod data; mod requests; mod worker; -use crate::data::Block; -use crate::data::Dashboard; -use crate::data::Kernel; -use crate::data::Output; -use crate::data::Statistics; -use crate::data::Transactions; -use crate::requests::CONFIG; - // Rendering main (Dashboard) page. #[get("/")] @@ -219,14 +215,16 @@ fn stats(statistics: &State>>) -> Template { let data = statistics.lock().unwrap(); Template::render("stats", context! { - route: "stats", - date: data.date.clone(), - user_agent: data.user_agent.clone(), - count: data.count.clone(), - total: data.total, - hashrate: data.hashrate.clone(), - txns: data.txns.clone(), - fees: data.fees.clone(), + route: "stats", + date: data.date.clone(), + user_agent: data.user_agent.clone(), + count: data.count.clone(), + total: data.total, + hashrate: data.hashrate.clone(), + txns: data.txns.clone(), + fees: data.fees.clone(), + utxo_count: data.utxo_count.clone(), + output_size: OUTPUT_SIZE, }) } @@ -669,27 +667,51 @@ async fn main() { let txns_clone = txns.clone(); let stats = Arc::new(Mutex::new(Statistics::new())); let stats_clone = stats.clone(); - let mut ready = false; - // Starting the Worker + let mut ready_data = false; + let mut ready_stats = false; + let mut date = "".to_string(); + + // Collecting main data tokio::spawn(async move { loop { - let result = worker::run(dash_clone.clone(), blocks_clone.clone(), - txns_clone.clone(), stats_clone.clone()).await; + let result = worker::data(dash_clone.clone(), blocks_clone.clone(), + txns_clone.clone(), stats_clone.clone()).await; match result { Ok(_v) => { - if ready == false { - ready = true; - info!("ready."); + if ready_data == false { + ready_data = true; + info!("worker::data ready."); } }, Err(e) => { - ready = false; + ready_data = false; error!("{}", e); }, } + let date_now = format!("\"{}\"", Utc::now().format("%d-%m-%Y")); + + if date.is_empty() || date != date_now { + date = date_now; + let result = worker::stats(dash_clone.clone(), txns_clone.clone(), + stats_clone.clone()).await; + + match result { + Ok(_v) => { + if ready_stats == false { + ready_stats = true; + info!("worker::stats ready."); + } + }, + Err(e) => { + ready_stats = false; + error!("{}", e); + }, + } + } + tokio::time::sleep(Duration::from_secs(15)).await; } }); diff --git a/src/requests.rs b/src/requests.rs index ba831f7..94a9349 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -748,3 +748,42 @@ pub async fn get_block_list_by_height(height: &str, blocks: &mut Vec, Ok(()) } +// Collecting unspent outputs. +pub async fn get_unspent_outputs(dashboard: Arc>) -> Result<(), anyhow::Error> { + let mut highest_mmr_index = 0; + let mut current_mmr_index = 0; + let mut utxo_count = 0; + + // Get the highest MMR index + let resp = call("get_unspent_outputs", "[1, null, 10000, false]", "1", "foreign").await?; + + if resp != Value::Null { + highest_mmr_index = resp["result"]["Ok"]["highest_index"].to_string().parse::().unwrap(); + current_mmr_index = resp["result"]["Ok"]["outputs"].as_array().unwrap().last().unwrap()["mmr_index"].to_string().parse::().unwrap(); + } + + // Get all unspent outputs + while current_mmr_index < highest_mmr_index { + current_mmr_index = current_mmr_index + 1; + let params = &format!("[{}, {}, 10000, false]", current_mmr_index, highest_mmr_index)[..]; + + let resp = call("get_unspent_outputs", params, "1", "foreign").await?; + + if resp != Value::Null { + if let Some(v) = resp["result"]["Ok"]["outputs"].as_array().unwrap().last() { + current_mmr_index = v["mmr_index"].to_string().parse::().unwrap(); + utxo_count = utxo_count + resp["result"]["Ok"]["outputs"].as_array().unwrap().len(); + } else { + // Break the loop if we got no outputs from the node request + break; + } + } + } + + let mut data = dashboard.lock().unwrap(); + + data.utxo_count = utxo_count.to_string(); + + Ok(()) +} + diff --git a/src/worker.rs b/src/worker.rs index fe74116..0e9de56 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -9,10 +9,9 @@ use crate::data::Transactions; use crate::requests; -// Tokio Runtime Worker. -// Collecting all the data. -pub async fn run(dash: Arc>, blocks: Arc>>, - txns: Arc>, stats: Arc>) -> Result<(), anyhow::Error> { +// Collecting main data. +pub async fn data(dash: Arc>, blocks: Arc>>, + txns: Arc>, stats: Arc>) -> Result<(), anyhow::Error> { let _ = requests::get_status(dash.clone()).await?; let _ = requests::get_mempool(dash.clone()).await?; let _ = requests::get_connected_peers(dash.clone(), stats.clone()).await?; @@ -22,33 +21,31 @@ pub async fn run(dash: Arc>, blocks: Arc>>, let _ = requests::get_recent_blocks(dash.clone(), blocks.clone()).await?; let _ = requests::get_txn_stats(dash.clone(), txns.clone()).await?; + Ok(()) +} + +// Collecting statistics. +pub async fn stats(dash: Arc>, txns: Arc>, stats: Arc>) -> Result<(), anyhow::Error> { + let _ = requests::get_unspent_outputs(dash.clone()).await?; + let mut stats = stats.lock().unwrap(); let dash = dash.lock().unwrap(); let txns = txns.lock().unwrap(); - // Every day push new stats into the vectors - if let Some(v) = stats.date.last() { - // If new day, push the stats - if *v != format!("\"{}\"", Utc::now().format("%d-%m-%Y")) { - if stats.date.len() == 30 { - stats.date.remove(0); - stats.hashrate.remove(0); - stats.txns.remove(0); - stats.fees.remove(0); - } - stats.date.push(format!("\"{}\"", Utc::now().format("%d-%m-%Y"))); - stats.hashrate.push(dash.hashrate_kgs.clone()); - stats.txns.push(txns.period_24h.clone()); - stats.fees.push(txns.fees_24h.clone()); - } - } else { - // Vector is empty, pushing current stats - stats.date.push(format!("\"{}\"", Utc::now().format("%d-%m-%Y"))); - stats.hashrate.push(dash.hashrate_kgs.clone()); - stats.txns.push(txns.period_24h.clone()); - stats.fees.push(txns.fees_24h.clone()); + if stats.date.len() == 30 { + stats.date.remove(0); + stats.hashrate.remove(0); + stats.txns.remove(0); + stats.fees.remove(0); + stats.utxo_count.remove(0); } + stats.date.push(format!("\"{}\"", Utc::now().format("%d-%m-%Y"))); + stats.hashrate.push(dash.hashrate_kgs.clone()); + stats.txns.push(txns.period_24h.clone()); + stats.fees.push(txns.fees_24h.clone()); + stats.utxo_count.push(dash.utxo_count.clone()); + Ok(()) } diff --git a/templates/stats.html.tera b/templates/stats.html.tera index d1071b4..fb7e89c 100644 --- a/templates/stats.html.tera +++ b/templates/stats.html.tera @@ -18,7 +18,7 @@
-
HASHRATE
+
TRANSACTIONS & FEES
@@ -27,20 +27,25 @@
-
TRANSACTIONS & FEES
+
HASHRATE
+ +
+
+
+
UNSPENT OUTPUTS
+
+
+
+