#![forbid(unsafe_code)]
mod helpers;
use helpers::*;
mod routes;
use snarkvm::{
console::{program::ProgramID, types::Field},
prelude::{Ledger, Network, PrivateKey, Transaction, store::ConsensusStorage},
};
use super::*;
use anyhow::Context;
use axum::{
body::Body,
extract::{ConnectInfo, DefaultBodyLimit, Query, State},
http::{Method, Request, StatusCode, header::CONTENT_TYPE},
middleware,
response::Response,
routing::{get, post},
};
use axum_extra::response::ErasedJson;
use parking_lot::Mutex;
use std::{
net::SocketAddr,
sync::{Arc, atomic::AtomicUsize},
};
use tokio::{net::TcpListener, task::JoinHandle};
use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder};
use tower_http::{
cors::{Any, CorsLayer},
trace::TraceLayer,
};
use tracing::{debug, info};
pub const API_VERSION_V1: &str = "v1";
pub const API_VERSION_V2: &str = "v2";
#[derive(Clone)]
pub struct Rest<N: Network, C: ConsensusStorage<N>> {
ledger: Ledger<N, C>,
buffer: Arc<Mutex<Vec<Transaction<N>>>>,
handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
num_verifying_deploys: Arc<AtomicUsize>,
num_verifying_executions: Arc<AtomicUsize>,
manual_block_creation: bool,
private_key: PrivateKey<N>,
}
impl<N: Network, C: 'static + ConsensusStorage<N>> Rest<N, C> {
pub async fn start(
rest_ip: SocketAddr,
rest_rps: u32,
ledger: Ledger<N, C>,
manual_block_creation: bool,
private_key: PrivateKey<N>,
) -> Result<Self> {
let mut server = Self {
ledger,
buffer: Arc::new(Mutex::new(Vec::new())),
handles: Default::default(),
num_verifying_deploys: Default::default(),
num_verifying_executions: Default::default(),
manual_block_creation,
private_key,
};
server.spawn_server(rest_ip, rest_rps).await?;
Ok(server)
}
}
impl<N: Network, C: ConsensusStorage<N>> Rest<N, C> {
fn build_routes(&self, rest_rps: u32) -> axum::Router {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
.allow_headers([CONTENT_TYPE]);
let governor_config = GovernorConfigBuilder::default()
.per_nanosecond((1_000_000_000 / rest_rps) as u64)
.burst_size(rest_rps)
.finish()
.expect("Couldn't set up rate limiting for the REST server!");
let governor_layer =
GovernorLayer::new(governor_config).error_handler(|error: tower_governor::errors::GovernorError| {
let error_message = error.to_string();
let mut response = axum::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
});
let routes = axum::Router::new()
.route("/consensus_version", get(Self::get_consensus_version))
.route("/block/height/latest", get(Self::get_block_height_latest))
.route("/block/hash/latest", get(Self::get_block_hash_latest))
.route("/block/latest", get(Self::get_block_latest))
.route("/block/{height_or_hash}", get(Self::get_block))
.route("/block/{height_or_hash}/header", get(Self::get_block_header))
.route("/block/{height_or_hash}/transactions", get(Self::get_block_transactions))
.route("/block/create", post(Self::create_block))
.route("/transaction/broadcast", post(Self::transaction_broadcast))
.route("/transaction/confirmed/{id}", get(Self::get_confirmed_transaction))
.route("/transaction/unconfirmed/{id}", get(Self::get_unconfirmed_transaction))
.route("/transaction/{id}", get(Self::get_transaction))
.route("/find/blockHash/{tx_id}", get(Self::find_block_hash))
.route("/find/blockHeight/{state_root}", get(Self::find_block_height_from_state_root))
.route("/find/transactionID/deployment/{program_id}", get(Self::find_latest_transaction_id_from_program_id))
.route("/find/transactionID/deployment/{program_id}/{edition}", get(Self::find_transaction_id_from_program_id_and_edition))
.route("/find/transactionID/{transition_id}", get(Self::find_transaction_id_from_transition_id))
.route("/find/transitionID/{input_or_output_id}", get(Self::find_transition_id))
.route("/program/{id}", get(Self::get_program))
.route("/program/{id}/latest_edition", get(Self::get_latest_program_edition))
.route("/program/{id}/{edition}", get(Self::get_program_for_edition))
.route("/program/{id}/mappings", get(Self::get_mapping_names))
.route("/program/{id}/mapping/{name}/{key}", get(Self::get_mapping_value))
.route("/program/{id}/mapping/{name}", get(Self::get_mapping_values))
.route("/program/{id}/view/{function}", post(Self::evaluate_view_latest))
.route("/program/{id}/view/{function}/{height}", post(Self::evaluate_view))
.route("/blocks", get(Self::get_blocks))
.route("/height/{hash}", get(Self::get_height))
.route("/statePath/{commitment}", get(Self::get_state_path_for_commitment))
.route("/statePaths", get(Self::get_state_paths_for_commitments))
.route("/stateRoot/latest", get(Self::get_state_root_latest))
.route("/stateRoot/{height}", get(Self::get_state_root));
routes
.with_state(self.clone())
.layer(TraceLayer::new_for_http())
.layer(middleware::map_request(log_middleware))
.layer(cors)
.layer(DefaultBodyLimit::max(2 * 768 * 1024))
.layer(governor_layer)
}
async fn spawn_server(&mut self, rest_ip: SocketAddr, rest_rps: u32) -> Result<()> {
debug!("REST rate limit per IP - {rest_rps} RPS");
let default_router = axum::Router::new().nest(
&format!("/{}", N::SHORT_NAME),
self.build_routes(rest_rps).layer(middleware::map_response(v1_error_middleware)),
);
let v1_router = axum::Router::new().nest(
&format!("/{API_VERSION_V1}/{}", N::SHORT_NAME),
self.build_routes(rest_rps).layer(middleware::map_response(v1_error_middleware)),
);
let v2_router =
axum::Router::new().nest(&format!("/{API_VERSION_V2}/{}", N::SHORT_NAME), self.build_routes(rest_rps));
let router = default_router.merge(v1_router).merge(v2_router);
let rest_listener =
TcpListener::bind(rest_ip).await.with_context(|| "Failed to bind TCP port for REST endpoints")?;
let handle = tokio::spawn(async move {
if let Err(e) = axum::serve(rest_listener, router.into_make_service_with_connect_info::<SocketAddr>()).await
{
eprintln!("REST server crashed: {e:?}");
}
});
self.handles.lock().push(handle);
Ok(())
}
}
async fn log_middleware(ConnectInfo(addr): ConnectInfo<SocketAddr>, request: Request<Body>) -> Request<Body> {
info!("Received {:?} {:?} from {:?}", request.method(), request.uri(), addr);
request
}
async fn v1_error_middleware(response: Response) -> Response {
const V1_STATUS_CODE: StatusCode = StatusCode::INTERNAL_SERVER_ERROR;
if response.status().is_success() {
return response;
}
let fallback = || {
let mut response = Response::new(Body::from("Failed to convert error"));
*response.status_mut() = V1_STATUS_CODE;
response
};
let Ok(bytes) = axum::body::to_bytes(response.into_body(), usize::MAX).await else {
return fallback();
};
let Ok(json_err) = serde_json::from_slice::<SerializedRestError>(&bytes) else {
return fallback();
};
let mut message = json_err.message;
for next in json_err.chain.into_iter() {
message = format!("{message} — {next}");
}
let mut response = Response::new(Body::from(message));
*response.status_mut() = V1_STATUS_CODE;
response
}