diff --git a/src/data_types.rs b/src/data_types.rs index 26043fe..0916eb8 100644 --- a/src/data_types.rs +++ b/src/data_types.rs @@ -1,4 +1,4 @@ -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct RPCPayload { pub jsonrpc: String, pub id: String, @@ -17,7 +17,7 @@ impl Default for RPCPayload { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct RPCParams { pub hash: Option, pub txs_hashes: Option>, @@ -84,21 +84,21 @@ pub struct GetInfoResult { pub wide_difficulty: String } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct BlockByHeaderHash { pub id: String, pub jsonrpc: String, pub result: BlockByHeaderHashResult } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct BlockByHeaderHashResult { pub status: String, pub untrusted: bool, pub block_header: BlockHeader } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct BlockHeader { pub block_size: u32, pub depth: u32, @@ -116,12 +116,12 @@ pub struct BlockHeader { pub timestamp: i64 } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct GetTransactions { pub txs: Vec } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct GetTransactionsTxs { pub block_height: u32, pub block_timestamp: i64, @@ -130,16 +130,15 @@ pub struct GetTransactionsTxs { pub output_indices: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct GetBlock { pub id: String, pub jsonrpc: String, pub result: GetBlockResult } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug)] pub struct GetBlockResult { - pub blob: String, pub block_header: BlockHeader, pub credits: u8, pub miner_tx_hash: String, diff --git a/src/main.rs b/src/main.rs index 2026a4c..08ca419 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,14 @@ #[macro_use] extern crate serde_derive; extern crate reqwest; mod data_types; + use rocket::http::RawStr; use rocket::response::Redirect; use rocket_contrib::json::{Json, JsonValue}; use rocket_contrib::templates::Template; use rocket_contrib::serve::StaticFiles; use reqwest::blocking::{RequestBuilder, Client}; +use reqwest::Error; use std::env; use data_types::*; @@ -38,30 +40,29 @@ fn issue_raw_rpc(method: &str, params: JsonValue) -> RequestBuilder { } #[get("/block/hash/")] -fn get_block_header_by_block_hash(block_hash: String) -> Json { +fn get_block_by_hash(block_hash: String) -> Json { let params = RPCParams { hash: Some(block_hash), ..Default::default() }; - let res: BlockByHeaderHash = issue_rpc(&"get_block_header_by_hash", Some(params)) + let res: GetBlock = issue_rpc(&"get_block", Some(params)) .send().unwrap().json().unwrap(); - Json(res.result.block_header) + Json(res.result) } #[get("/block/height/")] -fn get_block_by_height(block_height: String) -> String { +fn get_block_by_height(block_height: String) -> Json { let params = RPCParams { height: Some(block_height), ..Default::default() }; let res: GetBlock = issue_rpc(&"get_block", Some(params)) .send().unwrap().json().unwrap(); - // Json(res.result.block_header) - serde_json::to_string(&res).unwrap() + Json(res.result) } #[get("/transaction/")] -fn get_block_header_by_transaction_hash(tx_hash: String) -> Json { +fn get_transaction_by_hash(tx_hash: String) -> Json { let params: JsonValue = json!({"txs_hashes": [&tx_hash]}); let res: GetTransactions = issue_raw_rpc(&"get_transactions", params) .send().unwrap().json().unwrap(); @@ -70,68 +71,53 @@ fn get_block_header_by_transaction_hash(tx_hash: String) -> Json")] fn search(value: &RawStr) -> Redirect { + // This search implementation is not ideal but it works. + // We basically check the length of the search value and + // attempt to redirect to the appropriate route. let sl: usize = value.len(); - let first_byte = value.get(0..1).unwrap(); - println!("Search value: {}", value); - println!("First byte: {}", first_byte); + println!("{}", sl); if sl < 10 { + // Less than 10 characters is probably a block height. If it can + // be parsed as valid u32 then redirect to `get_block_by_height`, + // otherwise redirect to the error response. match value.parse::() { - Ok(v) => { - println!("Found: {}", v); - // "this looks like a block height" - return Redirect::found(uri!(get_block_by_height: value.as_str())); - }, - Err(e) => { - println!("Error: {}", e); - // "this is an invalid search query" - return Redirect::found(uri!(index)); - } + Ok(_) => return Redirect::found(uri!(get_block_by_height: value.as_str())), + Err(_) => return Redirect::found(uri!(error)) } - } else if sl < 95 { - // "this looks like a tx or block hash" - return Redirect::found(uri!(index)); - } else if sl == 95 { - match first_byte { - "9" => { - println!("This looks like a testnet address"); - return Redirect::found(uri!(index)); - }, - "A" => { - println!("This looks like a testnet subaddress"); - return Redirect::found(uri!(index)); - }, - "5" => { - println!("This looks like a stagenet address"); - return Redirect::found(uri!(index)); - }, - "7" => { - println!("This looks like a stagenet subaddress"); - return Redirect::found(uri!(index)); - }, - "4" => { - println!("This looks like a mainnet address"); - return Redirect::found(uri!(index)); - }, - "8" => { - println!("This looks like a mainnet subaddress"); - return Redirect::found(uri!(index)); - }, - _ => { - println!("Not sure what this is"); - return Redirect::found(uri!(index)); + } else if sl == 64 { + // Equal to 64 characters is probably a hash; block or tx. + // For this we attempt to query for a block with + // given hash. If we don't receive a valid/expected + // response then we attempt to query for a transaction hash. + // If neither works then redirect to error response. + let block_hash_params = RPCParams { + hash: Some(value.to_string()), + ..Default::default() + }; + let check_valid_block_hash: Result = issue_rpc( + &"get_block", Some(block_hash_params) + ).send().unwrap().json(); + + match check_valid_block_hash { + Ok(_) => return Redirect::found(uri!(get_block_by_hash: value.as_str())), + Err(_) => { + let tx_hash_params: JsonValue = json!({"txs_hashes": [&value.as_str()]}); + let check_valid_tx_hash: Result = issue_raw_rpc( + &"get_transactions", tx_hash_params + ).send().unwrap().json(); + + match check_valid_tx_hash { + Ok(_) => return Redirect::found(uri!(get_transaction_by_hash: value.as_str())), + Err(_) => return Redirect::found(uri!(error)) + } } } - } else if sl == 105 { - // "this looks like an integrated address" - return Redirect::found(uri!(index)); } else { - // "no idea what this is" - return Redirect::found(uri!(index)); + // Anything else hasn't been implemented yet + // so redirect to error response. + return Redirect::found(uri!(error)); }; - - // println!("No if stmt matched"); - // return Redirect::found(uri!(index)); } #[get("/")] @@ -141,6 +127,14 @@ fn index() -> Template { Template::render("index", &res.result) } +#[get("/error", )] +fn error() -> JsonValue { + json!({ + "status": "error", + "reason": "There was an error while searching the provided values." + }) +} + #[catch(404)] fn not_found() -> JsonValue { json!({ @@ -158,8 +152,9 @@ fn main() { index, search, get_block_by_height, - get_block_header_by_block_hash, - get_block_header_by_transaction_hash, + get_block_by_hash, + get_transaction_by_hash, + error ]) .mount("/static", StaticFiles::from("./static")) .register(catchers![not_found]) diff --git a/templates/base.html.tera b/templates/base.html.tera new file mode 100644 index 0000000..cf0a46e --- /dev/null +++ b/templates/base.html.tera @@ -0,0 +1,15 @@ + + + + + Monero Block Explorer + + + + +
+ {% block content %} + {% endblock content %} +
+ + diff --git a/templates/index.html.tera b/templates/index.html.tera index 125d18f..aac79b6 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -1,78 +1,71 @@ - - - - - Monero Block Explorer - - - - -
-

Monero Block Explorer

-
- -
-

Server Info

-
-
-

Network Stats

-
-
-

Transaction Pool

-
-
-

Top Block

-
-
-

Previous Block

-
+{% extends "base" %} -

get_info

-

alt_blocks_count: {{ alt_blocks_count }}

-

block_size_limit: {{ block_size_limit }}

-

block_size_median: {{ block_size_median }}

-

block_weight_limit: {{ block_weight_limit }}

-

block_weight_median: {{ block_weight_median }}

-

bootstrap_daemon_address: {{ bootstrap_daemon_address }}

-

credits: {{ credits }}

-

cumulative_difficulty: {{ cumulative_difficulty }}

-

cumulative_difficulty_top64: {{ cumulative_difficulty_top64 }}

-

database_size: {{ database_size }}

-

difficulty: {{ difficulty }}

-

difficulty_top64: {{ difficulty_top64 }}

-

free_space: {{ free_space }}

-

grey_peerlist_size: {{ grey_peerlist_size }}

-

height: {{ height }}

-

height_without_bootstrap: {{ height_without_bootstrap }}

-

incoming_connections_count: {{ incoming_connections_count }}

-

mainnet: {{ mainnet }}

-

nettype: {{ nettype }}

-

offline: {{ offline }}

-

outgoing_connections_count: {{ outgoing_connections_count }}

-

rpc_connections_count: {{ rpc_connections_count }}

-

stagenet: {{ stagenet }}

-

start_time: {{ start_time }}

-

status: {{ status }}

-

target: {{ target }}

-

target_height: {{ target_height }}

-

testnet: {{ testnet }}

-

top_block_hash: {{ top_block_hash }}

-

top_hash: {{ top_hash }}

-

tx_count: {{ tx_count }}

-

tx_pool_size: {{ tx_pool_size }}

-

untrusted: {{ untrusted }}

-

update_available: {{ update_available }}

-

version: {{ version }}

-

was_bootstrap_ever_used: {{ was_bootstrap_ever_used }}

-

white_peerlist_size: {{ white_peerlist_size }}

-

wide_cumulative_difficulty: {{ wide_cumulative_difficulty }}

-

wide_difficulty: {{ wide_difficulty }}

+{% block content %} +
+

Monero Block Explorer

+
+ +
+

Server Info

+
+
+

Network Stats

+
+
+

Transaction Pool

+
+
+

Top Block

+
+
+

Previous Block

+
- - +

get_info

+

alt_blocks_count: {{ alt_blocks_count }}

+

block_size_limit: {{ block_size_limit }}

+

block_size_median: {{ block_size_median }}

+

block_weight_limit: {{ block_weight_limit }}

+

block_weight_median: {{ block_weight_median }}

+

bootstrap_daemon_address: {{ bootstrap_daemon_address }}

+

credits: {{ credits }}

+

cumulative_difficulty: {{ cumulative_difficulty }}

+

cumulative_difficulty_top64: {{ cumulative_difficulty_top64 }}

+

database_size: {{ database_size }}

+

difficulty: {{ difficulty }}

+

difficulty_top64: {{ difficulty_top64 }}

+

free_space: {{ free_space }}

+

grey_peerlist_size: {{ grey_peerlist_size }}

+

height: {{ height }}

+

height_without_bootstrap: {{ height_without_bootstrap }}

+

incoming_connections_count: {{ incoming_connections_count }}

+

mainnet: {{ mainnet }}

+

nettype: {{ nettype }}

+

offline: {{ offline }}

+

outgoing_connections_count: {{ outgoing_connections_count }}

+

rpc_connections_count: {{ rpc_connections_count }}

+

stagenet: {{ stagenet }}

+

start_time: {{ start_time }}

+

status: {{ status }}

+

target: {{ target }}

+

target_height: {{ target_height }}

+

testnet: {{ testnet }}

+

top_block_hash: {{ top_block_hash }}

+

top_hash: {{ top_hash }}

+

tx_count: {{ tx_count }}

+

tx_pool_size: {{ tx_pool_size }}

+

untrusted: {{ untrusted }}

+

update_available: {{ update_available }}

+

version: {{ version }}

+

was_bootstrap_ever_used: {{ was_bootstrap_ever_used }}

+

white_peerlist_size: {{ white_peerlist_size }}

+

wide_cumulative_difficulty: {{ wide_cumulative_difficulty }}

+

wide_difficulty: {{ wide_difficulty }}

+ +{% endblock content %}