diff --git a/Cargo.lock b/Cargo.lock index 5cc456a..34f5b43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -234,6 +283,12 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "colored" version = "2.1.0" @@ -482,6 +537,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -736,13 +814,14 @@ dependencies = [ [[package]] name = "grin-explorer" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "chrono", "colored", "config", "either", + "env_logger", "fs_extra", "futures", "humantime", @@ -982,6 +1061,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -2540,6 +2625,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8d3ddc1..49b7b81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grin-explorer" -version = "0.1.4" +version = "0.1.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,6 +20,7 @@ lazy_static = "1.4.0" shellexpand = "3.1.0" either = "1.11.0" anyhow = "1.0.86" +env_logger = "0.11.3" [dependencies.reqwest] version = "0.11.23" diff --git a/Explorer.toml b/Explorer.toml index cd1dd2d..a5cf240 100644 --- a/Explorer.toml +++ b/Explorer.toml @@ -7,20 +7,38 @@ port = "3413" # Node protocol. Either HTTP or HTTPS. proto = "http" -# API username. +# API username. Comment out if user is not required. user = "grin" -# API secret path. +# API secret path. Comment out if secret is not required. api_secret_path = "~/.grin/main/.api_secret" -# Foreign API secret path. +# Foreign API secret path. Comment out if secret is not required. foreign_api_secret_path = "~/.grin/main/.foreign_api_secret" # Path to Grin directory. grin_dir = "~/.grin" -# CoinGecko API on/off switch. -coingecko_api = "on" +# Enable or disable CoinGecko API. +coingecko_api = "enabled" + +# Enable or disable node POST API public access. +public_api = "enabled" + + +# Grinnode config +# ip = "grinnode.live" +# port = "3413" +# proto = "https" +# coingecko_api = "enabled" +# public_api = "enabled" + + +# Grincoin config +# ip = "grincoin.org" +# proto = "https" +# coingecko_api = "enabled" +# public_api = "enabled" # Testnet config @@ -31,5 +49,6 @@ coingecko_api = "on" # api_secret_path = "~/.grin/test/.api_secret" # foreign_api_secret_path = "~/.grin/test/.foreign_api_secret" # grin_dir = "~/.grin" -# coingecko_api = "off" +# coingecko_api = "disabled" +# public_api = "enabled" diff --git a/README.md b/README.md index 3c8d9f1..c2cc644 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,14 @@ Grin is the very first, simple and fair MimbleWimble blockchain implementation. cd grin-explorer cargo build --release ``` -4. Run executable: `./target/release/grin-explorer` +4. Run executable: `RUST_LOG=rocket=warn,grin_explorer ./target/release/grin-explorer` You will see the following output: ``` - [ INFO ] Starting up Explorer. - [ INFO ] Starting up Rocket engine. - 🚀 Rocket has launched from http://127.0.0.1:8000 - [ OK ] Explorer Ready. + [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. ``` 5. Open explorer in your browser: http://127.0.0.1:8000 diff --git a/Rocket.toml b/Rocket.toml index 6077f33..1da001a 100644 --- a/Rocket.toml +++ b/Rocket.toml @@ -1,6 +1,5 @@ [default] 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. diff --git a/src/data.rs b/src/data.rs index bae2440..8f0b454 100644 --- a/src/data.rs +++ b/src/data.rs @@ -35,8 +35,6 @@ pub struct Dashboard { // mempool pub txns: String, pub stem: String, - // coingecko api - pub cg_api: String, } impl Dashboard { @@ -66,7 +64,6 @@ impl Dashboard { breakeven_cost: String::new(), txns: String::new(), stem: String::new(), - cg_api: String::new(), } } } @@ -170,6 +167,7 @@ pub struct ExplorerConfig { pub api_secret: String, pub foreign_api_secret: String, pub coingecko_api: String, + pub public_api: String, } impl ExplorerConfig { @@ -185,6 +183,29 @@ impl ExplorerConfig { api_secret: String::new(), foreign_api_secret: String::new(), coingecko_api: String::new(), + public_api: String::new(), + } + } +} + +// Output data +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Output { + pub height: String, + pub commit: String, + pub out_type: String, + pub status: String, + pub raw_data: String, +} + +impl Output { + pub fn new() -> Output { + Output { + height: String::new(), + commit: String::new(), + out_type: String::new(), + status: String::new(), + raw_data: String::new(), } } } diff --git a/src/main.rs b/src/main.rs index 5e74b06..77afd63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ use rocket::fs::FileServer; use rocket::State; use std::sync::{Arc, Mutex}; use std::time::Duration; -use colored::Colorize; use rocket::tokio; use rocket::response::Redirect; use either::Either; @@ -19,6 +18,8 @@ use crate::data::Dashboard; use crate::data::Block; use crate::data::Transactions; use crate::data::Kernel; +use crate::data::Output; +use crate::requests::CONFIG; // Rendering main (Dashboard) page. @@ -30,7 +31,7 @@ fn index(dashboard: &State>>) -> Template { route: "index", node_ver: &data.node_ver, proto_ver: &data.proto_ver, - cg_api: &data.cg_api, + cg_api: CONFIG.coingecko_api.clone(), }) } @@ -39,7 +40,8 @@ fn index(dashboard: &State>>) -> Template { #[get("/block_list")] fn block_list() -> Template { Template::render("block_list", context! { - route: "block_list", + route: "block_list", + cg_api: CONFIG.coingecko_api.clone(), }) } @@ -60,11 +62,13 @@ async fn block_list_by_height(input_height: &str) -> Template { if index >= height { Template::render("block_list", context! { - route: "block_list", + route: "block_list", + cg_api: CONFIG.coingecko_api.clone(), }) } else { Template::render("block_list", context! { - route: "block_list_by_height", + route: "block_list_by_height", + cg_api: CONFIG.coingecko_api.clone(), index, blocks, height, @@ -72,7 +76,8 @@ async fn block_list_by_height(input_height: &str) -> Template { } } else { Template::render("block_list", context! { - route: "block_list", + route: "block_list", + cg_api: CONFIG.coingecko_api.clone(), }) } } @@ -88,14 +93,16 @@ async fn block_details_by_height(height: &str) -> Template { if block.height.is_empty() == false { return Template::render("block_details", context! { - route: "block_details", + route: "block_details", + cg_api: CONFIG.coingecko_api.clone(), block, }); } } Template::render("error", context! { - route: "error", + route: "error", + cg_api: CONFIG.coingecko_api.clone(), }) } @@ -114,7 +121,8 @@ async fn block_header_by_hash(hash: &str) -> Either { } return Either::Left(Template::render("error", context! { - route: "error", + route: "error", + cg_api: CONFIG.coingecko_api.clone(), })) } @@ -128,13 +136,37 @@ async fn kernel(excess: &str) -> Template { if kernel.excess.is_empty() == false { return Template::render("kernel", context! { - route: "kernel", + route: "kernel", + cg_api: CONFIG.coingecko_api.clone(), kernel, }) } return Template::render("error", context! { - route: "error", + route: "error", + cg_api: CONFIG.coingecko_api.clone(), + }) +} + + +// Rendering page for a specified output. +#[get("/output/")] +async fn output(commit: &str) -> Template { + let mut output = Output::new(); + + let _ = requests::get_output(&commit, &mut output).await; + + if output.commit.is_empty() == false { + return Template::render("output", context! { + route: "output", + cg_api: CONFIG.coingecko_api.clone(), + output, + }) + } + + return Template::render("error", context! { + route: "error", + cg_api: CONFIG.coingecko_api.clone(), }) } @@ -143,11 +175,14 @@ async fn kernel(excess: &str) -> Template { // Using Option<&str> to match '/search' query without input params. // https://github.com/rwf2/Rocket/issues/608 #[get("/search?")] -fn search(input: Option<&str>) -> Either { +pub async fn search(input: Option<&str>) -> Either { // Unwrap Option and forward to Search page if no parameters let input = match input { Some(value) => value, - None => return Either::Left(Template::render("search", context! { route: "search", })), + None => return Either::Left(Template::render("search", context! { + route: "search", + cg_api: CONFIG.coingecko_api.clone(), + })), }; // Check for valid chars @@ -161,46 +196,68 @@ fn search(input: Option<&str>) -> Either { } else if input.len() == 64 { return Either::Right(Redirect::to(uri!(block_header_by_hash(input)))); - // Kernel + // Kernel or Unspent Output } else if input.len() == 66 { - return Either::Right(Redirect::to(uri!(kernel(input)))); + // First search for Kernel. + // If found, redirect to Kernel page, otherwise search for Unspent Output. + // As we can't distinguish between Kernel and Output, this will produce a redundant + // get_kernel call, but will allow for better UI (no need to ask user to input the type + // of the search request). + let mut kernel = Kernel::new(); + + let _ = requests::get_kernel(&input, &mut kernel).await; + + if kernel.excess.is_empty() == false { + // Here we are redirecting to kernel page and call get_kernel again there. + // Kernel page is a separate route and we want it to be accessed directly and + // via search functionality. + return Either::Right(Redirect::to(uri!(kernel(input)))); + } else { + // If Kernel not found, then search for Unspent Output + return Either::Right(Redirect::to(uri!(output(input)))); + } } } Either::Left(Template::render("error", context! { - route: "error", + route: "error", + cg_api: CONFIG.coingecko_api.clone(), })) } // Owner API. +// Whitelisted methods: get_connected_peers, get_peers, get_status. #[post("/v2/owner", data="")] async fn api_owner(data: &str) -> String { - let result = serde_json::from_str(data); + if CONFIG.public_api == "enabled" { + 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 { + let v: Value = match result { Ok(value) => value, - Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), + Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(), }; - return result.to_string(); - } + let method = match v["method"].as_str() { + Some(value) => value, + _ => return "{\"error\":\"bad syntax\"}".to_string(), + }; + + 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; - "{\"error\":\"not allowed\"}".to_string() + 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() + } else { + "{\"error\":\"not allowed\"}".to_string() + } } @@ -208,26 +265,30 @@ async fn api_owner(data: &str) -> String { // All methods are whitelisted. #[post("/v2/foreign", data="")] async fn api_foreign(data: &str) -> String { - let result = serde_json::from_str(data); + if CONFIG.public_api == "enabled" { + let result = serde_json::from_str(data); - let v: Value = match result { - Ok(value) => value, - Err(_err) => return "{\"error\":\"bad syntax\"}".to_string(), - }; + 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(), - }; + let method = match v["method"].as_str() { + Some(value) => value, + _ => return "{\"error\":\"bad syntax\"}".to_string(), + }; - let resp = requests::call(method, v["params"].to_string().as_str(), v["id"].to_string().as_str(), "foreign").await; + 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(), - }; + let result = match resp { + Ok(value) => value, + Err(_err) => return "{\"error\":\"rpc call failed\"}".to_string(), + }; - result.to_string() + return result.to_string(); + } else { + "{\"error\":\"not allowed\"}".to_string() + } } @@ -369,7 +430,11 @@ fn last_block_age(blocks: &State>>>) -> String { fn disk_usage(dashboard: &State>>) -> String { let data = dashboard.lock().unwrap(); - format!("{} GB", data.disk_usage) + if data.disk_usage.is_empty() == false { + return format!("{} GB", data.disk_usage); + } else { + return format!(""); + } } @@ -575,7 +640,9 @@ fn block_list_index(dashboard: &State>>) -> String { // Main #[rocket::main] async fn main() { - println!("{} Starting up Explorer.", "[ INFO ]".cyan()); + env_logger::init(); + + info!("starting up."); let dash = Arc::new(Mutex::new(Dashboard::new())); let dash_clone = dash.clone(); @@ -595,12 +662,12 @@ async fn main() { Ok(_v) => { if ready == false { ready = true; - println!("{} Explorer Ready.", "[ OK ]".green()); + info!("ready."); } }, Err(e) => { ready = false; - println!("{} {}.", "[ ERROR ]".red(), e); + error!("{}", e); }, } @@ -608,8 +675,6 @@ async fn main() { } }); - println!("{} Starting up Rocket engine.", "[ INFO ]".cyan()); - // Starting Rocket engine. let _ = rocket::build() .manage(dash) @@ -624,7 +689,7 @@ async fn main() { 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, - api_owner, api_foreign]) + output, api_owner, api_foreign]) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) .launch() diff --git a/src/requests.rs b/src/requests.rs index b6f62dd..9681f5a 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -16,12 +16,13 @@ use crate::data::Dashboard; use crate::data::Block; use crate::data::Transactions; use crate::data::ExplorerConfig; -use crate::Kernel; +use crate::data::Kernel; +use crate::data::Output; // Static explorer config structure lazy_static! { - static ref CONFIG: ExplorerConfig = { + pub static ref CONFIG: ExplorerConfig = { let mut cfg = ExplorerConfig::new(); let settings = Config::builder().add_source(config::File::with_name("Explorer")) .build().unwrap(); @@ -38,15 +39,22 @@ lazy_static! { "foreign_api_secret_path" => cfg.foreign_api_secret_path = value, "grin_dir" => cfg.grin_dir = value, "coingecko_api" => cfg.coingecko_api = value, + "public_api" => cfg.public_api = value, _ => println!("{} Unknown config setting '{}'.", "[ ERROR ]".red(), name), } } - cfg.api_secret = fs::read_to_string(format!("{}", - shellexpand::tilde(&cfg.api_secret_path))).unwrap(); - cfg.foreign_api_secret = fs::read_to_string(format!("{}", - shellexpand::tilde(&cfg.foreign_api_secret_path))).unwrap(); - cfg.grin_dir = format!("{}", shellexpand::tilde(&cfg.grin_dir)); + if cfg.api_secret_path.is_empty() == false { + cfg.api_secret = fs::read_to_string(format!("{}", shellexpand::tilde(&cfg.api_secret_path))).unwrap(); + } + + if cfg.foreign_api_secret_path.is_empty() == false { + cfg.foreign_api_secret = fs::read_to_string(format!("{}", shellexpand::tilde(&cfg.foreign_api_secret_path))).unwrap(); + } + + if cfg.grin_dir.is_empty() == false { + cfg.grin_dir = format!("{}", shellexpand::tilde(&cfg.grin_dir)); + } cfg }; @@ -58,13 +66,16 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul let rpc_url; let secret; - if rpc_type == "owner" { - rpc_url = format!("{}://{}:{}/v2/owner", CONFIG.proto, CONFIG.ip, CONFIG.port); - secret = CONFIG.api_secret.clone(); + if CONFIG.port.is_empty() == false { + rpc_url = format!("{}://{}:{}/v2/{}", CONFIG.proto, CONFIG.ip, CONFIG.port, rpc_type); + } else { + rpc_url = format!("{}://{}/v2/{}", CONFIG.proto, CONFIG.ip, rpc_type); } - else { - rpc_url = format!("{}://{}:{}/v2/foreign", CONFIG.proto, CONFIG.ip, CONFIG.port); - secret = CONFIG.foreign_api_secret.clone(); + + if rpc_type == "owner" { + secret = CONFIG.api_secret.clone(); + } else { + secret = CONFIG.foreign_api_secret.clone(); } let client = reqwest::Client::new(); @@ -75,6 +86,11 @@ pub async fn call(method: &str, params: &str, id: &str, rpc_type: &str) -> Resul .send() .await?; + match result.error_for_status_ref() { + Ok(_res) => (), + Err(err) => { error!("rpc failed, status code: {:?}", err.status().unwrap()); }, + } + let val: Value = serde_json::from_str(&result.text().await.unwrap())?; Ok(val) @@ -94,9 +110,6 @@ pub async fn get_status(dashboard: Arc>) -> Result<(), anyhow:: data.proto_ver = resp["result"]["Ok"]["protocol_version"].to_string(); } - // Also set cg_api value - data.cg_api = CONFIG.coingecko_api.clone(); - Ok(()) } @@ -150,7 +163,7 @@ pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { let result; let mut val = Value::Null; - if CONFIG.coingecko_api == "on" { + if CONFIG.coingecko_api == "enabled" { 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(); @@ -170,23 +183,18 @@ pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { 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); + data.soft_supply = format!("{:.2}", supply.to_string().parse::().unwrap() / 3150000000.0 * 100.0); - if CONFIG.coingecko_api == "on" && val != Value::Null { + if CONFIG.coingecko_api == "enabled" && 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()); + warn!("{}", 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.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.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) @@ -202,16 +210,24 @@ 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_dir; - if CONFIG.coingecko_api == "on" { - chain_data = format!("{}/main/chain_data", CONFIG.grin_dir); + if CONFIG.coingecko_api == "enabled" { + chain_dir = format!("{}/main/chain_data", CONFIG.grin_dir); } else { - chain_data = format!("{}/test/chain_data", CONFIG.grin_dir); + chain_dir = 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); + match get_size(chain_dir.clone()) { + Ok(chain_size) => data.disk_usage = format!("{:.2}", (chain_size as f64) / 1000.0 / 1000.0 / 1000.0), + Err(e) => { + if CONFIG.ip == "127.0.0.1" || CONFIG.ip == "0.0.0.0" { + error!("{}: \"{}\"", e, chain_dir); + } else { + // Ignore error for external node connection + } + }, + } Ok(()) } @@ -224,8 +240,7 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an 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 params2 = &format!("[{}, null, null]", height.parse::().unwrap() - difficulty_window)[..]; let resp1 = call("get_block", params1, "1", "foreign").await?; let resp2 = call("get_block", params2, "1", "foreign").await?; @@ -243,9 +258,9 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an // https://forum.grin.mw/t/difference-c31-and-c32-c33/7018/7 let hashrate = (net_diff as f64) * 42.0 / 60.0 / 16384.0; - // KG/s + // kG/s if hashrate > 1000.0 { - data.hashrate = format!("{:.2} KG/s", hashrate / 1000.0); + data.hashrate = format!("{:.2} kG/s", hashrate / 1000.0); // G/s } else { data.hashrate = format!("{:.2} G/s", hashrate); @@ -253,7 +268,7 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an data.difficulty = net_diff.to_string(); - if CONFIG.coingecko_api == "on" { + if CONFIG.coingecko_api == "enabled" { // Calculating G1-mini production per hour let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0; @@ -261,10 +276,12 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an // 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() + if data.price_usd.is_empty() == false { + 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.breakeven_cost = format!("{:.2}", data.price_usd.parse::().unwrap() + / (120.0 / 1000.0 * (1.0 / coins_per_hour))); + } } } } @@ -403,6 +420,41 @@ pub async fn get_block_header(hash: &str, height: &mut String) } +// Get output. +pub async fn get_output(commit: &str, output: &mut Output) -> Result<(), anyhow::Error> { + // First check whether output is broadcasted but not confirmed yet (in mempool) + let mut resp = call("get_unconfirmed_transactions", "[]", "1", "foreign").await?; + + if resp["result"]["Ok"].is_null() == false { + for tx in resp["result"]["Ok"].as_array().unwrap() { + for out in tx["tx"]["body"]["outputs"].as_array().unwrap() { + if out["commit"].as_str().unwrap() == commit { + // Only Plain outputs in the mempool + output.out_type = "Plain".to_string(); + output.commit = out["commit"].as_str().unwrap().to_string(); + output.status = "Unconfirmed".to_string(); + // Found it, no need to continue + return Ok(()); + } + } + } + } + + let params = &format!("[[\"{}\"], null, null, true, true]", commit)[..]; + + resp = call("get_outputs", params, "1", "foreign").await?; + + if resp["result"]["Ok"][0].is_null() == false { + output.height = resp["result"]["Ok"][0]["block_height"].to_string(); + output.commit = resp["result"]["Ok"][0]["commit"].as_str().unwrap().to_string(); + output.out_type = resp["result"]["Ok"][0]["output_type"].as_str().unwrap().to_string(); + output.raw_data = serde_json::to_string_pretty(&resp).unwrap() + } + + Ok(()) +} + + // Get kernel. pub async fn get_kernel(excess: &str, kernel: &mut Kernel) -> Result<(), anyhow::Error> { // First check whether kernel is broadcasted but not confirmed yet (in mempool) diff --git a/templates/base.html.tera b/templates/base.html.tera index 4b3bb6a..cd525b1 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -21,7 +21,7 @@ -