#![forbid(unsafe_code)]
#[macro_use]
extern crate tracing;
mod helpers;
pub use helpers::*;
mod routes;
use snarkos_node_consensus::Consensus;
use snarkos_node_router::{
messages::{Message, UnconfirmedTransaction},
Routing,
};
use snarkvm::{
console::{program::ProgramID, types::Field},
ledger::narwhal::Data,
prelude::{cfg_into_iter, store::ConsensusStorage, Ledger, Network},
};
use anyhow::Result;
use axum::{
extract::{ConnectInfo, DefaultBodyLimit, Path, Query, State},
http::{header::CONTENT_TYPE, Method, Request, StatusCode},
middleware,
middleware::Next,
response::Response,
routing::{get, post},
Json,
};
use axum_extra::response::ErasedJson;
use parking_lot::Mutex;
use std::{net::SocketAddr, sync::Arc};
use tokio::task::JoinHandle;
use tower_http::{
cors::{Any, CorsLayer},
trace::TraceLayer,
};
#[derive(Clone)]
pub struct Rest<N: Network, C: ConsensusStorage<N>, R: Routing<N>> {
consensus: Option<Consensus<N>>,
ledger: Ledger<N, C>,
routing: Arc<R>,
handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
impl<N: Network, C: 'static + ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
pub fn start(
rest_ip: SocketAddr,
consensus: Option<Consensus<N>>,
ledger: Ledger<N, C>,
routing: Arc<R>,
) -> Result<Self> {
let mut server = Self { consensus, ledger, routing, handles: Default::default() };
server.spawn_server(rest_ip);
Ok(server)
}
}
impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
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>, R: Routing<N>> Rest<N, C, R> {
fn spawn_server(&mut self, rest_ip: SocketAddr) {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
.allow_headers([CONTENT_TYPE]);
let router = {
axum::Router::new()
.route("/testnet3/node/address", get(Self::get_node_address))
.route_layer(middleware::from_fn(auth_middleware))
.route("/testnet3/latest/height", get(Self::latest_height))
.route("/testnet3/latest/hash", get(Self::latest_hash))
.route("/testnet3/latest/block", get(Self::latest_block))
.route("/testnet3/latest/stateRoot", get(Self::latest_state_root))
.route("/testnet3/latest/committee", get(Self::latest_committee))
.route("/testnet3/block/height/latest", get(Self::get_block_height_latest))
.route("/testnet3/block/hash/latest", get(Self::get_block_hash_latest))
.route("/testnet3/block/latest", get(Self::get_block_latest))
.route("/testnet3/block/:height_or_hash", get(Self::get_block))
.route("/testnet3/block/:height_or_hash/transactions", get(Self::get_block_transactions))
.route("/testnet3/transaction/:id", get(Self::get_transaction))
.route("/testnet3/transaction/broadcast", post(Self::transaction_broadcast))
.route("/testnet3/find/blockHash/:tx_id", get(Self::find_block_hash))
.route("/testnet3/find/transactionID/deployment/:program_id", get(Self::find_transaction_id_from_program_id))
.route("/testnet3/find/transactionID/:transition_id", get(Self::find_transaction_id_from_transition_id))
.route("/testnet3/find/transitionID/:input_or_output_id", get(Self::find_transition_id))
.route("/testnet3/peers/count", get(Self::get_peers_count))
.route("/testnet3/peers/all", get(Self::get_peers_all))
.route("/testnet3/peers/all/metrics", get(Self::get_peers_all_metrics))
.route("/testnet3/program/:id", get(Self::get_program))
.route("/testnet3/program/:id/mappings", get(Self::get_mapping_names))
.route("/testnet3/program/:id/mapping/:name/:key", get(Self::get_mapping_value))
.route("/testnet3/blocks", get(Self::get_blocks))
.route("/testnet3/height/:hash", get(Self::get_height))
.route("/testnet3/memoryPool/transmissions", get(Self::get_memory_pool_transmissions))
.route("/testnet3/memoryPool/solutions", get(Self::get_memory_pool_solutions))
.route("/testnet3/memoryPool/transactions", get(Self::get_memory_pool_transactions))
.route("/testnet3/statePath/:commitment", get(Self::get_state_path_for_commitment))
.route("/testnet3/stateRoot/latest", get(Self::get_state_root_latest))
.route("/testnet3/committee/latest", get(Self::get_committee_latest))
.with_state(self.clone())
.layer(TraceLayer::new_for_http())
.layer(middleware::from_fn(log_middleware))
.layer(cors)
.layer(DefaultBodyLimit::max(10 * 1024 * 1024))
};
self.handles.lock().push(tokio::spawn(async move {
axum::Server::bind(&rest_ip)
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.await
.expect("couldn't start rest server");
}))
}
}
async fn log_middleware<B>(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
request: Request<B>,
next: Next<B>,
) -> Result<Response, StatusCode>
where
B: Send,
{
info!("Received '{} {}' from '{addr}'", request.method(), request.uri());
Ok(next.run(request).await)
}