use crate::{
rpc_trait::RpcFunctions,
rpc_types::{Meta, RpcCredentials},
RpcImpl,
};
use snarkos_metrics::{self as metrics, misc};
use snarkos_network::Node;
use snarkos_storage::DynStorage;
use hyper::{
body::HttpBody,
server::Server,
service::{make_service_fn, service_fn},
Body,
};
use json_rpc_types as jrt;
use jsonrpc_core::Params;
use serde::Serialize;
use std::{convert::Infallible, net::SocketAddr};
const METHODS_EXPECTING_PARAMS: [&str; 15] = [
"getblock",
"getblockhash",
"getrawtransaction",
"gettransactioninfo",
"decoderawtransaction",
"sendtransaction",
"validaterawtransaction",
"createrawtransaction",
"createtransactionkernel",
"createtransaction",
"getrawrecord",
"decoderecord",
"decryptrecord",
"disconnect",
"connect",
];
#[allow(clippy::too_many_arguments)]
pub fn start_rpc_server(
rpc_addr: SocketAddr,
storage: DynStorage,
node_server: Node,
username: Option<String>,
password: Option<String>,
) -> tokio::task::JoinHandle<()> {
let credentials = match (username, password) {
(Some(username), Some(password)) => Some(RpcCredentials { username, password }),
_ => None,
};
let rpc_impl = RpcImpl::new(storage, credentials, node_server);
let service = make_service_fn(move |_conn| {
let rpc = rpc_impl.clone();
async move { Ok::<_, Infallible>(service_fn(move |req| handle_rpc(rpc.clone(), req))) }
});
let server = Server::bind(&rpc_addr).serve(service);
tokio::spawn(async move {
server.await.expect("The RPC server couldn't be started!");
})
}
async fn handle_rpc(rpc: RpcImpl, req: hyper::Request<Body>) -> Result<hyper::Response<Body>, Infallible> {
metrics::increment_counter!(misc::RPC_REQUESTS);
let auth = req
.headers()
.get(hyper::header::AUTHORIZATION)
.map(|h| h.to_str().unwrap_or("").to_owned());
let meta = Meta { auth };
let mut body = req.into_body();
let data = match body.data().await {
Some(Ok(data)) => data,
err_or_none => {
let mut error = jrt::Error::with_custom_msg(jrt::ErrorCode::ParseError, "Couldn't read the RPC body");
if let Some(Err(err)) = err_or_none {
error.data = Some(err.to_string());
}
let resp = jrt::Response::<(), String>::error(jrt::Version::V2, error, None);
let body = serde_json::to_vec(&resp).unwrap_or_default();
return Ok(hyper::Response::new(body.into()));
}
};
let req: jrt::Request<Params> = match serde_json::from_slice(&data) {
Ok(req) => req,
Err(_) => {
let resp = jrt::Response::<(), ()>::error(
jrt::Version::V2,
jrt::Error::with_custom_msg(jrt::ErrorCode::ParseError, "Couldn't parse the RPC body"),
None,
);
let body = serde_json::to_vec(&resp).unwrap_or_default();
return Ok(hyper::Response::new(body.into()));
}
};
let mut params = match read_params(&req) {
Ok(params) => params,
Err(err) => {
let resp = jrt::Response::<(), ()>::error(jrt::Version::V2, err, req.id.clone());
let body = serde_json::to_vec(&resp).unwrap_or_default();
return Ok(hyper::Response::new(body.into()));
}
};
let response = match &*req.method {
"getblock" => {
let result = rpc
.get_block(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getblockcount" => {
let result = rpc.get_block_count().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getbestblockhash" => {
let result = rpc.get_best_block_hash().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getblockhash" => match serde_json::from_value::<u32>(params.remove(0)) {
Ok(height) => {
let result = rpc.get_block_hash(height).await.map_err(convert_crate_err);
result_to_response(&req, result)
}
Err(_) => {
let err = jrt::Error::with_custom_msg(jrt::ErrorCode::ParseError, "Invalid block height!");
jrt::Response::error(jrt::Version::V2, err, req.id.clone())
}
},
"getrawtransaction" => {
let result = rpc
.get_raw_transaction(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"gettransactioninfo" => {
let result = rpc
.get_transaction_info(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"decoderawtransaction" => {
let result = rpc
.decode_raw_transaction(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"sendtransaction" => {
let result = rpc
.send_raw_transaction(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"validaterawtransaction" => {
let result = rpc
.validate_raw_transaction(params[0].as_str().unwrap_or("").into())
.await
.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getconnectioncount" => {
let result = rpc.get_connection_count().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getpeerinfo" => {
let result = rpc.get_peer_info().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getnodeinfo" => {
let result = rpc.get_node_info().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getnodestats" => {
let result = rpc.get_node_stats().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getblocktemplate" => {
let result = rpc.get_block_template().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"getnetworkgraph" => {
let result = rpc.get_network_graph().await.map_err(convert_crate_err);
result_to_response(&req, result)
}
"createaccount" => {
let result = rpc
.create_account_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"createrawtransaction" => {
let result = rpc
.create_raw_transaction_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"createtransactionkernel" => {
let result = rpc
.create_transaction_kernel_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"createtransaction" => {
let result = rpc
.create_transaction_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"getrecordcommitments" => {
let result = rpc
.get_record_commitments_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"getrawrecord" => {
let result = rpc
.get_raw_record_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"decoderecord" => {
let result = rpc
.decode_record_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"decryptrecord" => {
let result = rpc
.decrypt_record_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"disconnect" => {
let result = rpc
.disconnect_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
"connect" => {
let result = rpc
.connect_protected(Params::Array(params), meta)
.await
.map_err(convert_core_err);
result_to_response(&req, result)
}
_ => {
let err = jrt::Error::from_code(jrt::ErrorCode::MethodNotFound);
jrt::Response::error(jrt::Version::V2, err, req.id.clone())
}
};
let body = serde_json::to_vec(&response).unwrap_or_default();
Ok(hyper::Response::new(body.into()))
}
fn read_params(req: &jrt::Request<Params>) -> Result<Vec<serde_json::Value>, jrt::Error<()>> {
if METHODS_EXPECTING_PARAMS.contains(&&*req.method) {
match &req.params {
Some(Params::Array(arr)) if !arr.is_empty() => Ok(arr.clone()),
Some(_) => Err(jrt::Error::from_code(jrt::ErrorCode::InvalidParams)),
None => Err(jrt::Error::from_code(jrt::ErrorCode::InvalidParams)),
}
} else {
Ok(vec![]) }
}
fn convert_crate_err(err: crate::error::RpcError) -> jrt::Error<String> {
let error = jrt::Error::with_custom_msg(jrt::ErrorCode::ServerError(-32000), "internal error");
error.set_data(err.to_string())
}
fn convert_core_err(err: jsonrpc_core::Error) -> jrt::Error<String> {
let error = jrt::Error::with_custom_msg(jrt::ErrorCode::InternalError, "JSONRPC server error");
error.set_data(err.to_string())
}
fn result_to_response<T: Serialize>(
request: &jrt::Request<Params>,
result: Result<T, jrt::Error<String>>,
) -> jrt::Response<serde_json::Value, String> {
match result {
Ok(res) => {
let result = serde_json::to_value(&res).unwrap_or_default();
jrt::Response::result(jrt::Version::V2, result, request.id.clone())
}
Err(err) => jrt::Response::error(jrt::Version::V2, err, request.id.clone()),
}
}