Compare commits

..

1 Commits

@ -1,17 +1,18 @@
[package] [package]
name = "monero-block-explorer" name = "monero-block-explorer"
version = "0.1.0" version = "0.2.0"
authors = ["lalanza808 <lance@lzahq.tech>"] authors = ["lalanza808 <lance@lzahq.tech>"]
edition = "2018" edition = "2018"
description = "Simple web application for querying nodes to retrieve information about the Monero blockchain." description = "Simple web application for querying nodes to retrieve information about the Monero blockchain."
[dependencies] [dependencies]
rocket = "0.4.4" rocket = "0.5.0-rc.2"
rocket_contrib = {version = "0.4.4", features = ["handlebars_templates", "tera_templates"]} # rocket = "0.4.4"
serde = "1.0" # rocket_contrib = {version = "0.4.4", features = ["handlebars_templates", "tera_templates"]}
serde_json = "1.0" # serde = "1.0"
serde_derive = "1.0" # serde_json = "1.0"
reqwest = { version = "0.10.4", features = ["blocking", "json"] } # serde_derive = "1.0"
openssl = { version = "0.10", features = ["vendored"] } # reqwest = { version = "0.10.4", features = ["blocking", "json"] }
qrcode-generator = "1.0.6" # openssl = { version = "0.10", features = ["vendored"] }
base64 = "0.12.0" # qrcode-generator = "1.0.6"
# base64 = "0.12.0"

@ -1,269 +1,281 @@
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;
extern crate reqwest;
extern crate qrcode_generator;
mod data_types;
use rocket::http::RawStr; #[get("/")]
use rocket::response::Redirect; fn index() -> &'static str {
use rocket_contrib::json::{Json,JsonValue}; "Hello, world!"
use rocket_contrib::templates::Template;
use rocket_contrib::serve::StaticFiles;
use reqwest::blocking::{RequestBuilder, Client};
use reqwest::Error;
use qrcode_generator::QrCodeEcc;
use std::env;
use data_types::*;
fn issue_rpc(method: &str, params: Option<RPCParams>) -> RequestBuilder {
let http_client = Client::new();
let url = format!(
"{}/json_rpc",
env::var("DAEMON_URI").unwrap()
);
let post_data = RPCPayload {
method: Some(method.to_string()),
params: params,
..Default::default()
};
http_client.post(&url).json(&post_data)
} }
fn issue_raw_rpc(method: &str, params: JsonValue) -> RequestBuilder { #[launch]
let http_client = Client::new(); fn rocket() -> _ {
let url = format!( rocket::build().mount("/", routes![index])
"{}/{}",
env::var("DAEMON_URI").unwrap(),
&method
);
http_client.post(&url).json(&params)
} }
fn build_rpc(method: &str, data: Option<JsonValue>, raw: bool) -> RequestBuilder { // #![feature(proc_macro_hygiene, decl_macro)]
let http_client = Client::new(); // #[macro_use] extern crate rocket;
let daemon_uri = env::var("DAEMON_URI").unwrap(); // #[macro_use] extern crate rocket_contrib;
match raw { // #[macro_use] extern crate serde_derive;
true => { // extern crate reqwest;
let uri = format!("{}/{}", &daemon_uri, &method); // extern crate qrcode_generator;
if let None = data { // mod data_types;
http_client.post(&uri)
} else {
http_client.post(&uri).json(&data)
}
},
false => {
let uri = format!("{}/json_rpc", &daemon_uri);
let data = RPCPayload {
method: Some(method.to_string()),
..Default::default()
};
http_client.post(&uri).json(&data)
}
}
}
#[get("/block/hash/<block_hash>")] // use rocket::http::RawStr;
fn get_block_by_hash(block_hash: String) -> Template { // use rocket::response::Redirect;
let params = RPCParams { // use rocket_contrib::json::{Json,JsonValue};
hash: Some(block_hash), // use rocket_contrib::templates::Template;
..Default::default() // use rocket_contrib::serve::StaticFiles;
}; // use reqwest::blocking::{RequestBuilder, Client};
let res: GetBlock = issue_rpc(&"get_block", Some(params)) // use reqwest::Error;
.send().unwrap().json().unwrap(); // use qrcode_generator::QrCodeEcc;
Template::render("block", &res.result) // use std::env;
} // use data_types::*;
#[get("/block/height/<block_height>")] // fn issue_rpc(method: &str, params: Option<RPCParams>) -> RequestBuilder {
fn get_block_by_height(block_height: String) -> Template { // let http_client = Client::new();
let params = RPCParams { // let url = format!(
height: Some(block_height), // "{}/json_rpc",
..Default::default() // env::var("DAEMON_URI").unwrap()
}; // );
let res: GetBlock = issue_rpc(&"get_block", Some(params)) // let post_data = RPCPayload {
.send().unwrap().json().unwrap(); // method: Some(method.to_string()),
Template::render("block", &res.result) // params: params,
} // ..Default::default()
// };
// http_client.post(&url).json(&post_data)
// }
#[get("/transaction/<tx_hash>")] // fn issue_raw_rpc(method: &str, params: JsonValue) -> RequestBuilder {
fn get_transaction_by_hash(tx_hash: String) -> Template { // let http_client = Client::new();
let params: JsonValue = json!({ // let url = format!(
"txs_hashes": [&tx_hash], // "{}/{}",
"decode_as_json": true // env::var("DAEMON_URI").unwrap(),
}); // &method
let mut res: GetTransactions = issue_raw_rpc(&"get_transactions", params) // );
.send().unwrap().json().unwrap(); // http_client.post(&url).json(&params)
for f in &mut res.txs { // }
f.process();
};
let context = json!({
"tx_info": res.txs,
"tx_hash": tx_hash
});
Template::render("transaction", context)
}
#[get("/address/<wallet_address>?<tx_amount>&<tx_description>&<recipient_name>&<tx_payment_id>")] // fn build_rpc(method: &str, data: Option<JsonValue>, raw: bool) -> RequestBuilder {
fn show_wallet_address( // let http_client = Client::new();
wallet_address: String, // let daemon_uri = env::var("DAEMON_URI").unwrap();
tx_amount: Option<String>, // match raw {
tx_description: Option<String>, // true => {
recipient_name: Option<String>, // let uri = format!("{}/{}", &daemon_uri, &method);
tx_payment_id: Option<String> // if let None = data {
) -> Template { // http_client.post(&uri)
let address_uri = format!( // } else {
"monero:{}&tx_amount={}&tx_description={}&recipient_name={}&tx_payment_id={}", // http_client.post(&uri).json(&data)
wallet_address, // }
tx_amount.unwrap_or("".to_string()), // },
tx_description.unwrap_or("".to_string()), // false => {
recipient_name.unwrap_or("".to_string()), // let uri = format!("{}/json_rpc", &daemon_uri);
tx_payment_id.unwrap_or("".to_string()) // let data = RPCPayload {
); // method: Some(method.to_string()),
let qr_code: String = qrcode_generator::to_svg_to_string(address_uri, QrCodeEcc::Low, 256, None) // ..Default::default()
.unwrap(); // };
let qr_code: String = base64::encode(qr_code); // http_client.post(&uri).json(&data)
let context: JsonValue = json!({ // }
"qr_code": qr_code, // }
"wallet_address": wallet_address // }
});
Template::render("address", context)
}
#[get("/search?<value>")] // #[get("/block/hash/<block_hash>")]
fn search(value: &RawStr) -> Redirect { // fn get_block_by_hash(block_hash: String) -> Template {
// This search implementation is not ideal but it works. // let params = RPCParams {
// We basically check the length of the search value and // hash: Some(block_hash),
// attempt to redirect to the appropriate route. // ..Default::default()
let sl: usize = value.len(); // };
if sl == 0 { // let res: GetBlock = issue_rpc(&"get_block", Some(params))
return Redirect::found(uri!(index)); // .send().unwrap().json().unwrap();
} else if sl < 8 { // Template::render("block", &res.result)
// Less than 8 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::<u32>() {
Ok(_) => return Redirect::found(uri!(get_block_by_height: value.as_str())),
Err(_) => return Redirect::found(uri!(error))
}
} 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<GetBlock, Error> = issue_rpc(
&"get_block", Some(block_hash_params)
).send().unwrap().json();
match check_valid_block_hash { // #[get("/block/height/<block_height>")]
Ok(_) => return Redirect::found(uri!(get_block_by_hash: value.as_str())), // fn get_block_by_height(block_height: String) -> Template {
Err(_) => { // let params = RPCParams {
let tx_hash_params: JsonValue = json!({"txs_hashes": [&value.as_str()]}); // height: Some(block_height),
let check_valid_tx_hash: Result<GetTransactions, Error> = issue_raw_rpc( // ..Default::default()
&"get_transactions", tx_hash_params // };
).send().unwrap().json(); // let res: GetBlock = issue_rpc(&"get_block", Some(params))
// .send().unwrap().json().unwrap();
// Template::render("block", &res.result)
// }
match check_valid_tx_hash { // #[get("/transaction/<tx_hash>")]
Ok(_) => return Redirect::found(uri!(get_transaction_by_hash: value.as_str())), // fn get_transaction_by_hash(tx_hash: String) -> Template {
Err(_) => return Redirect::found(uri!(error)) // let params: JsonValue = json!({
} // "txs_hashes": [&tx_hash],
} // "decode_as_json": true
} // });
} else if sl == 95 { // let mut res: GetTransactions = issue_raw_rpc(&"get_transactions", params)
// Equal to 95 characters is probably a wallet address. // .send().unwrap().json().unwrap();
// For this let's just redirect to the `show_wallet_address` route. // for f in &mut res.txs {
return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", "")) // f.process();
} else if sl == 105 { // };
// Equal to 105 characters is probably an integrated address. // let context = json!({
// For this let's just redirect to the `show_wallet_address` route. // "tx_info": res.txs,
return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", "")) // "tx_hash": tx_hash
} else { // });
// Anything else hasn't been implemented yet // Template::render("transaction", context)
// so redirect to error response. // }
return Redirect::found(uri!(error));
};
}
#[get("/tx_pool")] // #[get("/address/<wallet_address>?<tx_amount>&<tx_description>&<recipient_name>&<tx_payment_id>")]
fn show_tx_pool() -> Json<GetTransactionPool> { // fn show_wallet_address(
let mut tx_pool: GetTransactionPool = build_rpc( // wallet_address: String,
&"get_transaction_pool", None, true // tx_amount: Option<String>,
).send().unwrap().json().unwrap(); // tx_description: Option<String>,
// recipient_name: Option<String>,
// tx_payment_id: Option<String>
// ) -> Template {
// let address_uri = format!(
// "monero:{}&tx_amount={}&tx_description={}&recipient_name={}&tx_payment_id={}",
// wallet_address,
// tx_amount.unwrap_or("".to_string()),
// tx_description.unwrap_or("".to_string()),
// recipient_name.unwrap_or("".to_string()),
// tx_payment_id.unwrap_or("".to_string())
// );
// let qr_code: String = qrcode_generator::to_svg_to_string(address_uri, QrCodeEcc::Low, 256, None)
// .unwrap();
// let qr_code: String = base64::encode(qr_code);
// let context: JsonValue = json!({
// "qr_code": qr_code,
// "wallet_address": wallet_address
// });
// Template::render("address", context)
// }
for f in &mut tx_pool.transactions { // #[get("/search?<value>")]
f.process(); // 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();
// if sl == 0 {
// return Redirect::found(uri!(index));
// } else if sl < 8 {
// // Less than 8 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::<u32>() {
// Ok(_) => return Redirect::found(uri!(get_block_by_height: value.as_str())),
// Err(_) => return Redirect::found(uri!(error))
// }
// } 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<GetBlock, Error> = issue_rpc(
// &"get_block", Some(block_hash_params)
// ).send().unwrap().json();
Json(tx_pool) // 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<GetTransactions, Error> = issue_raw_rpc(
// &"get_transactions", tx_hash_params
// ).send().unwrap().json();
#[get("/")] // match check_valid_tx_hash {
fn index() -> Template { // Ok(_) => return Redirect::found(uri!(get_transaction_by_hash: value.as_str())),
let daemon_info: GetInfo = issue_rpc(&"get_info", None) // Err(_) => return Redirect::found(uri!(error))
.send().unwrap().json().unwrap(); // }
// }
// }
// } else if sl == 95 {
// // Equal to 95 characters is probably a wallet address.
// // For this let's just redirect to the `show_wallet_address` route.
// return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", ""))
// } else if sl == 105 {
// // Equal to 105 characters is probably an integrated address.
// // For this let's just redirect to the `show_wallet_address` route.
// return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", ""))
// } else {
// // Anything else hasn't been implemented yet
// // so redirect to error response.
// return Redirect::found(uri!(error));
// };
// }
let mut tx_pool: GetTransactionPool = build_rpc( // #[get("/tx_pool")]
&"get_transaction_pool", None, true // fn show_tx_pool() -> Json<GetTransactionPool> {
).send().unwrap().json().unwrap(); // let mut tx_pool: GetTransactionPool = build_rpc(
// &"get_transaction_pool", None, true
// ).send().unwrap().json().unwrap();
let mut tx_json_raw: Vec<TransactionJSON> = vec![]; // for f in &mut tx_pool.transactions {
// f.process();
// };
for f in &mut tx_pool.transactions { // Json(tx_pool)
f.process(); // }
let j: TransactionJSON = serde_json::from_str(&f.tx_json).unwrap();
tx_json_raw.push(j)
};
let context: JsonValue = json!({ // #[get("/")]
"daemon_info": daemon_info.result, // fn index() -> Template {
"tx_pool": tx_pool.transactions, // let daemon_info: GetInfo = issue_rpc(&"get_info", None)
"tx_json": tx_json_raw // .send().unwrap().json().unwrap();
});
Template::render("index", context) // let mut tx_pool: GetTransactionPool = build_rpc(
} // &"get_transaction_pool", None, true
// ).send().unwrap().json().unwrap();
#[get("/error", )] // let mut tx_json_raw: Vec<TransactionJSON> = vec![];
fn error() -> JsonValue {
json!({
"status": "error",
"reason": "There was an error while searching the provided values."
})
}
#[catch(404)] // for f in &mut tx_pool.transactions {
fn not_found() -> JsonValue { // f.process();
json!({ // let j: TransactionJSON = serde_json::from_str(&f.tx_json).unwrap();
"status": "error", // tx_json_raw.push(j)
"reason": "Resource was not found." // };
})
}
fn main() { // let context: JsonValue = json!({
let env_url = env::var("DAEMON_URI"); // "daemon_info": daemon_info.result,
match env_url { // "tx_pool": tx_pool.transactions,
Ok(_) => { // "tx_json": tx_json_raw
rocket::ignite() // });
.mount("/", routes![
index, // Template::render("index", context)
search, // }
show_tx_pool,
get_block_by_height, // #[get("/error", )]
get_block_by_hash, // fn error() -> JsonValue {
get_transaction_by_hash, // json!({
show_wallet_address, // "status": "error",
error // "reason": "There was an error while searching the provided values."
]) // })
.mount("/static", StaticFiles::from("./static")) // }
.register(catchers![not_found])
.attach(Template::fairing()) // #[catch(404)]
.launch(); // fn not_found() -> JsonValue {
}, // json!({
Err(_) => panic!("Environment variable `DAEMON_URI` not provided.") // "status": "error",
} // "reason": "Resource was not found."
} // })
// }
// fn main() {
// let env_url = env::var("DAEMON_URI");
// match env_url {
// Ok(_) => {
// rocket::ignite()
// .mount("/", routes![
// index,
// search,
// show_tx_pool,
// get_block_by_height,
// get_block_by_hash,
// get_transaction_by_hash,
// show_wallet_address,
// error
// ])
// .mount("/static", StaticFiles::from("./static"))
// .register(catchers![not_found])
// .attach(Template::fairing())
// .launch();
// },
// Err(_) => panic!("Environment variable `DAEMON_URI` not provided.")
// }
// }

Loading…
Cancel
Save