Compare commits
10 Commits
e7c7fcc91f
...
8f900fd70a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f900fd70a | ||
|
|
edf7800a88 | ||
|
|
ec6d99e9d0 | ||
|
|
349f0705d3 | ||
|
|
21bb64a276 | ||
|
|
d22fe50de6 | ||
|
|
552923ae40 | ||
|
|
817835302b | ||
|
|
847e4f1e3d | ||
|
|
1de0c69405 |
1326
Cargo.lock
generated
1326
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
@ -2,53 +2,70 @@
|
|||||||
name = "neptune-explorer"
|
name = "neptune-explorer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "neptune-explorer"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "high_rate_attack"
|
||||||
|
path = "src/bin/high_rate_attack.rs"
|
||||||
|
required-features = ["attacks"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "scraper"
|
||||||
|
path = "src/bin/scraper.rs"
|
||||||
|
required-features = ["attacks"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7.9", features = ["macros"] }
|
axum = { version = "0.7.9", features = ["macros"] }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.115"
|
serde_json = "1.0.145"
|
||||||
tokio = { version = "1.37.0", features = ["full", "tracing"] }
|
tokio = { version = "1.48.0", features = ["full", "tracing"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
neptune-cash = "0.3.0"
|
neptune-cash = "0.4.0"
|
||||||
tarpc = { version = "^0.34", features = [
|
tarpc = { version = "^0.34", features = [
|
||||||
"tokio1",
|
"tokio1",
|
||||||
"serde-transport",
|
"serde-transport",
|
||||||
"serde-transport-json",
|
"serde-transport-json",
|
||||||
"tcp",
|
"tcp",
|
||||||
] }
|
] }
|
||||||
clap = "4.5.4"
|
clap = "4.5.50"
|
||||||
thiserror = "1.0.59"
|
thiserror = "1.0.69"
|
||||||
boilerplate = { version = "1.0.0" }
|
boilerplate = { version = "1.0.1" }
|
||||||
html-escaper = "0.2.0"
|
html-escaper = "0.2.0"
|
||||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
tower-http = { version = "0.5.2", features = ["fs"] }
|
||||||
readonly = "0.2.12"
|
readonly = "0.2.13"
|
||||||
url = "2.5.0"
|
url = "2.5.7"
|
||||||
lettre = {version = "0.11.7", features = ["tokio1-native-tls"]}
|
lettre = { version = "0.11.19", features = ["tokio1-native-tls"] }
|
||||||
chrono = "0.4.34"
|
chrono = "0.4.42"
|
||||||
|
|
||||||
# only should be used inside main.rs, for the binary.
|
# only should be used inside main.rs, for the binary.
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.100"
|
||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
derive_more = { version = "1.0.0", features = ["display"] }
|
derive_more = { version = "1.0.0", features = ["display"] }
|
||||||
|
|
||||||
# not a direct dep. workaround for weird "could not resolve" cargo error
|
# not a direct dep. workaround for weird "could not resolve" cargo error
|
||||||
indexmap = "2.7.0"
|
indexmap = "2.12.0"
|
||||||
|
|
||||||
blake3 = {version = "1.8.2", optional = true}
|
blake3 = { version = "1.8.2", optional = true }
|
||||||
rand = {version = "0.9.2", optional = true}
|
rand = { version = "0.9.2", optional = true }
|
||||||
|
reqwest = { version = "0.12.24", optional = true }
|
||||||
|
log = {version = "0.4.28", optional = true}
|
||||||
|
env_logger = {version = "0.11.8", optional = true}
|
||||||
|
regex = {version = "1.12.2", optional = true }
|
||||||
|
futures = {version = "0.3.31", optional = true }
|
||||||
|
|
||||||
#[dev-dependencies]
|
#[dev-dependencies]
|
||||||
test-strategy = "0.4.3"
|
test-strategy = "0.4.3"
|
||||||
proptest = "1.7.0"
|
proptest = "1.9.0"
|
||||||
arbitrary = "1.4.1"
|
arbitrary = "1.4.2"
|
||||||
proptest-arbitrary-interop = "0.1.0"
|
proptest-arbitrary-interop = "0.1.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
neptune-cash = { git = "https://github.com/Neptune-Crypto/neptune-core.git", branch = "asz/transparent-transactions" }
|
neptune-cash = { git = "https://github.com/Neptune-Crypto/neptune-core.git", rev = "71f471a526a13ddd41ab4400e1a873471d03ede6" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mock = ["dep:blake3", "dep:rand"]
|
mock = ["dep:blake3", "dep:rand"]
|
||||||
|
attacks = ["reqwest", "log", "env_logger", "regex", "dep:rand", "futures"]
|
||||||
|
|||||||
@ -34,14 +34,15 @@ not tested or supported. Please let us know if you get it work. patches accep
|
|||||||
2. start neptune-explorer
|
2. start neptune-explorer
|
||||||
|
|
||||||
```
|
```
|
||||||
nohup neptune-explorer 2>&1 > /path/to/logs/neptune-explorer.log &
|
nohup neptune-explorer --site-domain testdomain 2>&1 > /path/to/logs/neptune-explorer.log &
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
* The block-explorer automatically uses the same network (mainnet, testnet, etc) as the neptune-core instance it is connected to, and the network is displayed in the web interface.
|
* The block-explorer automatically uses the same network (mainnet, testnet, etc) as the neptune-core instance it is connected to, and the network is displayed in the web interface.
|
||||||
* If neptune-core RPC server is running on a non-standard port, you can provide it with the `--neptune-rpc-port` flag.
|
* If neptune-core RPC server is running on a non-standard port, you can provide it with the `--neptune-rpc-port` flag.
|
||||||
* neptune-explorer listens for http requests on port 3000 by default. This can be changed with the `--listen-port` flag.
|
* neptune-explorer listens for http requests on port 3000 by default. This can be changed with the `--listen-port` flag.
|
||||||
* Site name must be specified with the `--site-name` flag.
|
* Site name can be specified with the --site-name flag.
|
||||||
|
* Site domain *must* be specified with the `--site-domain` flag.
|
||||||
|
|
||||||
|
|
||||||
## Connecting via Browser
|
## Connecting via Browser
|
||||||
@ -52,7 +53,7 @@ Just navigate to http://localhost:3000/
|
|||||||
|
|
||||||
When connected to an out-of-date or unsynced neptune-core node, it might be a good idea to turn on mocking so that whenever a resource is unavailable, a random one is generated and returned. To do this, compile with the feature flag "mock" and make sure that the "MOCK" environment variable is set.
|
When connected to an out-of-date or unsynced neptune-core node, it might be a good idea to turn on mocking so that whenever a resource is unavailable, a random one is generated and returned. To do this, compile with the feature flag "mock" and make sure that the "MOCK" environment variable is set.
|
||||||
|
|
||||||
In one command: `MOCK=1 cargo run --features "mock" -- --site-name testname`
|
In one command: `MOCK=1 cargo run --features "mock" -- --site-domain testdomain`
|
||||||
|
|
||||||
## SSL/TLS, Nginx, etc.
|
## SSL/TLS, Nginx, etc.
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use lettre::AsyncSmtpTransport;
|
||||||
|
use lettre::AsyncTransport;
|
||||||
|
use lettre::Message;
|
||||||
|
use lettre::Tokio1Executor;
|
||||||
|
use tracing::info;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::model::app_state::AppState;
|
use crate::model::app_state::AppState;
|
||||||
use crate::model::config::AlertConfig;
|
use crate::model::config::AlertConfig;
|
||||||
use crate::model::config::Config;
|
use crate::model::config::Config;
|
||||||
use crate::model::config::SmtpMode;
|
use crate::model::config::SmtpMode;
|
||||||
use clap::Parser;
|
|
||||||
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
|
|
||||||
use tracing::{info, warn};
|
|
||||||
|
|
||||||
// pub fn alert_params_configured() -> bool {
|
|
||||||
// Config::parse().alert_config().is_some()
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn check_alert_params() -> bool {
|
pub fn check_alert_params() -> bool {
|
||||||
match Config::parse().alert_config() {
|
match Config::parse().alert_config() {
|
||||||
|
|||||||
27
src/bin/high_rate_attack.rs
Normal file
27
src/bin/high_rate_attack.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use futures::future::join_all;
|
||||||
|
use reqwest::Client;
|
||||||
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let client = Client::new();
|
||||||
|
let url = "http://127.0.0.1:3000/your_endpoint";
|
||||||
|
let num_requests = 200; // adjust as needed
|
||||||
|
let concurrency = 20; // parallel tasks
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let futures = (0..num_requests).map(|_| {
|
||||||
|
let client = &client;
|
||||||
|
async move {
|
||||||
|
let _ = client.get(url).send().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
join_all(futures).await;
|
||||||
|
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
println!(
|
||||||
|
"Sent {} requests in {:.2?} (concurrency {})",
|
||||||
|
num_requests, elapsed, concurrency
|
||||||
|
);
|
||||||
|
}
|
||||||
102
src/bin/scraper.rs
Normal file
102
src/bin/scraper.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use log::LevelFilter;
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use rand::seq::IteratorRandom;
|
||||||
|
use regex::Regex;
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::{signal, time};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Scrape the explorer website when running locally.
|
||||||
|
///
|
||||||
|
/// This program maintains a dictionary of URLs, which is initially populated
|
||||||
|
/// with 'http://localhost:3000'. It fetches a random URL from the dictionary in
|
||||||
|
/// each iteration, logs positive messages if successful, extracts new URLs from
|
||||||
|
/// the response body to add to the dictionary, logs warnings or errors for
|
||||||
|
/// request failures or timeouts, sleeps for a bit, and continues until Ctrl-C
|
||||||
|
/// is pressed.
|
||||||
|
///
|
||||||
|
/// Run with:
|
||||||
|
/// `> cargo run --bin scraper`
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Initialize logger
|
||||||
|
env_logger::builder().filter_level(LevelFilter::Info).init();
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.timeout(Duration::from_millis(300))
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build HTTP client");
|
||||||
|
|
||||||
|
let root_url = "http://localhost:3000".to_string();
|
||||||
|
let urls = Arc::new(Mutex::new(HashSet::from([root_url.clone()])));
|
||||||
|
|
||||||
|
let href_regex = Regex::new(r#"<a\s+(?:[^>]*?\s+)?href=['\"](.*?)['\"]"#).unwrap();
|
||||||
|
|
||||||
|
info!("Starting fetch loop. Press Ctrl-C to stop.");
|
||||||
|
|
||||||
|
let urls_clone = Arc::clone(&urls);
|
||||||
|
let fetch_loop = async move {
|
||||||
|
loop {
|
||||||
|
// Pick a random URL safely
|
||||||
|
let url_opt = {
|
||||||
|
let urls_guard = urls_clone.lock().unwrap();
|
||||||
|
urls_guard.iter().choose(&mut rand::rng()).cloned()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = url_opt {
|
||||||
|
match client.get(&url).send().await {
|
||||||
|
Ok(resp) => {
|
||||||
|
if resp.status().is_success() {
|
||||||
|
match resp.text().await {
|
||||||
|
Ok(text) => {
|
||||||
|
info!("Success fetching {}", url);
|
||||||
|
let mut urls_guard = urls_clone.lock().unwrap();
|
||||||
|
for cap in href_regex.captures_iter(&text) {
|
||||||
|
let href = &cap[1];
|
||||||
|
if let Ok(parsed_url) =
|
||||||
|
Url::parse(&[&root_url.clone(), href].concat())
|
||||||
|
{
|
||||||
|
let normalized = parsed_url.as_str();
|
||||||
|
if urls_guard.insert(normalized.to_owned()) {
|
||||||
|
info!(
|
||||||
|
"Added new URL to dictionary: {}",
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to read response body from {}: {}", url, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Non-success status {} from {}", resp.status(), url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if err.is_timeout() {
|
||||||
|
warn!("Timeout fetching {}", url);
|
||||||
|
} else {
|
||||||
|
error!("Error fetching {}: {}", url, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("URL dictionary is empty, no URL to fetch");
|
||||||
|
}
|
||||||
|
|
||||||
|
time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = fetch_loop => {}, // This runs indefinitely unless stopped
|
||||||
|
_ = signal::ctrl_c() => {
|
||||||
|
info!("Ctrl-C received, stopping...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::model::app_state::AppStateInner;
|
|
||||||
use html_escaper::Escape;
|
use html_escaper::Escape;
|
||||||
|
|
||||||
|
use crate::model::app_state::AppStateInner;
|
||||||
|
|
||||||
#[derive(Debug, Clone, boilerplate::Boilerplate)]
|
#[derive(Debug, Clone, boilerplate::Boilerplate)]
|
||||||
#[boilerplate(filename = "web/html/components/header.html")]
|
#[boilerplate(filename = "web/html/components/header.html")]
|
||||||
pub struct HeaderHtml<'a> {
|
pub struct HeaderHtml<'a> {
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
use crate::html::component::header::HeaderHtml;
|
use std::collections::HashMap;
|
||||||
use crate::html::page::not_found::not_found_html_response;
|
use std::sync::Arc;
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::announcement_selector::AnnouncementSelector;
|
|
||||||
use crate::model::announcement_type::AnnouncementType;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
|
||||||
use axum::extract::rejection::PathRejection;
|
use axum::extract::rejection::PathRejection;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
@ -17,10 +13,16 @@ use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
|||||||
use neptune_cash::prelude::triton_vm::prelude::BFieldCodec;
|
use neptune_cash::prelude::triton_vm::prelude::BFieldCodec;
|
||||||
use neptune_cash::prelude::twenty_first::tip5::Tip5;
|
use neptune_cash::prelude::twenty_first::tip5::Tip5;
|
||||||
use neptune_cash::util_types::mutator_set::addition_record::AdditionRecord;
|
use neptune_cash::util_types::mutator_set::addition_record::AdditionRecord;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::html::component::header::HeaderHtml;
|
||||||
|
use crate::html::page::not_found::not_found_html_response;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::announcement_selector::AnnouncementSelector;
|
||||||
|
use crate::model::announcement_type::AnnouncementType;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn announcement_page(
|
pub async fn announcement_page(
|
||||||
maybe_path: Result<Path<AnnouncementSelector>, PathRejection>,
|
maybe_path: Result<Path<AnnouncementSelector>, PathRejection>,
|
||||||
@ -84,12 +86,19 @@ pub async fn announcement_page(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|output| output.addition_record())
|
.map(|output| output.addition_record())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
addition_record_indices = state.rpc_client.addition_record_indices_for_block(context::current(), state.token(), block_selector, &addition_records).await
|
addition_record_indices = state
|
||||||
|
.rpc_client
|
||||||
|
.addition_record_indices_for_block(
|
||||||
|
context::current(),
|
||||||
|
state.token(),
|
||||||
|
block_selector,
|
||||||
|
&addition_records,
|
||||||
|
)
|
||||||
|
.await
|
||||||
.map_err(|e| not_found_html_response(state, Some(e.to_string())))?
|
.map_err(|e| not_found_html_response(state, Some(e.to_string())))?
|
||||||
.map_err(rpc_method_err)?
|
.map_err(rpc_method_err)?
|
||||||
.expect(
|
.into_iter()
|
||||||
"block guaranteed to exist because we got here; getting its announcements should work",
|
.collect::<HashMap<_, _>>();
|
||||||
);
|
|
||||||
|
|
||||||
let mut transparent_utxos_cache = state.transparent_utxos_cache.lock().await;
|
let mut transparent_utxos_cache = state.transparent_utxos_cache.lock().await;
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
use crate::html::component::header::HeaderHtml;
|
use std::sync::Arc;
|
||||||
use crate::html::page::not_found::not_found_html_response;
|
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use crate::model::block_selector_extended::BlockSelectorExtended;
|
|
||||||
use axum::extract::rejection::PathRejection;
|
use axum::extract::rejection::PathRejection;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
@ -10,10 +7,15 @@ use axum::response::Html;
|
|||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use html_escaper::Escape;
|
use html_escaper::Escape;
|
||||||
use html_escaper::Trusted;
|
use html_escaper::Trusted;
|
||||||
use neptune_cash::models::blockchain::block::block_info::BlockInfo;
|
use neptune_cash::protocol::consensus::block::block_info::BlockInfo;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::html::component::header::HeaderHtml;
|
||||||
|
use crate::html::page::not_found::not_found_html_response;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_page(
|
pub async fn block_page(
|
||||||
user_input_maybe: Result<Path<BlockSelectorExtended>, PathRejection>,
|
user_input_maybe: Result<Path<BlockSelectorExtended>, PathRejection>,
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
use crate::http_util::not_found_html_err;
|
|
||||||
use crate::http_util::not_found_html_handler;
|
|
||||||
use crate::model::app_state::AppStateInner;
|
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use html_escaper::Escape;
|
use html_escaper::Escape;
|
||||||
|
|
||||||
|
use crate::http_util::not_found_html_err;
|
||||||
|
use crate::http_util::not_found_html_handler;
|
||||||
|
use crate::model::app_state::AppStateInner;
|
||||||
|
|
||||||
pub fn not_found_page(error_msg: Option<String>) -> Html<String> {
|
pub fn not_found_page(error_msg: Option<String>) -> Html<String> {
|
||||||
#[derive(boilerplate::Boilerplate)]
|
#[derive(boilerplate::Boilerplate)]
|
||||||
#[boilerplate(filename = "web/html/page/not_found.html")]
|
#[boilerplate(filename = "web/html/page/not_found.html")]
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::RawQuery;
|
use axum::extract::RawQuery;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Redirect;
|
use axum::response::Redirect;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use std::sync::Arc;
|
|
||||||
// use axum::routing::get;
|
|
||||||
// use axum::routing::Router;
|
|
||||||
use super::not_found::not_found_html_response;
|
use super::not_found::not_found_html_response;
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
// use super::root::root;
|
|
||||||
// use super::utxo::utxo_page;
|
|
||||||
use crate::model::app_state::AppState;
|
use crate::model::app_state::AppState;
|
||||||
// use neptune_explorer::model::config::Config;
|
|
||||||
|
|
||||||
/// This converts a query string into a path and redirects browser.
|
/// This converts a query string into a path and redirects browser.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
use crate::html::page::not_found::not_found_html_response;
|
use std::sync::Arc;
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use crate::model::app_state::AppStateInner;
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use html_escaper::Escape;
|
use html_escaper::Escape;
|
||||||
use neptune_cash::models::blockchain::block::block_height::BlockHeight;
|
use neptune_cash::api::export::BlockHeight;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::html::page::not_found::not_found_html_response;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::app_state::AppStateInner;
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn root(State(state_rw): State<Arc<AppState>>) -> Result<Html<String>, Response> {
|
pub async fn root(State(state_rw): State<Arc<AppState>>) -> Result<Html<String>, Response> {
|
||||||
#[derive(boilerplate::Boilerplate)]
|
#[derive(boilerplate::Boilerplate)]
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
use crate::html::component::header::HeaderHtml;
|
use std::sync::Arc;
|
||||||
use crate::html::page::not_found::not_found_html_response;
|
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
|
||||||
use axum::extract::rejection::PathRejection;
|
use axum::extract::rejection::PathRejection;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
@ -12,9 +9,14 @@ use html_escaper::Escape;
|
|||||||
use html_escaper::Trusted;
|
use html_escaper::Trusted;
|
||||||
use neptune_cash::api::export::Tip5;
|
use neptune_cash::api::export::Tip5;
|
||||||
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::html::component::header::HeaderHtml;
|
||||||
|
use crate::html::page::not_found::not_found_html_response;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn utxo_page(
|
pub async fn utxo_page(
|
||||||
index_maybe: Result<Path<u64>, PathRejection>,
|
index_maybe: Result<Path<u64>, PathRejection>,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use axum::http::StatusCode;
|
|||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use neptune_cash::rpc_server::error::RpcError;
|
use neptune_cash::application::rpc::server::error::RpcError;
|
||||||
use tarpc::client::RpcError as TarpcError;
|
use tarpc::client::RpcError as TarpcError;
|
||||||
|
|
||||||
// note: http StatusCodes are defined at:
|
// note: http StatusCodes are defined at:
|
||||||
|
|||||||
@ -4,3 +4,4 @@ pub mod http_util;
|
|||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod neptune_rpc;
|
pub mod neptune_rpc;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
|
pub mod shared;
|
||||||
|
|||||||
@ -13,8 +13,10 @@ use neptune_explorer::model::app_state::AppState;
|
|||||||
use neptune_explorer::neptune_rpc;
|
use neptune_explorer::neptune_rpc;
|
||||||
use neptune_explorer::rpc::block_digest::block_digest;
|
use neptune_explorer::rpc::block_digest::block_digest;
|
||||||
use neptune_explorer::rpc::block_info::block_info;
|
use neptune_explorer::rpc::block_info::block_info;
|
||||||
|
use neptune_explorer::rpc::circulating_supply::circulating_supply;
|
||||||
use neptune_explorer::rpc::pow_puzzle::pow_puzzle;
|
use neptune_explorer::rpc::pow_puzzle::pow_puzzle;
|
||||||
use neptune_explorer::rpc::provide_pow_solution::provide_pow_solution;
|
use neptune_explorer::rpc::provide_pow_solution::provide_pow_solution;
|
||||||
|
use neptune_explorer::rpc::total_supply::total_supply;
|
||||||
use neptune_explorer::rpc::utxo_digest::utxo_digest;
|
use neptune_explorer::rpc::utxo_digest::utxo_digest;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@ -58,6 +60,8 @@ pub fn setup_routes(app_state: AppState) -> Router {
|
|||||||
.route("/rpc/block_digest/*selector", get(block_digest))
|
.route("/rpc/block_digest/*selector", get(block_digest))
|
||||||
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
.route("/rpc/utxo_digest/:index", get(utxo_digest))
|
||||||
.route("/rpc/pow_puzzle/*address", get(pow_puzzle))
|
.route("/rpc/pow_puzzle/*address", get(pow_puzzle))
|
||||||
|
.route("/rpc/circulating_supply", get(circulating_supply))
|
||||||
|
.route("/rpc/total_supply", get(total_supply))
|
||||||
.route("/rpc/provide_pow_solution", post(provide_pow_solution))
|
.route("/rpc/provide_pow_solution", post(provide_pow_solution))
|
||||||
// -- Dynamic HTML pages --
|
// -- Dynamic HTML pages --
|
||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use neptune_cash::api::export::BlockHeight;
|
use neptune_cash::api::export::BlockHeight;
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
|
|
||||||
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelector;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelectorLiteral;
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
|
/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
|
||||||
///
|
///
|
||||||
@ -58,11 +60,11 @@ impl FromStr for AnnouncementSelector {
|
|||||||
let (block_selector, index) = match parts.as_slice() {
|
let (block_selector, index) = match parts.as_slice() {
|
||||||
["tip", index] => {
|
["tip", index] => {
|
||||||
let index = index.parse::<u64>().map_err(Self::Err::TipIndex)?;
|
let index = index.parse::<u64>().map_err(Self::Err::TipIndex)?;
|
||||||
(BlockSelector::Tip, index)
|
(BlockSelector::Special(BlockSelectorLiteral::Tip), index)
|
||||||
}
|
}
|
||||||
["genesis", index] => index
|
["genesis", index] => index
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map(|i| (BlockSelector::Genesis, i))
|
.map(|i| (BlockSelector::Special(BlockSelectorLiteral::Genesis), i))
|
||||||
.map_err(Self::Err::GenesisIndex)?,
|
.map_err(Self::Err::GenesisIndex)?,
|
||||||
["height", number, index] => {
|
["height", number, index] => {
|
||||||
let height_as_u64 = number.parse::<u64>().map_err(Self::Err::BlockHeight)?;
|
let height_as_u64 = number.parse::<u64>().map_err(Self::Err::BlockHeight)?;
|
||||||
@ -128,8 +130,10 @@ impl Display for AnnouncementSelector {
|
|||||||
BlockSelector::Height(block_height) => {
|
BlockSelector::Height(block_height) => {
|
||||||
write!(f, "height/{}/{}", block_height, self.index)
|
write!(f, "height/{}/{}", block_height, self.index)
|
||||||
}
|
}
|
||||||
BlockSelector::Genesis => write!(f, "genesis/{}", self.index),
|
BlockSelector::Special(BlockSelectorLiteral::Genesis) => {
|
||||||
BlockSelector::Tip => write!(f, "tip/{}", self.index),
|
write!(f, "genesis/{}", self.index)
|
||||||
|
}
|
||||||
|
BlockSelector::Special(BlockSelectorLiteral::Tip) => write!(f, "tip/{}", self.index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,15 +151,18 @@ impl<'de> Deserialize<'de> for AnnouncementSelector {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
|
||||||
use proptest::string::string_regex;
|
|
||||||
use proptest::{prop_assert, prop_assert_eq};
|
|
||||||
use proptest_arbitrary_interop::arb;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use arbitrary::Arbitrary;
|
||||||
|
use arbitrary::Unstructured;
|
||||||
|
use proptest::prop_assert;
|
||||||
|
use proptest::prop_assert_eq;
|
||||||
|
use proptest::string::string_regex;
|
||||||
|
use proptest_arbitrary_interop::arb;
|
||||||
use test_strategy::proptest;
|
use test_strategy::proptest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
impl<'a> Arbitrary<'a> for AnnouncementSelector {
|
impl<'a> Arbitrary<'a> for AnnouncementSelector {
|
||||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||||
// Pick one of the variants randomly
|
// Pick one of the variants randomly
|
||||||
@ -185,7 +192,7 @@ mod tests {
|
|||||||
// Genesis selector
|
// Genesis selector
|
||||||
let index = u64::arbitrary(u)? as usize;
|
let index = u64::arbitrary(u)? as usize;
|
||||||
AnnouncementSelector {
|
AnnouncementSelector {
|
||||||
block_selector: BlockSelector::Genesis,
|
block_selector: BlockSelector::Special(BlockSelectorLiteral::Genesis),
|
||||||
index,
|
index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +200,7 @@ mod tests {
|
|||||||
// Tip selector
|
// Tip selector
|
||||||
let index = u64::arbitrary(u)? as usize;
|
let index = u64::arbitrary(u)? as usize;
|
||||||
AnnouncementSelector {
|
AnnouncementSelector {
|
||||||
block_selector: BlockSelector::Tip,
|
block_selector: BlockSelector::Special(BlockSelectorLiteral::Tip),
|
||||||
index,
|
index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
use crate::model::config::Config;
|
use std::sync::Arc;
|
||||||
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
|
||||||
use crate::neptune_rpc;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use neptune_cash::config_models::network::Network;
|
use neptune_cash::api::export::Network;
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
|
use neptune_cash::application::rpc::auth;
|
||||||
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
||||||
use neptune_cash::rpc_auth;
|
use neptune_cash::protocol::consensus::block::block_selector::{
|
||||||
use std::sync::Arc;
|
BlockSelector, BlockSelectorLiteral,
|
||||||
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::model::config::Config;
|
||||||
|
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
||||||
|
use crate::neptune_rpc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AppStateInner {
|
pub struct AppStateInner {
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
@ -26,7 +30,7 @@ pub struct AppStateInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppStateInner {
|
impl AppStateInner {
|
||||||
pub fn token(&self) -> rpc_auth::Token {
|
pub fn token(&self) -> auth::Token {
|
||||||
self.rpc_client.token
|
self.rpc_client.token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +61,7 @@ impl AppState {
|
|||||||
.block_digest(
|
.block_digest(
|
||||||
tarpc::context::current(),
|
tarpc::context::current(),
|
||||||
rpc_client.token,
|
rpc_client.token,
|
||||||
BlockSelector::Genesis,
|
BlockSelector::Special(BlockSelectorLiteral::Genesis),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed calling neptune-core api: block_digest")?
|
.with_context(|| "Failed calling neptune-core api: block_digest")?
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
use super::height_or_digest::HeightOrDigest;
|
use std::num::ParseIntError;
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
|
use std::str::FromStr;
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelectorParseError;
|
|
||||||
|
use neptune_cash::api::export::BlockHeight;
|
||||||
|
use neptune_cash::api::export::Digest;
|
||||||
|
use neptune_cash::prelude::triton_vm::prelude::BFieldElement;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelector;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelectorParseError;
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
use std::str::FromStr;
|
|
||||||
|
use super::height_or_digest::HeightOrDigest;
|
||||||
|
|
||||||
/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
|
/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
|
||||||
///
|
///
|
||||||
@ -27,17 +33,34 @@ impl FromStr for BlockSelectorExtended {
|
|||||||
|
|
||||||
// note: this parses BlockSelector, plus height_or_digest/<value>
|
// note: this parses BlockSelector, plus height_or_digest/<value>
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match BlockSelector::from_str(s) {
|
let res = match BlockSelector::from_str(s) {
|
||||||
Ok(bs) => Ok(Self::from(bs)),
|
Ok(bs) => Ok(Self::from(bs)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let parts: Vec<_> = s.split('/').collect();
|
let parts: Vec<_> = s.split('/').collect();
|
||||||
if parts.len() == 2 && parts[0] == "height_or_digest" {
|
if parts.len() == 2 {
|
||||||
|
if parts[0] == "height_or_digest" {
|
||||||
Ok(Self::from(HeightOrDigest::from_str(parts[1])?))
|
Ok(Self::from(HeightOrDigest::from_str(parts[1])?))
|
||||||
|
} else if parts[0] == "digest" {
|
||||||
|
Ok(Self(BlockSelector::Digest(
|
||||||
|
Digest::try_from_hex(parts[1]).map_err(|tfhde| {
|
||||||
|
BlockSelectorParseError::InvalidSelector(tfhde.to_string())
|
||||||
|
})?,
|
||||||
|
)))
|
||||||
|
} else if parts[0] == "height" {
|
||||||
|
Ok(Self(BlockSelector::Height(BlockHeight::new(
|
||||||
|
BFieldElement::new(parts[1].parse().map_err(|e: ParseIntError| {
|
||||||
|
BlockSelectorParseError::InvalidSelector(e.to_string())
|
||||||
|
})?),
|
||||||
|
))))
|
||||||
|
} else {
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
use neptune_cash::models::blockchain::block::block_height::BlockHeight;
|
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
|
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelectorParseError;
|
|
||||||
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use neptune_cash::api::export::BlockHeight;
|
||||||
|
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelector;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelectorParseError;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
/// represents either a block-height or a block digest
|
/// represents either a block-height or a block digest
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum HeightOrDigest {
|
pub enum HeightOrDigest {
|
||||||
@ -32,7 +34,11 @@ impl FromStr for HeightOrDigest {
|
|||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(match s.parse::<u64>() {
|
Ok(match s.parse::<u64>() {
|
||||||
Ok(h) => Self::Height(h.into()),
|
Ok(h) => Self::Height(h.into()),
|
||||||
Err(_) => Self::Digest(Digest::try_from_hex(s)?),
|
Err(_) => {
|
||||||
|
let digest = Digest::try_from_hex(s)
|
||||||
|
.map_err(|_| BlockSelectorParseError::InvalidSelector(s.to_string()))?;
|
||||||
|
Self::Digest(digest)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,36 @@
|
|||||||
use crate::alert_email;
|
use std::net::Ipv4Addr;
|
||||||
use crate::model::app_state::AppState;
|
use std::net::SocketAddr;
|
||||||
use crate::model::config::Config;
|
use std::sync::Arc;
|
||||||
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use neptune_cash::api::export::Announcement;
|
use neptune_cash::api::export::Announcement;
|
||||||
use neptune_cash::config_models::data_directory::DataDirectory;
|
use neptune_cash::api::export::Network;
|
||||||
use neptune_cash::config_models::network::Network;
|
use neptune_cash::application::config::data_directory::DataDirectory;
|
||||||
use neptune_cash::models::blockchain::block::block_height::BlockHeight;
|
use neptune_cash::application::rpc::auth;
|
||||||
use neptune_cash::models::blockchain::block::block_info::BlockInfo;
|
use neptune_cash::application::rpc::server::error::RpcError;
|
||||||
use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
|
use neptune_cash::application::rpc::server::RPCClient;
|
||||||
|
use neptune_cash::application::rpc::server::RpcResult;
|
||||||
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
use neptune_cash::prelude::tasm_lib::prelude::Digest;
|
||||||
use neptune_cash::rpc_auth;
|
use neptune_cash::protocol::consensus::block::block_height::BlockHeight;
|
||||||
use neptune_cash::rpc_server::error::RpcError;
|
use neptune_cash::protocol::consensus::block::block_info::BlockInfo;
|
||||||
use neptune_cash::rpc_server::RPCClient;
|
use neptune_cash::protocol::consensus::block::block_selector::BlockSelector;
|
||||||
use neptune_cash::rpc_server::RpcResult;
|
|
||||||
use neptune_cash::util_types::mutator_set::addition_record::AdditionRecord;
|
use neptune_cash::util_types::mutator_set::addition_record::AdditionRecord;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::client;
|
use tarpc::client;
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
use tarpc::tokio_serde::formats::Json as RpcJson;
|
use tarpc::tokio_serde::formats::Json as RpcJson;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::debug;
|
||||||
|
use tracing::info;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::alert_email;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::config::Config;
|
||||||
|
use crate::model::transparent_utxo_tuple::TransparentUtxoTuple;
|
||||||
|
|
||||||
#[cfg(feature = "mock")]
|
#[cfg(feature = "mock")]
|
||||||
const MOCK_KEY: &str = "MOCK";
|
const MOCK_KEY: &str = "MOCK";
|
||||||
@ -35,7 +38,7 @@ const MOCK_KEY: &str = "MOCK";
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AuthenticatedClient {
|
pub struct AuthenticatedClient {
|
||||||
pub client: RPCClient,
|
pub client: RPCClient,
|
||||||
pub token: rpc_auth::Token,
|
pub token: auth::Token,
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +55,7 @@ impl AuthenticatedClient {
|
|||||||
pub async fn block_info(
|
pub async fn block_info(
|
||||||
&self,
|
&self,
|
||||||
ctx: ::tarpc::context::Context,
|
ctx: ::tarpc::context::Context,
|
||||||
token: rpc_auth::Token,
|
token: auth::Token,
|
||||||
block_selector: BlockSelector,
|
block_selector: BlockSelector,
|
||||||
) -> ::core::result::Result<RpcResult<Option<BlockInfo>>, ::tarpc::client::RpcError> {
|
) -> ::core::result::Result<RpcResult<Option<BlockInfo>>, ::tarpc::client::RpcError> {
|
||||||
let rpc_result = self.client.block_info(ctx, token, block_selector).await;
|
let rpc_result = self.client.block_info(ctx, token, block_selector).await;
|
||||||
@ -95,7 +98,7 @@ impl AuthenticatedClient {
|
|||||||
pub async fn utxo_digest(
|
pub async fn utxo_digest(
|
||||||
&self,
|
&self,
|
||||||
ctx: ::tarpc::context::Context,
|
ctx: ::tarpc::context::Context,
|
||||||
token: rpc_auth::Token,
|
token: auth::Token,
|
||||||
leaf_index: u64,
|
leaf_index: u64,
|
||||||
_transparent_utxos_cache: Arc<Mutex<Vec<TransparentUtxoTuple>>>,
|
_transparent_utxos_cache: Arc<Mutex<Vec<TransparentUtxoTuple>>>,
|
||||||
) -> ::core::result::Result<RpcResult<Option<Digest>>, ::tarpc::client::RpcError> {
|
) -> ::core::result::Result<RpcResult<Option<Digest>>, ::tarpc::client::RpcError> {
|
||||||
@ -125,7 +128,7 @@ impl AuthenticatedClient {
|
|||||||
pub async fn announcements_in_block(
|
pub async fn announcements_in_block(
|
||||||
&self,
|
&self,
|
||||||
ctx: ::tarpc::context::Context,
|
ctx: ::tarpc::context::Context,
|
||||||
token: rpc_auth::Token,
|
token: auth::Token,
|
||||||
block_selector: BlockSelector,
|
block_selector: BlockSelector,
|
||||||
) -> Result<Result<Option<Vec<Announcement>>, RpcError>, ::tarpc::client::RpcError> {
|
) -> Result<Result<Option<Vec<Announcement>>, RpcError>, ::tarpc::client::RpcError> {
|
||||||
let rpc_result = self
|
let rpc_result = self
|
||||||
@ -191,11 +194,11 @@ impl AuthenticatedClient {
|
|||||||
pub async fn addition_record_indices_for_block(
|
pub async fn addition_record_indices_for_block(
|
||||||
&self,
|
&self,
|
||||||
ctx: ::tarpc::context::Context,
|
ctx: ::tarpc::context::Context,
|
||||||
token: rpc_auth::Token,
|
token: auth::Token,
|
||||||
block_selector: BlockSelector,
|
block_selector: BlockSelector,
|
||||||
_addition_records: &[AdditionRecord],
|
_addition_records: &[AdditionRecord],
|
||||||
) -> ::core::result::Result<
|
) -> ::core::result::Result<
|
||||||
RpcResult<Option<HashMap<AdditionRecord, Option<u64>>>>,
|
RpcResult<Vec<(AdditionRecord, Option<u64>)>>,
|
||||||
::tarpc::client::RpcError,
|
::tarpc::client::RpcError,
|
||||||
> {
|
> {
|
||||||
let rpc_result = self
|
let rpc_result = self
|
||||||
@ -204,7 +207,7 @@ impl AuthenticatedClient {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// if the RPC call was successful, return that
|
// if the RPC call was successful, return that
|
||||||
if let Ok(Ok(Some(_))) = rpc_result {
|
if let Ok(Ok(_)) = rpc_result {
|
||||||
return rpc_result;
|
return rpc_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,8 +240,8 @@ impl AuthenticatedClient {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<Vec<(_, _)>>();
|
||||||
return Ok(Ok(Some(addition_record_indices)));
|
return Ok(Ok(addition_record_indices));
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, return the original error
|
// otherwise, return the original error
|
||||||
@ -250,12 +253,12 @@ impl AuthenticatedClient {
|
|||||||
pub async fn gen_authenticated_rpc_client() -> Result<AuthenticatedClient, anyhow::Error> {
|
pub async fn gen_authenticated_rpc_client() -> Result<AuthenticatedClient, anyhow::Error> {
|
||||||
let client = gen_rpc_client().await?;
|
let client = gen_rpc_client().await?;
|
||||||
|
|
||||||
let rpc_auth::CookieHint {
|
let auth::CookieHint {
|
||||||
data_directory,
|
data_directory,
|
||||||
network,
|
network,
|
||||||
} = get_cookie_hint(&client, &None).await?;
|
} = get_cookie_hint(&client, &None).await?;
|
||||||
|
|
||||||
let token: rpc_auth::Token = rpc_auth::Cookie::try_load(&data_directory).await?.into();
|
let token: auth::Token = auth::Cookie::try_load(&data_directory).await?.into();
|
||||||
|
|
||||||
Ok(AuthenticatedClient {
|
Ok(AuthenticatedClient {
|
||||||
client,
|
client,
|
||||||
@ -289,14 +292,14 @@ pub async fn gen_rpc_client() -> Result<RPCClient, anyhow::Error> {
|
|||||||
async fn get_cookie_hint(
|
async fn get_cookie_hint(
|
||||||
client: &RPCClient,
|
client: &RPCClient,
|
||||||
data_dir: &Option<std::path::PathBuf>,
|
data_dir: &Option<std::path::PathBuf>,
|
||||||
) -> anyhow::Result<rpc_auth::CookieHint> {
|
) -> anyhow::Result<auth::CookieHint> {
|
||||||
async fn fallback(
|
async fn fallback(
|
||||||
client: &RPCClient,
|
client: &RPCClient,
|
||||||
data_dir: &Option<std::path::PathBuf>,
|
data_dir: &Option<std::path::PathBuf>,
|
||||||
) -> anyhow::Result<rpc_auth::CookieHint> {
|
) -> anyhow::Result<auth::CookieHint> {
|
||||||
let network = client.network(context::current()).await??;
|
let network = client.network(context::current()).await??;
|
||||||
let data_directory = DataDirectory::get(data_dir.to_owned(), network)?;
|
let data_directory = DataDirectory::get(data_dir.to_owned(), network)?;
|
||||||
Ok(rpc_auth::CookieHint {
|
Ok(auth::CookieHint {
|
||||||
data_directory,
|
data_directory,
|
||||||
network,
|
network,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
use crate::http_util::not_found_err;
|
use std::sync::Arc;
|
||||||
use crate::http_util::rpc_err;
|
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use crate::model::block_selector_extended::BlockSelectorExtended;
|
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Json;
|
use axum::response::Json;
|
||||||
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::http_util::not_found_err;
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_digest(
|
pub async fn block_digest(
|
||||||
Path(selector): Path<BlockSelectorExtended>,
|
Path(selector): Path<BlockSelectorExtended>,
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::response::Json;
|
||||||
|
use axum::response::Response;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_info::BlockInfo;
|
||||||
|
use tarpc::context;
|
||||||
|
|
||||||
use crate::http_util::not_found_err;
|
use crate::http_util::not_found_err;
|
||||||
use crate::http_util::rpc_err;
|
use crate::http_util::rpc_err;
|
||||||
use crate::http_util::rpc_method_err;
|
use crate::http_util::rpc_method_err;
|
||||||
use crate::model::app_state::AppState;
|
use crate::model::app_state::AppState;
|
||||||
use crate::model::block_selector_extended::BlockSelectorExtended;
|
use crate::model::block_selector_extended::BlockSelectorExtended;
|
||||||
use axum::extract::Path;
|
|
||||||
use axum::extract::State;
|
|
||||||
use axum::response::Json;
|
|
||||||
use axum::response::Response;
|
|
||||||
use neptune_cash::models::blockchain::block::block_info::BlockInfo;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn block_info(
|
pub async fn block_info(
|
||||||
|
|||||||
30
src/rpc/circulating_supply.rs
Normal file
30
src/rpc/circulating_supply.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::response::Json;
|
||||||
|
use axum::response::Response;
|
||||||
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::shared::monetary_supplies;
|
||||||
|
|
||||||
|
/// Return the current monetary amount that is liquid, assuming all redemptions
|
||||||
|
/// on the old chain have successfully been made. Returned unit is in number of
|
||||||
|
/// coins. To convert to number of nau, multiply by $4*10^{30}$/
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn circulating_supply(State(state): State<Arc<AppState>>) -> Result<Json<i32>, Response> {
|
||||||
|
let s = state.load();
|
||||||
|
|
||||||
|
let block_height = s
|
||||||
|
.rpc_client
|
||||||
|
.block_height(context::current(), s.token())
|
||||||
|
.await
|
||||||
|
.map_err(rpc_err)?
|
||||||
|
.map_err(rpc_method_err)?;
|
||||||
|
|
||||||
|
let (liquid_supply, _) = monetary_supplies(block_height);
|
||||||
|
|
||||||
|
Ok(Json(liquid_supply.ceil_num_whole_coins()))
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
pub mod block_digest;
|
pub mod block_digest;
|
||||||
pub mod block_info;
|
pub mod block_info;
|
||||||
|
pub mod circulating_supply;
|
||||||
pub mod pow_puzzle;
|
pub mod pow_puzzle;
|
||||||
pub mod provide_pow_solution;
|
pub mod provide_pow_solution;
|
||||||
|
pub mod total_supply;
|
||||||
pub mod utxo_digest;
|
pub mod utxo_digest;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Json;
|
use axum::response::Json;
|
||||||
use neptune_cash::models::state::wallet::address::generation_address::GenerationReceivingAddress;
|
use neptune_cash::application::rpc::server::error::RpcError;
|
||||||
use neptune_cash::rpc_server::error::RpcError;
|
use neptune_cash::application::rpc::server::proof_of_work_puzzle::ProofOfWorkPuzzle;
|
||||||
use neptune_cash::rpc_server::ProofOfWorkPuzzle;
|
use neptune_cash::state::wallet::address::generation_address::GenerationReceivingAddress;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
use crate::http_util::not_found_err;
|
use crate::http_util::not_found_err;
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
use crate::http_util::rpc_err;
|
use std::sync::Arc;
|
||||||
use crate::http_util::rpc_method_err;
|
|
||||||
use crate::model::app_state::AppState;
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::Json;
|
use axum::response::Json;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use neptune_cash::models::blockchain::block::block_header::BlockPow;
|
|
||||||
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_header::BlockPow;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PowSolution {
|
pub struct PowSolution {
|
||||||
pow: BlockPow,
|
pow: BlockPow,
|
||||||
|
|||||||
31
src/rpc/total_supply.rs
Normal file
31
src/rpc/total_supply.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::response::Json;
|
||||||
|
use axum::response::Response;
|
||||||
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
|
use crate::http_util::rpc_method_err;
|
||||||
|
use crate::model::app_state::AppState;
|
||||||
|
use crate::shared::monetary_supplies;
|
||||||
|
|
||||||
|
/// Return the current total monetary supply, the sum of the timeloced and
|
||||||
|
/// liquid supply. Assumes all redemptions on the old chain have successfully
|
||||||
|
/// been made. Returned unit is in number of coins. To convert to number of
|
||||||
|
/// nau, multiply by $4*10^{30}$.
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn total_supply(State(state): State<Arc<AppState>>) -> Result<Json<i32>, Response> {
|
||||||
|
let s = state.load();
|
||||||
|
|
||||||
|
let block_height = s
|
||||||
|
.rpc_client
|
||||||
|
.block_height(context::current(), s.token())
|
||||||
|
.await
|
||||||
|
.map_err(rpc_err)?
|
||||||
|
.map_err(rpc_method_err)?;
|
||||||
|
|
||||||
|
let (_, total_supply) = monetary_supplies(block_height);
|
||||||
|
|
||||||
|
Ok(Json(total_supply.ceil_num_whole_coins()))
|
||||||
|
}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Json;
|
use axum::response::Json;
|
||||||
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
use neptune_cash::prelude::twenty_first::tip5::Digest;
|
||||||
use std::sync::Arc;
|
|
||||||
use tarpc::context;
|
use tarpc::context;
|
||||||
|
|
||||||
|
use crate::http_util::not_found_err;
|
||||||
|
use crate::http_util::rpc_err;
|
||||||
use crate::http_util::rpc_method_err;
|
use crate::http_util::rpc_method_err;
|
||||||
use crate::{
|
use crate::model::app_state::AppState;
|
||||||
http_util::{not_found_err, rpc_err},
|
|
||||||
model::app_state::AppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn utxo_digest(
|
pub async fn utxo_digest(
|
||||||
|
|||||||
60
src/shared.rs
Normal file
60
src/shared.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use neptune_cash::api::export::BlockHeight;
|
||||||
|
use neptune_cash::api::export::NativeCurrencyAmount;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_height::BLOCKS_PER_GENERATION;
|
||||||
|
use neptune_cash::protocol::consensus::block::block_height::NUM_BLOCKS_SKIPPED_BECAUSE_REBOOT;
|
||||||
|
use neptune_cash::protocol::consensus::block::Block;
|
||||||
|
use neptune_cash::protocol::consensus::block::PREMINE_MAX_SIZE;
|
||||||
|
|
||||||
|
/// Return the pair (liquid supply, total supply)
|
||||||
|
///
|
||||||
|
/// Assumes all redemption claims have been rewarded.
|
||||||
|
pub(crate) fn monetary_supplies(
|
||||||
|
block_height: BlockHeight,
|
||||||
|
) -> (NativeCurrencyAmount, NativeCurrencyAmount) {
|
||||||
|
let block_height: u64 = block_height.into();
|
||||||
|
let generation_0_subsidy = Block::block_subsidy(BlockHeight::genesis().next());
|
||||||
|
let effective_block_height = block_height + NUM_BLOCKS_SKIPPED_BECAUSE_REBOOT;
|
||||||
|
let (num_generations, num_blocks_in_curr_gen): (u64, u32) = (
|
||||||
|
effective_block_height / BLOCKS_PER_GENERATION,
|
||||||
|
(effective_block_height % BLOCKS_PER_GENERATION)
|
||||||
|
.try_into()
|
||||||
|
.expect("There are fewer than u32::MAX blocks per generation"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut liquid_supply = PREMINE_MAX_SIZE;
|
||||||
|
let mut liquid_subsidy = generation_0_subsidy.half();
|
||||||
|
let mut total_supply = PREMINE_MAX_SIZE;
|
||||||
|
let blocks_per_generation: u32 = BLOCKS_PER_GENERATION
|
||||||
|
.try_into()
|
||||||
|
.expect("There are fewer than u32::MAX blocks per generation");
|
||||||
|
for _ in 0..num_generations {
|
||||||
|
liquid_supply += liquid_subsidy.scalar_mul(blocks_per_generation);
|
||||||
|
total_supply += liquid_subsidy.scalar_mul(2);
|
||||||
|
liquid_subsidy = liquid_subsidy.half();
|
||||||
|
}
|
||||||
|
|
||||||
|
let liquid_supply_current_generation = liquid_subsidy.scalar_mul(num_blocks_in_curr_gen);
|
||||||
|
liquid_supply += liquid_supply_current_generation;
|
||||||
|
total_supply += liquid_supply_current_generation.scalar_mul(2);
|
||||||
|
|
||||||
|
// How much of timelocked miner rewards have been unlocked? Assume that the
|
||||||
|
// timelock is exactly one generation long. In reality the timelock is
|
||||||
|
// is defined in relation to timestamp and not block heights, so this is
|
||||||
|
// only a (good) approximation.
|
||||||
|
|
||||||
|
let mut released_subsidy = generation_0_subsidy.half();
|
||||||
|
for _ in 1..num_generations {
|
||||||
|
liquid_supply += released_subsidy.scalar_mul(blocks_per_generation);
|
||||||
|
released_subsidy = released_subsidy.half();
|
||||||
|
}
|
||||||
|
|
||||||
|
if num_generations > 0 {
|
||||||
|
liquid_supply += released_subsidy.scalar_mul(num_blocks_in_curr_gen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you want correct results for anything but main net, and claims are
|
||||||
|
// only being refunded on main net, the size of the claims pool must be
|
||||||
|
// subtracted here, if that the network is not main net.
|
||||||
|
|
||||||
|
(liquid_supply, total_supply)
|
||||||
|
}
|
||||||
@ -147,6 +147,10 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="container" style="margin-top: 2em; font-size: 0.9em; text-align: center;">
|
||||||
|
<a href="https://github.com/Neptune-Crypto/neptune-explorer" target="_blank">Source code</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user