rewrite interface fns
Some checks are pending
Audit / audit (push) Waiting to run
Deny / audit (push) Waiting to run

This commit is contained in:
hinto.janai 2024-07-23 21:04:05 -04:00
parent 61ab466a73
commit b8966761e3
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
12 changed files with 397 additions and 155 deletions

49
Cargo.lock generated
View file

@ -114,14 +114,11 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
@ -132,13 +129,10 @@ dependencies = [
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@ -159,19 +153,6 @@ dependencies = [
"sync_wrapper 0.1.2",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
@ -402,7 +383,7 @@ version = "4.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"syn 2.0.66",
@ -830,6 +811,7 @@ dependencies = [
"cuprate-epee-encoding",
"cuprate-json-rpc",
"cuprate-rpc-types",
"paste",
"serde",
"tower",
]
@ -838,6 +820,7 @@ dependencies = [
name = "cuprate-rpc-types"
version = "0.0.0"
dependencies = [
"axum",
"cuprate-epee-encoding",
"cuprate-fixed-bytes",
"monero-serai",
@ -1270,12 +1253,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@ -1387,12 +1364,6 @@ version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.3.1"
@ -1405,7 +1376,6 @@ dependencies = [
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@ -2478,18 +2448,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha2"
version = "0.10.8"
@ -2864,7 +2822,6 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",

View file

@ -46,6 +46,7 @@ opt-level = 1
opt-level = 3
[workspace.dependencies]
axum = { version = "0.7.5", default-features = false }
async-trait = { version = "0.1.74", default-features = false }
bitflags = { version = "2.4.2", default-features = false }
borsh = { version = "1.2.1", default-features = false }

View file

@ -16,8 +16,9 @@ cuprate-epee-encoding = { path = "../../net/epee-encoding" }
cuprate-json-rpc = { path = "../json-rpc" }
cuprate-rpc-types = { path = "../types" }
axum = { version = "0.7.5", features = ["macros"] }
axum = { workspace = true, features = ["json"] }
serde = { workspace = true }
tower = { workspace = true }
paste = { workspace = true }
[dev-dependencies]

View file

@ -7,77 +7,80 @@ use axum::{extract::State, routing::method_routing::get, Router};
use tower::Service;
use crate::{
error::Error, request::Request, response::Response, route, rpc_handler::RpcHandler, RpcState,
error::Error,
request::Request,
response::Response,
route::{self, bin},
rpc_handler::RpcHandler,
RpcState,
};
//---------------------------------------------------------------------------------------------------- Router
/// TODO
#[allow(clippy::needless_pass_by_value)]
#[rustfmt::skip]
pub fn create_router<H: RpcHandler>() -> Router<H> {
// List of `monerod` routes:
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189>
let mut router = Router::new();
let router = Router::new();
// JSON-RPC route.
router = router.route("/json_rpc", get(route::json_rpc::<H>));
// // JSON-RPC route.
// router = router.route("/json_rpc", get(route::json_rpc::<H>));
// Other JSON routes.
for other_route in [
"/get_height",
"/getheight",
"/get_transactions",
"/gettransactions",
"/get_alt_blocks_hashes",
"/is_key_image_spent",
"/send_raw_transaction",
"/sendrawtransaction",
"/start_mining",
"/stop_mining",
"/mining_status",
"/save_bc",
"/get_peer_list",
"/get_public_nodes",
"/set_log_hash_rate",
"/set_log_level",
"/set_log_categories",
"/get_transaction_pool",
"/get_transaction_pool_hashes",
"/get_transaction_pool_stats",
"/set_bootstrap_daemon",
"/stop_daemon",
"/get_info",
"/getinfo",
"/get_net_stats",
"/get_limit",
"/set_limit",
"/out_peers",
"/in_peers",
"/get_outs",
"/update",
"/pop_blocks",
] {
router = router.route(other_route, get(route::other::<H>));
}
// // Other JSON routes.
// for other_route in [
// "/get_height",
// "/getheight",
// "/get_transactions",
// "/gettransactions",
// "/get_alt_blocks_hashes",
// "/is_key_image_spent",
// "/send_raw_transaction",
// "/sendrawtransaction",
// "/start_mining",
// "/stop_mining",
// "/mining_status",
// "/save_bc",
// "/get_peer_list",
// "/get_public_nodes",
// "/set_log_hash_rate",
// "/set_log_level",
// "/set_log_categories",
// "/get_transaction_pool",
// "/get_transaction_pool_hashes",
// "/get_transaction_pool_stats",
// "/set_bootstrap_daemon",
// "/stop_daemon",
// "/get_info",
// "/getinfo",
// "/get_net_stats",
// "/get_limit",
// "/set_limit",
// "/out_peers",
// "/in_peers",
// "/get_outs",
// "/update",
// "/pop_blocks",
// ] {
// router = router.route(other_route, get(route::other::<H>));
// }
// Binary routes.
for binary_route in [
"/get_blocks.bin",
"/getblocks.bin",
"/get_blocks_by_height.bin",
"/getblocks_by_height.bin",
"/get_hashes.bin",
"/gethashes.bin",
"/get_o_indexes.bin",
"/get_outs.bin",
"/get_transaction_pool_hashes.bin",
"/get_output_distribution.bin",
] {
router = router.route(binary_route, get(route::bin::<H>));
}
// Unknown route.
router = router.route("/*", get(route::unknown));
router
.route("/get_blocks.bin", get(bin::get_blocks::<H>))
.route("/getblocks.bin", get(bin::get_blocks::<H>))
.route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::<H>))
.route("/getblocks_by_height.bin", get(bin::get_blocks_by_height::<H>))
.route("/get_hashes.bin", get(bin::get_hashes::<H>))
.route("/gethashes.bin", get(bin::get_hashes::<H>))
.route("/get_o_indexes.bin", get(bin::get_o_indexes::<H>))
.route("/get_outs.bin", get(bin::get_outs::<H>))
.route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::<H>))
.route("/get_output_distribution.bin", get(bin::get_output_distribution::<H>))
// // Unknown route.
// router = router.route("/*", get(route::unknown));
// router
}

View file

@ -0,0 +1,97 @@
//! TODO
#![allow(clippy::unused_async)] // TODO: remove after impl
//---------------------------------------------------------------------------------------------------- Import
use std::{borrow::Cow, future::Future, sync::Arc};
use axum::{body::Bytes, extract::State, http::StatusCode, response::IntoResponse, Json};
use cuprate_json_rpc::{
error::{ErrorCode, ErrorObject},
Id,
};
use tower::{Service, ServiceExt};
use cuprate_epee_encoding::from_bytes;
use cuprate_rpc_types::{
bin::{
BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksByHeightResponse,
GetBlocksRequest, GetBlocksResponse, GetHashesRequest, GetHashesResponse,
GetOutputIndexesRequest, GetOutputIndexesResponse, GetOutsRequest, GetOutsResponse,
GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
},
json::{
GetOutputDistributionRequest, GetOutputDistributionResponse, JsonRpcRequest,
JsonRpcResponse,
},
other::{OtherRequest, OtherResponse},
RpcRequest,
};
use crate::{
error::Error, request::Request, response::Response, rpc_handler::RpcHandler,
rpc_state::RpcState,
};
//---------------------------------------------------------------------------------------------------- Routes
/// TODO
macro_rules! serialize_binary_request {
($variant:ident, $request:ident) => {
BinRequest::$variant(
from_bytes(&mut $request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
)
};
($variant:ident, $request:ident $(=> $constructor:expr)?) => {
BinRequest::$variant(())
};
}
/// TODO
macro_rules! generate_binary_endpoints {
($(
$endpoint:ident => $variant:ident $(=> $constructor:expr)?
),*) => { paste::paste! {
$(
/// TODO
#[allow(unused_mut)]
pub(crate) async fn $endpoint<H: RpcHandler>(
State(handler): State<H>,
mut request: Bytes,
) -> Result<Bytes, StatusCode> {
// Serialize into the request type.
let request = serialize_binary_request!($variant, request $(=> $constructor)?);
// TODO: call handler
let Response::Binary(response) = todo!() else {
panic!("RPC handler did not return a binary response");
};
// Assert the response from the inner handler is correct.
let BinResponse::$variant(response) = response else {
panic!("RPC handler returned incorrect response");
};
// Serialize to bytes and respond.
match cuprate_epee_encoding::to_bytes(response) {
Ok(bytes) => Ok(bytes.freeze()),
Err(e) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}
)*
}};
}
generate_binary_endpoints! {
get_blocks => GetBlocks,
get_blocks_by_height => GetBlocksByHeight,
get_hashes => GetHashes,
get_o_indexes => GetOutputIndexes,
get_outs => GetOuts,
get_transaction_pool_hashes => GetTransactionPoolHashes => (),
get_output_distribution => GetOutputDistribution
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -1,4 +1,5 @@
//! TODO
#![allow(clippy::unused_async)] // TODO: remove after impl
//---------------------------------------------------------------------------------------------------- Import
use std::{borrow::Cow, future::Future, sync::Arc};
@ -10,7 +11,12 @@ use cuprate_json_rpc::{
};
use tower::{Service, ServiceExt};
use cuprate_epee_encoding::from_bytes;
use cuprate_rpc_types::{
bin::{
BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest,
GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest,
},
json::{JsonRpcRequest, JsonRpcResponse},
other::{OtherRequest, OtherResponse},
RpcRequest,
@ -55,49 +61,6 @@ pub(crate) async fn json_rpc<H: RpcHandler>(
Ok(Json(response))
}
/// TODO
pub(crate) async fn bin<H: RpcHandler>(
State(handler): State<H>,
request: Bytes, // TODO: BinRequest
) -> Result<Vec<u8>, StatusCode> {
// TODO
// if handler.state().restricted() && request.is_restricted() {
if handler.state().restricted() {
return Err(StatusCode::NOT_FOUND);
}
// TODO: call handler
let Response::Binary(response) = todo!() else {
panic!("RPC handler returned incorrect response");
};
let binary: Vec<u8> = todo!(); // TODO: serialize response.
Ok(binary)
}
/// TODO
pub(crate) async fn other<H: RpcHandler>(
State(handler): State<H>,
Json(request): Json<OtherRequest>,
) -> Result<Json<OtherResponse>, StatusCode> {
if handler.state().restricted() && request.is_restricted() {
todo!();
}
// TODO: call handler
let Response::Other(response) = todo!() else {
panic!("RPC handler returned incorrect response");
};
Ok(Json(response))
}
/// TODO
pub(crate) async fn unknown() -> StatusCode {
StatusCode::NOT_FOUND
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {

View file

@ -0,0 +1,6 @@
//! TODO
pub(crate) mod bin;
mod json;
pub(crate) mod other;
mod unknown;

View file

@ -0,0 +1,135 @@
//! TODO
#![allow(clippy::unused_async)] // TODO: remove after impl
//---------------------------------------------------------------------------------------------------- Import
use std::{borrow::Cow, future::Future, sync::Arc};
use axum::{body::Bytes, extract::State, http::StatusCode, Json};
use cuprate_json_rpc::{
error::{ErrorCode, ErrorObject},
Id,
};
use tower::{Service, ServiceExt};
use cuprate_epee_encoding::from_bytes;
use cuprate_rpc_types::{
bin::{
BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest,
GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest,
},
json::{JsonRpcRequest, JsonRpcResponse},
other::{OtherRequest, OtherResponse},
RpcRequest,
};
use crate::{
error::Error, request::Request, response::Response, rpc_handler::RpcHandler,
rpc_state::RpcState,
};
//---------------------------------------------------------------------------------------------------- Routes
/// TODO
pub(crate) async fn json_rpc<H: RpcHandler>(
State(handler): State<H>,
Json(request): Json<cuprate_json_rpc::Request<JsonRpcRequest>>,
) -> Result<Json<cuprate_json_rpc::Response<JsonRpcResponse>>, StatusCode> {
// Return early if this RPC server is restricted and
// the requested method is only for non-restricted RPC.
if handler.state().restricted() && request.body.is_restricted() {
let error_object = ErrorObject {
code: ErrorCode::ServerError(-1 /* TODO */),
message: Cow::Borrowed("Restricted. TODO"),
data: None,
};
// JSON-RPC 2.0 rule:
// If there was an error in detecting the `Request`'s ID,
// the `Response` must contain an `Id::Null`
let id = request.id.unwrap_or(Id::Null);
let response = cuprate_json_rpc::Response::err(id, error_object);
// TODO
return Ok(Json(response));
}
// TODO: call handler
let Response::JsonRpc(response) = todo!() else {
panic!("RPC handler returned incorrect response");
};
Ok(Json(response))
}
/// TODO
pub(crate) async fn binary<H: RpcHandler>(
State(handler): State<H>,
endpoint: &'static str,
mut request: Bytes, // TODO: BinRequest
) -> Result<BinResponse, StatusCode> {
let error = |_| StatusCode::INTERNAL_SERVER_ERROR;
let request = match endpoint {
"/get_blocks.bin" | "/getblocks.bin" => {
BinRequest::GetBlocks(from_bytes(&mut request).map_err(error)?)
}
"/get_blocks_by_height.bin" | "/getblocks_by_height.bin" => {
BinRequest::GetBlocksByHeight(from_bytes(&mut request).map_err(error)?)
}
"/get_hashes.bin" | "/gethashes.bin" => {
BinRequest::GetHashes(from_bytes(&mut request).map_err(error)?)
}
"/get_o_indexes.bin" => {
BinRequest::GetOutputIndexes(from_bytes(&mut request).map_err(error)?)
}
"/get_outs.bin" => BinRequest::GetOuts(from_bytes(&mut request).map_err(error)?),
"/get_transaction_pool_hashes.bin" => BinRequest::GetTransactionPoolHashes(()),
"/get_output_distribution.bin" => {
BinRequest::GetOutputDistribution(from_bytes(&mut request).map_err(error)?)
}
// INVARIANT:
// The `create_router` function only passes the above endpoints.
_ => unreachable!(),
};
// TODO
if handler.state().restricted() && request.is_restricted() {
return Err(StatusCode::NOT_FOUND);
}
// TODO: call handler
let Response::Binary(response) = todo!() else {
panic!("RPC handler returned incorrect response");
};
Ok(response)
}
/// TODO
pub(crate) async fn other<H: RpcHandler>(
State(handler): State<H>,
Json(request): Json<OtherRequest>,
) -> Result<Json<OtherResponse>, StatusCode> {
if handler.state().restricted() && request.is_restricted() {
todo!();
}
// TODO: call handler
let Response::Other(response) = todo!() else {
panic!("RPC handler returned incorrect response");
};
Ok(Json(response))
}
/// TODO
pub(crate) async fn unknown() -> StatusCode {
StatusCode::NOT_FOUND
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -0,0 +1,40 @@
//! TODO
#![allow(clippy::unused_async)] // TODO: remove after impl
//---------------------------------------------------------------------------------------------------- Import
use std::{borrow::Cow, future::Future, sync::Arc};
use axum::{body::Bytes, extract::State, http::StatusCode, Json};
use cuprate_json_rpc::{
error::{ErrorCode, ErrorObject},
Id,
};
use tower::{Service, ServiceExt};
use cuprate_epee_encoding::from_bytes;
use cuprate_rpc_types::{
bin::{
BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest,
GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest,
},
json::{JsonRpcRequest, JsonRpcResponse},
other::{OtherRequest, OtherResponse},
RpcRequest,
};
use crate::{
error::Error, request::Request, response::Response, rpc_handler::RpcHandler,
rpc_state::RpcState,
};
//---------------------------------------------------------------------------------------------------- Routes
/// TODO
pub(crate) async fn unknown() -> StatusCode {
StatusCode::NOT_FOUND
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -9,7 +9,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/types"
keywords = ["cuprate", "rpc", "types", "monero"]
[features]
default = ["serde", "epee"]
default = ["serde", "epee", "axum"]
serde = ["dep:serde", "cuprate-fixed-bytes/serde"]
epee = ["dep:cuprate-epee-encoding"]
@ -17,6 +17,7 @@ epee = ["dep:cuprate-epee-encoding"]
cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" }
axum = { workspace = true, optional = true }
monero-serai = { workspace = true }
paste = { workspace = true }
serde = { workspace = true, optional = true }

View file

@ -5,6 +5,15 @@
//---------------------------------------------------------------------------------------------------- Import
use cuprate_fixed_bytes::ByteArrayVec;
#[cfg(feature = "axum")]
use axum::{
async_trait,
body::{Body, Bytes},
extract::{FromRequest, Request},
http::StatusCode,
response::{IntoResponse, Response},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -31,13 +40,13 @@ define_request_and_response! {
core_rpc_server_commands_defs.h => 162..=262,
GetBlocks,
Request {
requested_info: u8 = default_zero(), "default_zero",
requested_info: u8 = default_zero::<u8>(), "default_zero",
// FIXME: This is a `std::list` in `monerod` because...?
block_ids: ByteArrayVec<32>,
start_height: u64,
prune: bool,
no_miner_tx: bool = default_false(), "default_false",
pool_info_since: u64 = default_zero(), "default_zero",
pool_info_since: u64 = default_zero::<u64>(), "default_zero",
},
// TODO: this has custom epee (de)serialization.
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L242-L259>
@ -150,6 +159,7 @@ pub enum BinRequest {
GetOutputIndexes(GetOutputIndexesRequest),
GetOuts(GetOutsRequest),
GetTransactionPoolHashes(GetTransactionPoolHashesRequest),
GetOutputDistribution(crate::json::GetOutputDistributionRequest),
}
impl RpcRequest for BinRequest {
@ -162,7 +172,8 @@ impl RpcRequest for BinRequest {
| Self::GetHashes(_)
| Self::GetOutputIndexes(_)
| Self::GetOuts(_)
| Self::GetTransactionPoolHashes(()) => false,
| Self::GetTransactionPoolHashes(())
| Self::GetOutputDistribution(_) => false,
}
}
}
@ -179,6 +190,33 @@ pub enum BinResponse {
GetOutputIndexes(GetOutputIndexesResponse),
GetOuts(GetOutsResponse),
GetTransactionPoolHashes(GetTransactionPoolHashesResponse),
GetOutputDistribution(crate::json::GetOutputDistributionResponse),
}
#[cfg(feature = "axum")]
#[cfg(feature = "epee")]
impl axum::response::IntoResponse for BinResponse {
fn into_response(self) -> axum::response::Response {
use cuprate_epee_encoding::to_bytes;
let mut bytes = axum::body::Bytes::new();
let writer = &mut bytes;
let result = match self {
Self::GetBlocks(s) => to_bytes(s),
Self::GetBlocksByHeight(s) => to_bytes(s),
Self::GetHashes(s) => to_bytes(s),
Self::GetOutputIndexes(s) => to_bytes(s),
Self::GetOuts(s) => to_bytes(s),
Self::GetTransactionPoolHashes(s) => to_bytes(s),
Self::GetOutputDistribution(s) => to_bytes(s),
};
match result {
Ok(bytes) => bytes.into_response(),
Err(e) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
}
//---------------------------------------------------------------------------------------------------- Tests

View file

@ -483,9 +483,9 @@ define_request_and_response! {
version: u32,
release: bool,
#[serde(skip_serializing_if = "is_zero")]
current_height: u64 = default_zero(), "default_zero",
current_height: u64 = default_zero::<u64>(), "default_zero",
#[serde(skip_serializing_if = "is_zero")]
target_height: u64 = default_zero(), "default_zero",
target_height: u64 = default_zero::<u64>(), "default_zero",
#[serde(skip_serializing_if = "Vec::is_empty")]
hard_forks: Vec<HardforkEntry> = default_vec(), "default_vec",
}