From 9ceebf3b9e548e33eeadcd7a2b448b549a228188 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Sun, 19 May 2024 17:20:48 +0300 Subject: [PATCH 01/10] Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- templates/base.html.tera | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3c3156..979161b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,7 +730,7 @@ dependencies = [ [[package]] name = "grin-explorer" -version = "0.1.2" +version = "0.1.3" dependencies = [ "chrono", "colored", diff --git a/Cargo.toml b/Cargo.toml index 3a21405..96efd10 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 diff --git a/templates/base.html.tera b/templates/base.html.tera index 8c1c0f0..701ee1d 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -212,7 +212,7 @@
- v.0.1.2 + v.0.1.3
From 33426c1a5eaeffce0cfec317b697f3265bc522b8 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Sun, 19 May 2024 17:37:54 +0300 Subject: [PATCH 02/10] Hardening search input --- src/main.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index feb0e9a..a907f89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -138,11 +138,12 @@ async fn kernel(kernel: &str) -> Either { // 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 From 38ee6500eb361c80c81fcb5b83ccf154f4aa12bc Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Sun, 19 May 2024 20:04:13 +0300 Subject: [PATCH 03/10] Introduced coingecko api switch, preparations for testnet explorer --- Explorer.toml | 16 +- src/data.rs | 5 + src/main.rs | 15 +- src/requests.rs | 94 +++++----- templates/index.html.tera | 348 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 435 insertions(+), 43 deletions(-) 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/src/data.rs b/src/data.rs index a8a462c..caca842 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(), } } } @@ -141,6 +144,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 +159,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 a907f89..fa93a9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,9 +25,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, }) } @@ -211,7 +212,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() @@ -491,7 +498,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); diff --git a/src/requests.rs b/src/requests.rs index b758888..d3ac911 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -36,6 +36,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), } } @@ -140,15 +141,21 @@ 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(); - + + // Save the setting into Dashboard structure for later use + data.cg_api = CONFIG.coingecko_api.clone(); + if data.height.is_empty() == false { // Calculating coin supply // Adding +1 as block index starts with 0 @@ -157,28 +164,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 +200,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); @@ -204,7 +220,7 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), Er 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)[..]; @@ -227,17 +243,19 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), Er data.hashrate = format!("{:.2}", hashrate / 1000.0); 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))); + } } } @@ -421,7 +439,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 +502,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(); diff --git a/templates/index.html.tera b/templates/index.html.tera index b4a8f36..ac1e8c0 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -4,6 +4,11 @@ +{# We have different UI to display if CoinGecko API is disabled by user #} + +{% if cg_api == "on" %} +{# CoinGecko API is enabled #} +
@@ -456,6 +461,349 @@
+{% else %} +{# CoinGecko API is disabled #} + +
+
+
+
+
MARKET
+
+
+
Yearly Inflation Rate 
%
+
+
+
+
Coin Supply 
+
+
+
+
Soft Total Supply + + +
+
+
+
+ +
+
+
MINING
+
+
+
Hashrate 
KG/s
+
+
+
+
Difficulty 
+
+
+
+
Block Reward 
ツ 60
+
+
+
+
+ + +
+
+
+
BLOCKCHAIN
+
+
+
Size 
+
+
+
+
Block Height 
+
+
+
+
Time Since Last Block 
+
+
+
+
+
+
TRANSACTIONS & FEES
+
+
+
1H Period 
+
+
+
+
24H Period 
+
+
+
+
+ + +
+
+
+
MEMPOOL
+
+
+
Transactions 
+
+
+
+
Stem 
+
+
+
+
+
+
CONNECTIONS
+
+
+
Inbound 
+
+
+
+
Outbound 
+
+
+
+
+
+
NODE
+
+
+
Version 
{{ node_ver }}
+
+
+
+
Protocol 
{{ proto_ver }}
+
+
+
+
Sync Status 
+
+
+
+
+
+ + +
+ +
+
+
MARKET
+
+
+
Yearly Inflation Rate 
%
+
+
+
+
Coin Supply 
+
+
+
+
Soft Total Supply + + +
+
+
+
+ + +
+
+
+
BLOCKCHAIN
+
+
+
+
Size 
+
+
+
+
Block Height 
+
+
+
+
Time Since Last Block 
+
+
+
+
+
+
MINING
+
+
+
Hashrate 
KG/s
+
+
+
+
Difficulty 
+
+
+
+
Block Reward 
ツ 60
+
+
+
+
+
+
TRANSACTIONS & FEES
+
+
+
1H Period 
+
+
+
+
24H Period 
+
+
+
+ + + +
+
+
MEMPOOL
+
+
+
Transactions 
+
+
+
+
Stem 
+
+
+
+
+
+
CONNECTIONS
+
+
+
Inbound 
+
+
+
+
Outbound 
+
+
+
+
+
+
NODE
+
+
+
Version 
{{ node_ver }}
+
+
+
+
Protocol 
{{ proto_ver }}
+
+
+
+
Sync Status 
+
+
+
+ +
+ + + +
+ + + + + + + + + + +
+ + +{% endif %} +
{% endblock content%} From 04fde44594b22b25a7b169f39476321953905a5e Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Mon, 20 May 2024 02:33:19 +0300 Subject: [PATCH 04/10] More preparations for testnet explorer --- Rocket.toml | 4 ++++ src/main.rs | 2 +- src/requests.rs | 6 +++--- templates/error.html.tera | 6 +----- 4 files changed, 9 insertions(+), 9 deletions(-) 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/main.rs b/src/main.rs index fa93a9d..c7227db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,7 +155,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)))); } diff --git a/src/requests.rs b/src/requests.rs index d3ac911..f1b4839 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -93,6 +93,9 @@ 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(()) } @@ -153,9 +156,6 @@ pub async fn get_market(dashboard: Arc>) -> Result<(), Error> { let mut data = dashboard.lock().unwrap(); - // Save the setting into Dashboard structure for later use - data.cg_api = CONFIG.coingecko_api.clone(); - if data.height.is_empty() == false { // Calculating coin supply // Adding +1 as block index starts with 0 diff --git a/templates/error.html.tera b/templates/error.html.tera index 15c7ec0..88b82f8 100644 --- a/templates/error.html.tera +++ b/templates/error.html.tera @@ -7,11 +7,7 @@

No results found.


- Explorer supports requests by block number, block hash or kernel.

- Examples:
- Block number - 2765726
- Block hash - 0000fc4d93e5717579b955ab840165d96603f009804a228be22da76f6f906a3c
- Kernel - 084caeb931b7e8cb73d6419ea74ea157a3cef19f6e9307108a8a808df58437a4ef + Explorer supports requests by block number, block hash or kernel.
From 50173f92215c8699541c35d735c9c0f573a52dac Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Mon, 20 May 2024 16:37:24 +0300 Subject: [PATCH 05/10] Generalize returned Error --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/requests.rs | 39 ++++++++++++++++++++------------------- src/worker.rs | 3 +-- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 979161b..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" @@ -732,6 +738,7 @@ dependencies = [ name = "grin-explorer" version = "0.1.3" dependencies = [ + "anyhow", "chrono", "colored", "config", diff --git a/Cargo.toml b/Cargo.toml index 96efd10..164b703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/src/requests.rs b/src/requests.rs index f1b4839..4fe3375 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -53,7 +53,7 @@ lazy_static! { // RPC requests to grin node. -async fn call(method: &str, params: &str, rpc_type: &str) -> Result { +async fn call(method: &str, params: &str, rpc_type: &str) -> Result { let rpc_url; let secret; @@ -74,14 +74,14 @@ async fn call(method: &str, params: &str, rpc_type: &str) -> Result>) -> Result<(), Error> { +pub async fn get_status(dashboard: Arc>) -> Result<(), anyhow::Error> { let resp = call("get_status", "[]", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -101,7 +101,7 @@ pub async fn get_status(dashboard: Arc>) -> Result<(), Error> { // Collecting: txns, stem. -pub async fn get_mempool(dashboard: Arc>) -> Result<(), Error> { +pub async fn get_mempool(dashboard: Arc>) -> Result<(), anyhow::Error> { let resp1 = call("get_pool_size", "[]", "foreign").await?; let resp2 = call("get_stempool_size", "[]", "foreign").await?; @@ -117,7 +117,8 @@ pub async fn get_mempool(dashboard: Arc>) -> Result<(), Error> // Collecting: inbound, outbound. -pub async fn get_connected_peers(dashboard: Arc>) -> Result<(), Error> { +pub async fn get_connected_peers(dashboard: Arc>) + -> Result<(), anyhow::Error> { let resp = call("get_connected_peers", "[]", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -216,7 +217,7 @@ 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()); @@ -265,7 +266,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; @@ -273,11 +274,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, "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(); @@ -324,7 +325,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; @@ -380,10 +381,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, "foreign").await?; if resp["result"]["Ok"].is_null() == false { *height = resp["result"]["Ok"]["height"].to_string(); @@ -395,10 +396,10 @@ 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> { + -> Result<(), anyhow::Error> { let params = &format!("[\"{}\", null, null]", kernel)[..]; - let resp = call("get_kernel", params, "foreign").await.unwrap(); + let resp = call("get_kernel", params, "foreign").await?; if resp["result"]["Ok"].is_null() == false { *height = resp["result"]["Ok"]["height"].to_string(); @@ -410,11 +411,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, "foreign").await?; for resp_block in resp["result"]["Ok"]["blocks"].as_array().unwrap() { let mut block = Block::new(); @@ -528,11 +529,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", "[]", "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?; From fca10e1a2c0d2ee8e6b3318f739f2e09090d0297 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Mon, 20 May 2024 19:02:12 +0300 Subject: [PATCH 06/10] Adjust hashrate metrics for testnet --- src/main.rs | 2 +- src/requests.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index c7227db..46159a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -314,7 +314,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() } diff --git a/src/requests.rs b/src/requests.rs index 4fe3375..b885fa8 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -240,8 +240,16 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an // 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(); if CONFIG.coingecko_api == "on" { From b9b0045448abd192c3e8e6e91fe9797f19d83c6f Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Thu, 23 May 2024 17:41:03 +0300 Subject: [PATCH 07/10] API support --- src/main.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- src/requests.rs | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 46159a8..4682bb8 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; @@ -168,6 +169,64 @@ fn search(input: &str) -> Either { } +// Owner API. +#[post("/api/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(), "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("/api/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(), + }; + + let resp = requests::call(method, v["params"].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 { @@ -560,7 +619,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 b885fa8..dbcfef4 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -53,7 +53,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, rpc_type: &str) -> Result { let rpc_url; let secret; From 3aecc6d557959931b2ea287220cd2dd0494f8228 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Thu, 23 May 2024 18:32:05 +0300 Subject: [PATCH 08/10] Shorten api url --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4682bb8..4298fd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -170,7 +170,7 @@ fn search(input: &str) -> Either { // Owner API. -#[post("/api/v2/owner", data="")] +#[post("/v2/owner", data="")] async fn api_owner(data: &str) -> String { let result = serde_json::from_str(data); @@ -184,6 +184,7 @@ async fn api_owner(data: &str) -> String { _ => return "{\"error\":\"bad syntax\"}".to_string(), }; + println!("{}", method); // 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(), "owner").await; @@ -202,7 +203,7 @@ async fn api_owner(data: &str) -> String { // Foreign API. // All methods are whitelisted. -#[post("/api/v2/foreign", data="")] +#[post("/v2/foreign", data="")] async fn api_foreign(data: &str) -> String { let result = serde_json::from_str(data); @@ -216,6 +217,7 @@ async fn api_foreign(data: &str) -> String { _ => return "{\"error\":\"bad syntax\"}".to_string(), }; + println!("{}", method); let resp = requests::call(method, v["params"].to_string().as_str(), "foreign").await; let result = match resp { From 7aadd85e19f36a1aaffaa2d1c31c19ed064bd325 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Thu, 23 May 2024 18:43:38 +0300 Subject: [PATCH 09/10] Remove debug prints --- src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4298fd2..26207b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,7 +184,6 @@ async fn api_owner(data: &str) -> String { _ => return "{\"error\":\"bad syntax\"}".to_string(), }; - println!("{}", method); // 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(), "owner").await; @@ -217,7 +216,6 @@ async fn api_foreign(data: &str) -> String { _ => return "{\"error\":\"bad syntax\"}".to_string(), }; - println!("{}", method); let resp = requests::call(method, v["params"].to_string().as_str(), "foreign").await; let result = match resp { From 2aaebee5f46b255fde2fdef16cce5eb69077ade2 Mon Sep 17 00:00:00 2001 From: aglkm <39521015+aglkm@users.noreply.github.com> Date: Fri, 24 May 2024 15:14:16 +0300 Subject: [PATCH 10/10] Added kernel page, respect id field in api --- src/data.rs | 23 +++++++++++++++++ src/main.rs | 30 ++++++++++++---------- src/requests.rs | 47 +++++++++++++++++++++------------- templates/base.html.tera | 4 +-- templates/kernel.html.tera | 52 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 templates/kernel.html.tera diff --git a/src/data.rs b/src/data.rs index caca842..d529320 100644 --- a/src/data.rs +++ b/src/data.rs @@ -111,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 { diff --git a/src/main.rs b/src/main.rs index 26207b6..3584b85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,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. @@ -119,21 +120,22 @@ 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", - })) + }) } @@ -183,10 +185,10 @@ async fn api_owner(data: &str) -> String { 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(), "owner").await; + 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, @@ -216,7 +218,9 @@ async fn api_foreign(data: &str) -> String { _ => return "{\"error\":\"bad syntax\"}".to_string(), }; - let resp = requests::call(method, v["params"].to_string().as_str(), "foreign").await; + 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, diff --git a/src/requests.rs b/src/requests.rs index dbcfef4..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 @@ -53,7 +54,7 @@ lazy_static! { // RPC requests to grin node. -pub 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; @@ -68,7 +69,7 @@ pub async fn call(method: &str, params: &str, rpc_type: &str) -> Result Result>) -> Result<(), anyhow::Error> { - let resp = call("get_status", "[]", "owner").await?; + let resp = call("get_status", "[]", "1", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -102,8 +103,8 @@ pub async fn get_status(dashboard: Arc>) -> Result<(), anyhow:: // Collecting: txns, stem. pub async fn get_mempool(dashboard: Arc>) -> Result<(), anyhow::Error> { - let resp1 = call("get_pool_size", "[]", "foreign").await?; - let resp2 = call("get_stempool_size", "[]", "foreign").await?; + let resp1 = call("get_pool_size", "[]", "1", "foreign").await?; + let resp2 = call("get_stempool_size", "[]", "1", "foreign").await?; let mut data = dashboard.lock().unwrap(); @@ -119,7 +120,7 @@ pub async fn get_mempool(dashboard: Arc>) -> Result<(), anyhow: // Collecting: inbound, outbound. pub async fn get_connected_peers(dashboard: Arc>) -> Result<(), anyhow::Error> { - let resp = call("get_connected_peers", "[]", "owner").await?; + let resp = call("get_connected_peers", "[]", "1", "owner").await?; let mut data = dashboard.lock().unwrap(); @@ -225,8 +226,8 @@ pub async fn get_mining_stats(dashboard: Arc>) -> Result<(), an 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(); @@ -283,7 +284,7 @@ pub async fn get_block_list_data(height: &String, 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.height = resp["result"]["Ok"]["header"]["height"].to_string(); @@ -343,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(); @@ -392,7 +393,7 @@ pub async fn get_block_header(hash: &str, height: &mut String) -> Result<(), anyhow::Error> { let params = &format!("[null, \"{}\", null]", hash)[..]; - let resp = call("get_header", params, "foreign").await?; + let resp = call("get_header", params, "1", "foreign").await?; if resp["result"]["Ok"].is_null() == false { *height = resp["result"]["Ok"]["height"].to_string(); @@ -403,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) +pub async fn get_kernel(excess: &str, kernel: &mut Kernel) -> Result<(), anyhow::Error> { - let params = &format!("[\"{}\", null, null]", kernel)[..]; + let params = &format!("[\"{}\", null, null]", excess)[..]; - let resp = call("get_kernel", params, "foreign").await?; + 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(()) @@ -423,7 +436,7 @@ pub async fn get_block_kernels(height: &String, blocks: &mut Vec) if height.is_empty() == false { let params = &format!("[{}, {}, 720, false]", height.parse::().unwrap() - 720, height)[..]; - let resp = call("get_blocks", params, "foreign").await?; + 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(); @@ -541,7 +554,7 @@ pub async fn get_block_list_by_height(height: &str, blocks: &mut Vec, let mut i = 0; let height = height.to_string(); - let resp = call("get_status", "[]", "owner").await?; + 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/templates/base.html.tera b/templates/base.html.tera index 701ee1d..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 %}
-