#![forbid(unsafe_code)]
#[macro_use]
extern crate tracing;
mod helpers;
pub use helpers::*;
mod routes;
use amareleo_node_consensus::Consensus;
use snarkvm::{
console::{program::ProgramID, types::Field},
prelude::{Ledger, Network, cfg_into_iter, store::ConsensusStorage},
};
use anyhow::Result;
use axum::{
Json,
body::Body,
extract::{ConnectInfo, DefaultBodyLimit, Path, Query, State},
http::{Method, Request, StatusCode, header::CONTENT_TYPE},
middleware,
middleware::Next,
response::Response,
routing::{get, post},
};
use axum_extra::response::ErasedJson;
use parking_lot::Mutex;
use std::{net::SocketAddr, sync::Arc};
use tokio::{net::TcpListener, task::JoinHandle};
use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder};
use tower_http::{
cors::{Any, CorsLayer},
trace::TraceLayer,
};
#[derive(Clone)]
pub struct Rest<N: Network, C: ConsensusStorage<N>> {
consensus: Option<Consensus<N>>,
ledger: Ledger<N, C>,
handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
impl<N: Network, C: 'static + ConsensusStorage<N>> Rest<N, C> {
pub async fn start(
rest_ip: SocketAddr,
rest_rps: u32,
consensus: Option<Consensus<N>>,
ledger: Ledger<N, C>,
) -> Result<Self> {
let mut server = Self { consensus, ledger, handles: Default::default() };
server.spawn_server(rest_ip, rest_rps).await;
Ok(server)
}
}
impl<N: Network, C: ConsensusStorage<N>> Rest<N, C> {
pub const fn ledger(&self) -> &Ledger<N, C> {
&self.ledger
}
pub const fn handles(&self) -> &Arc<Mutex<Vec<JoinHandle<()>>>> {
&self.handles
}
}
impl<N: Network, C: ConsensusStorage<N>> Rest<N, C> {
async fn spawn_server(&mut self, rest_ip: SocketAddr, rest_rps: u32) {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
.allow_headers([CONTENT_TYPE]);
debug!("REST rate limit per IP - {rest_rps} RPS");
let governor_config = Box::new(
GovernorConfigBuilder::default()
.per_nanosecond((1_000_000_000 / rest_rps) as u64)
.burst_size(rest_rps)
.error_handler(|error| {
let error_message = error.to_string();
let mut response = Response::new(error_message.clone().into());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
if error_message.contains("Too Many Requests") {
*response.status_mut() = StatusCode::TOO_MANY_REQUESTS;
}
response
})
.finish()
.expect("Couldn't set up rate limiting for the REST server!"),
);
let network = match N::ID {
snarkvm::console::network::MainnetV0::ID => "mainnet",
snarkvm::console::network::TestnetV0::ID => "testnet",
snarkvm::console::network::CanaryV0::ID => "canary",
unknown_id => {
eprintln!("Unknown network ID ({unknown_id})");
return;
}
};
let router = {
let routes = axum::Router::new()
.route(
&format!("/{network}/node/address"),
get(Self::get_node_address),
)
.route(
&format!("/{network}/program/:id/mapping/:name"),
get(Self::get_mapping_values),
)
.route_layer(middleware::from_fn(auth_middleware))
.route(
&format!("/{network}/block/height/latest"),
get(Self::get_block_height_latest),
)
.route(
&format!("/{network}/block/hash/latest"),
get(Self::get_block_hash_latest),
)
.route(
&format!("/{network}/block/latest"),
get(Self::get_block_latest),
)
.route(
&format!("/{network}/block/:height_or_hash"),
get(Self::get_block),
)
.route(
&format!("/{network}/block/:height_or_hash/transactions"),
get(Self::get_block_transactions),
)
.route(
&format!("/{network}/transaction/:id"),
get(Self::get_transaction),
)
.route(
&format!("/{network}/transaction/confirmed/:id"),
get(Self::get_confirmed_transaction),
)
.route(
&format!("/{network}/transaction/broadcast"),
post(Self::transaction_broadcast),
)
.route(
&format!("/{network}/solution/broadcast"),
post(Self::solution_broadcast),
)
.route(
&format!("/{network}/find/blockHash/:tx_id"),
get(Self::find_block_hash),
)
.route(
&format!("/{network}/find/blockHeight/:state_root"),
get(Self::find_block_height_from_state_root),
)
.route(
&format!("/{network}/find/transactionID/deployment/:program_id"),
get(Self::find_transaction_id_from_program_id),
)
.route(
&format!("/{network}/find/transactionID/:transition_id"),
get(Self::find_transaction_id_from_transition_id),
)
.route(
&format!("/{network}/find/transitionID/:input_or_output_id"),
get(Self::find_transition_id),
)
.route(
&format!("/{network}/peers/count"),
get(Self::get_peers_count),
)
.route(&format!("/{network}/peers/all"), get(Self::get_peers_all))
.route(
&format!("/{network}/peers/all/metrics"),
get(Self::get_peers_all_metrics),
)
.route(&format!("/{network}/program/:id"), get(Self::get_program))
.route(
&format!("/{network}/program/:id/mappings"),
get(Self::get_mapping_names),
)
.route(
&format!("/{network}/program/:id/mapping/:name/:key"),
get(Self::get_mapping_value),
)
.route(&format!("/{network}/blocks"), get(Self::get_blocks))
.route(&format!("/{network}/height/:hash"), get(Self::get_height))
.route(
&format!("/{network}/memoryPool/transmissions"),
get(Self::get_memory_pool_transmissions),
)
.route(
&format!("/{network}/memoryPool/solutions"),
get(Self::get_memory_pool_solutions),
)
.route(
&format!("/{network}/memoryPool/transactions"),
get(Self::get_memory_pool_transactions),
)
.route(
&format!("/{network}/statePath/:commitment"),
get(Self::get_state_path_for_commitment),
)
.route(
&format!("/{network}/stateRoot/latest"),
get(Self::get_state_root_latest),
)
.route(
&format!("/{network}/stateRoot/:height"),
get(Self::get_state_root),
)
.route(
&format!("/{network}/committee/latest"),
get(Self::get_committee_latest),
)
.route(
&format!("/{network}/committee/:height"),
get(Self::get_committee),
)
.route(
&format!("/{network}/delegators/:validator"),
get(Self::get_delegators_for_validator),
);
#[cfg(feature = "history")]
let routes =
routes.route(&format!("/{network}/block/:blockHeight/history/:mapping"), get(Self::get_history));
routes
.with_state(self.clone())
.layer(TraceLayer::new_for_http())
.layer(middleware::from_fn(log_middleware))
.layer(cors)
.layer(DefaultBodyLimit::max(512 * 1024))
.layer(GovernorLayer {
config: Box::leak(governor_config),
})
};
let rest_listener = TcpListener::bind(rest_ip).await.unwrap();
self.handles.lock().push(tokio::spawn(async move {
axum::serve(rest_listener, router.into_make_service_with_connect_info::<SocketAddr>())
.await
.expect("couldn't start rest server");
}))
}
}
async fn log_middleware(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
request: Request<Body>,
next: Next,
) -> Result<Response, StatusCode> {
info!("Received '{} {}' from '{addr}'", request.method(), request.uri());
Ok(next.run(request).await)
}
pub fn fmt_id(id: impl ToString) -> String {
let id = id.to_string();
let mut formatted_id = id.chars().take(16).collect::<String>();
if id.chars().count() > 16 {
formatted_id.push_str("..");
}
formatted_id
}