diff --git a/Cargo.lock b/Cargo.lock index b3c3156..911b7e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "arrayvec" version = "0.7.4" @@ -730,8 +736,9 @@ dependencies = [ [[package]] name = "grin-explorer" -version = "0.1.2" +version = "0.1.3" dependencies = [ + "anyhow", "chrono", "colored", "config", diff --git a/Cargo.toml b/Cargo.toml index 3a21405..164b703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin-explorer" -version = "0.1.2" +version = "0.1.3" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,6 +19,7 @@ config = "0.14.0" lazy_static = "1.4.0" shellexpand = "3.1.0" either = "1.11.0" +anyhow = "1.0.86" [dependencies.reqwest] version = "0.11.23" diff --git a/Explorer.toml b/Explorer.toml index 95a7c76..cd1dd2d 100644 --- a/Explorer.toml +++ b/Explorer.toml @@ -16,6 +16,20 @@ api_secret_path = "~/.grin/main/.api_secret" # Foreign API secret path. foreign_api_secret_path = "~/.grin/main/.foreign_api_secret" -# Path to Grin directory +# Path to Grin directory. grin_dir = "~/.grin" +# CoinGecko API on/off switch. +coingecko_api = "on" + + +# Testnet config +# ip = "127.0.0.1" +# port = "13413" +# proto = "http" +# user = "grin" +# api_secret_path = "~/.grin/test/.api_secret" +# foreign_api_secret_path = "~/.grin/test/.foreign_api_secret" +# grin_dir = "~/.grin" +# coingecko_api = "off" + diff --git a/Rocket.toml b/Rocket.toml index 97e9661..6077f33 100644 --- a/Rocket.toml +++ b/Rocket.toml @@ -2,3 +2,7 @@ address = "127.0.0.1" log_level = "critical" +# Uncomment and change default port number (8000) if another instance of the explorer is needed to run. +# E.g. Mainnet (8000) and Testnet (8001) instances. +# port = 8000 + diff --git a/src/data.rs b/src/data.rs index a8a462c..d529320 100644 --- a/src/data.rs +++ b/src/data.rs @@ -35,6 +35,8 @@ pub struct Dashboard { // mempool pub txns: String, pub stem: String, + // coingecko api + pub cg_api: String, } impl Dashboard { @@ -64,6 +66,7 @@ impl Dashboard { breakeven_cost: String::new(), txns: String::new(), stem: String::new(), + cg_api: String::new(), } } } @@ -108,6 +111,29 @@ impl Block { } +// Kernel data +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Kernel { + pub height: String, + pub excess: String, + pub ker_type: String, + pub fee: String, + pub raw_data: String, +} + +impl Kernel { + pub fn new() -> Kernel { + Kernel { + height: String::new(), + excess: String::new(), + ker_type: String::new(), + fee: String::new(), + raw_data: String::new(), + } + } +} + + // Transactions data #[derive(Debug)] pub struct Transactions { @@ -141,6 +167,7 @@ pub struct ExplorerConfig { pub grin_dir: String, pub api_secret: String, pub foreign_api_secret: String, + pub coingecko_api: String, } impl ExplorerConfig { @@ -155,6 +182,7 @@ impl ExplorerConfig { grin_dir: String::new(), api_secret: String::new(), foreign_api_secret: String::new(), + coingecko_api: String::new(), } } } diff --git a/src/main.rs b/src/main.rs index feb0e9a..3584b85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use colored::Colorize; use rocket::tokio; use rocket::response::Redirect; use either::Either; +use serde_json::Value; mod worker; mod requests; @@ -17,6 +18,7 @@ mod data; use crate::data::Dashboard; use crate::data::Block; use crate::data::Transactions; +use crate::data::Kernel; // Rendering main (Dashboard) page. @@ -25,9 +27,10 @@ fn index(dashboard: &State>>) -> Template { let data = dashboard.lock().unwrap(); Template::render("index", context! { - route: "index", - node_ver: &data.node_ver, + route: "index", + node_ver: &data.node_ver, proto_ver: &data.proto_ver, + cg_api: &data.cg_api, }) } @@ -117,32 +120,34 @@ async fn block_header_by_hash(hash: &str) -> Either { // Rendering page for a specified kernel. -#[get("/kernel/")] -async fn kernel(kernel: &str) -> Either { - let mut height = String::new(); +#[get("/kernel/")] +async fn kernel(excess: &str) -> Template { + let mut kernel = Kernel::new(); - let _ = requests::get_kernel(&kernel, &mut height).await; + let _ = requests::get_kernel(&excess, &mut kernel).await; - if kernel.is_empty() == false { - if height.is_empty() == false { - return Either::Right(Redirect::to(uri!(block_details_by_height(height.as_str())))); - } + if kernel.height.is_empty() == false { + return Template::render("kernel", context! { + route: "kernel", + kernel, + }) } - return Either::Left(Template::render("error", context! { + return Template::render("error", context! { route: "error", - })) + }) } // Handling search request. #[post("/search", data="")] fn search(input: &str) -> Either { - // Trim 'search=' from the request data - let input = &input[7..].to_lowercase(); - - //Check if input is valid - if input.is_empty() == false { + //Check input length + if input.len() > "search=".len() { + // Trim 'search=' from the request data + let input = &input[7..].to_lowercase(); + + // Check for valid chars if input.chars().all(|x| (x >= 'a' && x <= 'f') || (x >= '0' && x <= '9')) == true { // Block number @@ -153,7 +158,7 @@ fn search(input: &str) -> Either { } else if input.len() == 64 { return Either::Right(Redirect::to(uri!(block_header_by_hash(input)))); - // Kernel hash + // Kernel } else if input.len() == 66 { return Either::Right(Redirect::to(uri!(kernel(input)))); } @@ -166,6 +171,66 @@ fn search(input: &str) -> Either { } +// Owner API. +#[post("/v2/owner", data="")] +async fn api_owner(data: &str) -> String { + let result = serde_json::from_str(data); + + let v: Value = match result { + Ok(value) => value, + Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(), + }; + + let method = match v["method"].as_str() { + Some(value) => value, + _ => return "{\"error\":\"bad syntax\"}".to_string(), + }; + + // Whitelisted methods: get_connected_peer, get_peers, get_status. + if method == "get_connected_peers" || method == "get_peers" || method == "get_status" { + let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "owner").await; + + let result = match resp { + Ok(value) => value, + Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), + }; + + return result.to_string(); + } + + "{\"error\":\"not allowed\"}".to_string() +} + + +// Foreign API. +// All methods are whitelisted. +#[post("/v2/foreign", data="")] +async fn api_foreign(data: &str) -> String { + let result = serde_json::from_str(data); + + let v: Value = match result { + Ok(value) => value, + Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(), + }; + + let method = match v["method"].as_str() { + Some(value) => value, + _ => return "{\"error\":\"bad syntax\"}".to_string(), + }; + + println!("{}", method); + println!("{}", data); + let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "foreign").await; + + let result = match resp { + Ok(value) => value, + Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), + }; + + result.to_string() +} + + // Start of HTMX routes. #[get("/rpc/peers/inbound")] fn peers_inbound(dashboard: &State>>) -> String { @@ -210,7 +275,13 @@ fn soft_supply(dashboard: &State>>) -> String { let data = dashboard.lock().unwrap(); if data.supply.is_empty() == false { - return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..3]); + // 9 digits plus 2 commas, e.g. 168,038,400 + if data.supply.len() == 11 { + return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..3]); + // 10 digits plus 2 commas + } else if data.supply.len() == 12 { + return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..4]); + } } "3150M".to_string() @@ -306,7 +377,7 @@ fn disk_usage(dashboard: &State>>) -> String { fn network_hashrate(dashboard: &State>>) -> String { let data = dashboard.lock().unwrap(); - format!("{} KG/s", data.hashrate) + data.hashrate.clone() } @@ -490,7 +561,7 @@ fn block_weight(count: usize, blocks: &State>>>) -> String fn block_list_index(dashboard: &State>>) -> String { let data = dashboard.lock().unwrap(); - if data.height.is_empty() == false { + if data.height.is_empty() == false && data.height.parse::().unwrap() > 10 { return format!("

", data.height.parse::().unwrap() - 10); @@ -552,7 +623,8 @@ async fn main() { block_time, block_txns, block_inputs, block_outputs, block_fees, block_weight, block_details_by_height, block_header_by_hash, soft_supply, production_cost, reward_ratio, breakeven_cost, - last_block_age, block_list_by_height, block_list_index, search, kernel]) + last_block_age, block_list_by_height, block_list_index, search, kernel, + api_owner, api_foreign]) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) .launch() diff --git a/src/requests.rs b/src/requests.rs index b758888..0fc6020 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -16,6 +16,7 @@ use crate::data::Dashboard; use crate::data::Block; use crate::data::Transactions; use crate::data::ExplorerConfig; +use crate::Kernel; // Static explorer config structure @@ -36,6 +37,7 @@ lazy_static! { "api_secret_path" => cfg.api_secret_path = value, "foreign_api_secret_path" => cfg.foreign_api_secret_path = value, "grin_dir" => cfg.grin_dir = value, + "coingecko_api" => cfg.coingecko_api = value, _ => println!("{} Unknown config setting '{}'.", "[ ERROR ]".red(), name), } } @@ -52,7 +54,7 @@ lazy_static! { // RPC requests to grin node. -async fn call(method: &str, params: &str, rpc_type: &str) -> Result { +pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Result { let rpc_url; let secret; @@ -67,21 +69,21 @@ async fn call(method: &str, params: &str, rpc_type: &str) -> Result>) -> Result<(), Error> { - let resp = call("get_status", "[]", "owner").await?; +pub async fn get_status(dashboard: Arc>) -> Result<(), anyhow::Error> { + let resp = call("get_status", "[]", "1", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -92,14 +94,17 @@ pub async fn get_status(dashboard: Arc>) -> Result<(), Error> { data.proto_ver = resp["result"]["Ok"]["protocol_version"].to_string(); } + // Also set cg_api value + data.cg_api = CONFIG.coingecko_api.clone(); + Ok(()) } // Collecting: txns, stem. -pub async fn get_mempool(dashboard: Arc>) -> Result<(), Error> { - let resp1 = call("get_pool_size", "[]", "foreign").await?; - let resp2 = call("get_stempool_size", "[]", "foreign").await?; +pub async fn get_mempool(dashboard: Arc>) -> Result<(), anyhow::Error> { + let resp1 = call("get_pool_size", "[]", "1", "foreign").await?; + let resp2 = call("get_stempool_size", "[]", "1", "foreign").await?; let mut data = dashboard.lock().unwrap(); @@ -113,8 +118,9 @@ pub async fn get_mempool(dashboard: Arc>) -> Result<(), Error> // Collecting: inbound, outbound. -pub async fn get_connected_peers(dashboard: Arc>) -> Result<(), Error> { - let resp = call("get_connected_peers", "[]", "owner").await?; +pub async fn get_connected_peers(dashboard: Arc>) + -> Result<(), anyhow::Error> { + let resp = call("get_connected_peers", "[]", "1", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -140,15 +146,18 @@ pub async fn get_connected_peers(dashboard: Arc>) -> Result<(), // Collecting: supply, inflation, price_usd, price_btc, volume_usd, volume_btc, cap_usd, cap_btc. pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { - let client = reqwest::Client::new(); - let result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true") - .send() - .await?; + let client; + let result; + let mut val = Value::Null; + + if CONFIG.coingecko_api == "on" { + client = reqwest::Client::new(); + result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true").send().await?; + val = serde_json::from_str(&result.text().await.unwrap()).unwrap(); + } - let val: Value = serde_json::from_str(&result.text().await.unwrap()).unwrap(); - let mut data = dashboard.lock().unwrap(); - + if data.height.is_empty() == false { // Calculating coin supply // Adding +1 as block index starts with 0 @@ -157,28 +166,32 @@ pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { // 31536000 seconds in a year let inflation = (31536000.0 / (supply as f64)) * 100.0; - data.inflation = format!("{:.2}", inflation); - data.supply = supply.to_formatted_string(&Locale::en); + data.inflation = format!("{:.2}", inflation); + data.supply = supply.to_formatted_string(&Locale::en); // https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153 data.soft_supply = format!("{:.2}", - supply.to_string().parse::().unwrap() / 3150000000.0 * 100.0); + supply.to_string().parse::().unwrap() / 3150000000.0 * 100.0); - // Check if CoingGecko API returned error - if let Some(status) = val.get("status") { - println!("{} {}.", "[ WARNING ]".yellow(), - status["error_message"].as_str().unwrap().to_string()); - } else { - data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::().unwrap()); - data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::().unwrap()); - data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::().unwrap() as u64) - .to_formatted_string(&Locale::en); - data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::() - .unwrap()); - data.cap_usd = (((supply as f64) * data.price_usd.parse::().unwrap()) as u64) - .to_formatted_string(&Locale::en); - data.cap_btc = (((supply as f64) * data.price_btc.parse::().unwrap()) as u64) - .to_formatted_string(&Locale::en); + if CONFIG.coingecko_api == "on" && val != Value::Null { + // Check if CoingGecko API returned error + if let Some(status) = val.get("status") { + println!("{} {}.", "[ WARNING ]".yellow(), + status["error_message"].as_str().unwrap().to_string()); + } else { + data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::() + .unwrap()); + data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::() + .unwrap()); + data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::().unwrap() as u64) + .to_formatted_string(&Locale::en); + data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::() + .unwrap()); + data.cap_usd = (((supply as f64) * data.price_usd.parse::().unwrap()) as u64) + .to_formatted_string(&Locale::en); + data.cap_btc = (((supply as f64) * data.price_btc.parse::().unwrap()) as u64) + .to_formatted_string(&Locale::en); + } } } @@ -189,8 +202,13 @@ pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { // Collecting: disk_usage. pub fn get_disk_usage(dashboard: Arc>) -> Result<(), Error> { let mut data = dashboard.lock().unwrap(); + let chain_data; - let chain_data = format!("{}/main/chain_data", CONFIG.grin_dir); + if CONFIG.coingecko_api == "on" { + chain_data = format!("{}/main/chain_data", CONFIG.grin_dir); + } else { + chain_data = format!("{}/test/chain_data", CONFIG.grin_dir); + } data.disk_usage = format!("{:.2}", (get_size(chain_data).unwrap() as f64) / 1000.0 / 1000.0 / 1000.0); @@ -200,16 +218,16 @@ pub fn get_disk_usage(dashboard: Arc>) -> Result<(), Error> { // Collecting: hashrate, difficulty, production cost, breakeven cost. -pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), Error> { +pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), anyhow::Error> { let difficulty_window = 1440; let height = get_current_height(dashboard.clone()); - if height.is_empty() == false { + if height.is_empty() == false && height.parse::().unwrap() > 1440 { let params1 = &format!("[{}, null, null]", height)[..]; let params2 = &format!("[{}, null, null]", height.parse::().unwrap() - difficulty_window)[..]; - let resp1 = call("get_block", params1, "foreign").await?; - let resp2 = call("get_block", params2, "foreign").await?; + let resp1 = call("get_block", params1, "1", "foreign").await?; + let resp2 = call("get_block", params2, "1", "foreign").await?; let mut data = dashboard.lock().unwrap(); @@ -223,21 +241,31 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), Er // https://forum.grin.mw/t/on-dual-pow-graph-rates-gps-and-difficulty/2144/52 // https://forum.grin.mw/t/difference-c31-and-c32-c33/7018/7 - let hashrate = (net_diff as f64) * 42.0 / 60.0 / 16384.0; - data.hashrate = format!("{:.2}", hashrate / 1000.0); + let hashrate = (net_diff as f64) * 42.0 / 60.0 / 16384.0; + + // KG/s + if hashrate > 1000.0 { + data.hashrate = format!("{:.2} KG/s", hashrate / 1000.0); + // G/s + } else { + data.hashrate = format!("{:.2} G/s", hashrate); + } + data.difficulty = net_diff.to_string(); - // Calculating G1-mini production per hour - let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0; + if CONFIG.coingecko_api == "on" { + // Calculating G1-mini production per hour + let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0; - // Calculating production cost of 1 grin - // Assuming $0.07 per kW/h - data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour)); + // Calculating production cost of 1 grin + // Assuming $0.07 per kW/h + data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour)); - data.reward_ratio = format!("{:.2}", data.price_usd.parse::().unwrap() - / data.production_cost.parse::().unwrap()); - data.breakeven_cost = format!("{:.2}", data.price_usd.parse::().unwrap() - / (120.0 / 1000.0 * (1.0 / coins_per_hour))); + data.reward_ratio = format!("{:.2}", data.price_usd.parse::().unwrap() + / data.production_cost.parse::().unwrap()); + data.breakeven_cost = format!("{:.2}", data.price_usd.parse::().unwrap() + / (120.0 / 1000.0 * (1.0 / coins_per_hour))); + } } } @@ -247,7 +275,7 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), Er // Collecting block data for recent blocks (block_list page). pub async fn get_block_list_data(height: &String, block: &mut Block) - -> Result<(), Error> { + -> Result<(), anyhow::Error> { // Max block weight is 40000 // One unit of weight is 32 bytes let kernel_weight = 3.0; @@ -255,11 +283,11 @@ pub async fn get_block_list_data(height: &String, block: &mut Block) let output_weight = 21.0; if height.is_empty() == false { - let params = &format!("[{}, null, null]", height)[..]; - let resp = call("get_block", params, "foreign").await.unwrap(); + let params = &format!("[{}, null, null]", height)[..]; + let resp = call("get_block", params, "1", "foreign").await?; if resp["result"]["Ok"].is_null() == false { - block.height = resp["result"]["Ok"]["header"]["height"].to_string(); + block.height = resp["result"]["Ok"]["header"]["height"].to_string(); let dt: DateTime = resp["result"]["Ok"]["header"]["timestamp"] .as_str().unwrap().to_string().parse().unwrap(); @@ -306,7 +334,7 @@ pub async fn get_block_list_data(height: &String, block: &mut Block) // Collecting block data. pub async fn get_block_data(height: &str, block: &mut Block) - -> Result<(), Error> { + -> Result<(), anyhow::Error> { // Max block weight is 40000 // One unit of weight is 32 bytes let kernel_weight = 3.0; @@ -316,7 +344,7 @@ pub async fn get_block_data(height: &str, block: &mut Block) if height.is_empty() == false { let params = &format!("[{}, null, null]", height)[..]; - let resp = call("get_block", params, "foreign").await?; + let resp = call("get_block", params, "1", "foreign").await?; if resp["result"]["Ok"].is_null() == false { block.hash = resp["result"]["Ok"]["header"]["hash"].as_str().unwrap().to_string(); @@ -362,10 +390,10 @@ pub async fn get_block_data(height: &str, block: &mut Block) // Get block height by hash. pub async fn get_block_header(hash: &str, height: &mut String) - -> Result<(), Error> { + -> Result<(), anyhow::Error> { let params = &format!("[null, \"{}\", null]", hash)[..]; - let resp = call("get_header", params, "foreign").await.unwrap(); + let resp = call("get_header", params, "1", "foreign").await?; if resp["result"]["Ok"].is_null() == false { *height = resp["result"]["Ok"]["height"].to_string(); @@ -376,14 +404,26 @@ pub async fn get_block_header(hash: &str, height: &mut String) // Get kernel. -pub async fn get_kernel(kernel: &str, height: &mut String) - -> Result<(), Error> { - let params = &format!("[\"{}\", null, null]", kernel)[..]; +pub async fn get_kernel(excess: &str, kernel: &mut Kernel) + -> Result<(), anyhow::Error> { + let params = &format!("[\"{}\", null, null]", excess)[..]; - let resp = call("get_kernel", params, "foreign").await.unwrap(); + let resp = call("get_kernel", params, "1", "foreign").await?; if resp["result"]["Ok"].is_null() == false { - *height = resp["result"]["Ok"]["height"].to_string(); + kernel.height = resp["result"]["Ok"]["height"].to_string(); + kernel.excess = resp["result"]["Ok"]["tx_kernel"]["excess"].as_str().unwrap().to_string(); + if resp["result"]["Ok"]["tx_kernel"]["features"]["Plain"].is_null() == false { + kernel.ker_type = "Plain".to_string(); + kernel.fee = format!("ツ {}", + resp["result"]["Ok"]["tx_kernel"]["features"]["Plain"]["fee"] + .to_string().parse::().unwrap() / 1000000000.0); + } else { + kernel.ker_type = resp["result"]["Ok"]["tx_kernel"]["features"].as_str().unwrap().to_string(); + kernel.fee = "ツ 0".to_string(); + } + + kernel.raw_data = serde_json::to_string_pretty(&resp).unwrap() } Ok(()) @@ -392,11 +432,11 @@ pub async fn get_kernel(kernel: &str, height: &mut String) // Collecting block kernels for transactions stats. pub async fn get_block_kernels(height: &String, blocks: &mut Vec) - -> Result<(), Error> { + -> Result<(), anyhow::Error> { if height.is_empty() == false { let params = &format!("[{}, {}, 720, false]", height.parse::().unwrap() - 720, height)[..]; - let resp = call("get_blocks", params, "foreign").await.unwrap(); + let resp = call("get_blocks", params, "1", "foreign").await?; for resp_block in resp["result"]["Ok"]["blocks"].as_array().unwrap() { let mut block = Block::new(); @@ -421,7 +461,7 @@ pub async fn get_txn_stats(dashboard: Arc>, let mut blocks = Vec::::new(); let height = get_current_height(dashboard.clone()); - if height.is_empty() == false { + if height.is_empty() == false && height.parse::().unwrap() > 1440 { // get_blocks grin rpc has limit of maximum of 1000 blocks request // https://github.com/mimblewimble/grin/blob/master/api/src/handlers/blocks_api.rs#L27 // So, collecting kernels 2 times by 720 blocks to get a day of blocks @@ -484,7 +524,7 @@ pub async fn get_recent_blocks(dashboard: Arc>, let mut i = 0; let height_str = get_current_height(dashboard.clone()); - if height_str.is_empty() == false { + if height_str.is_empty() == false && height_str.parse::().unwrap() > 0 { let height = height_str.parse::().unwrap(); let mut blocks_vec = Vec::::new(); @@ -510,11 +550,11 @@ pub async fn get_recent_blocks(dashboard: Arc>, // Collecting a specified list of blocks. pub async fn get_block_list_by_height(height: &str, blocks: &mut Vec, - latest_height: &mut u64) -> Result<(), Error> { + latest_height: &mut u64) -> Result<(), anyhow::Error> { let mut i = 0; let height = height.to_string(); - let resp = call("get_status", "[]", "owner").await.unwrap(); + let resp = call("get_status", "[]", "1", "owner").await?; if resp != Value::Null { *latest_height = resp["result"]["Ok"]["tip"]["height"].to_string().parse::().unwrap(); diff --git a/src/worker.rs b/src/worker.rs index b75343f..4a4f17e 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,5 +1,4 @@ use std::sync::{Arc, Mutex}; -use reqwest::Error; use crate::data::Dashboard; use crate::data::Block; @@ -10,7 +9,7 @@ use crate::requests; // Tokio Runtime Worker. // Collecting all the data. pub async fn run(dash: Arc>, blocks: Arc>>, - txns: Arc>) -> Result<(), Error> { + txns: 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()).await?; diff --git a/templates/base.html.tera b/templates/base.html.tera index 8c1c0f0..c9d1e2a 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -1,5 +1,5 @@ - + Grin Blockchain Explorer @@ -78,7 +78,7 @@ {% block content %}{% endblock content %} -