mirror of
https://github.com/Cuprate/cuprate.git
synced 2024-12-22 19:49:28 +00:00
rpc: impl cuprate-rpc-interface
(#233)
Some checks are pending
Audit / audit (push) Waiting to run
CI / fmt (push) Waiting to run
CI / typo (push) Waiting to run
CI / ci (macos-latest, stable, bash) (push) Waiting to run
CI / ci (ubuntu-latest, stable, bash) (push) Waiting to run
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Waiting to run
Deny / audit (push) Waiting to run
Doc / build (push) Waiting to run
Doc / deploy (push) Blocked by required conditions
Some checks are pending
Audit / audit (push) Waiting to run
CI / fmt (push) Waiting to run
CI / typo (push) Waiting to run
CI / ci (macos-latest, stable, bash) (push) Waiting to run
CI / ci (ubuntu-latest, stable, bash) (push) Waiting to run
CI / ci (windows-latest, stable-x86_64-pc-windows-gnu, msys2 {0}) (push) Waiting to run
Deny / audit (push) Waiting to run
Doc / build (push) Waiting to run
Doc / deploy (push) Blocked by required conditions
* fixed-bytes: add `serde`, document feature flags * fixed-bytes: add derives * rpc: add `as _` syntax to macro * rpc: use `ByteArrayVec` and `ContainerAsBlob` for binary types * fixed-bytes: re-add derives * rpc-types: dedup default value within macro * readme: fixed bytes section * types: custom epee - `BlockCompleteEntry` * types: custom epee - `KeyImageSpentStatus` * types: custom epee - `PoolInfoExtent` * types: add `Status::Other(String)` variant * types: custom epee - `TxEntry`, add `read_epee_field` macro * bin: custom epee - `GetBlocks` * types: add `serde.rs` * misc: make `TxEntry` an `enum`, impl serde * misc: `unimplemented!()` for `TxEntry`'s epee * types: add `BlockCompleteEntry` * rpc: replace `BlockCompleteEntry` with `cuprate-types` * types: document `BlockCompleteEntry` * bin: fix `number_of_fields` for `GetBlocksResponse` * misc: add `Distribution` * distribution: add todo * misc fixes * readme: add `(De)serialization invariants` * distribution: compress variants * types: add `block_complete_entry.rs` * net: fix imports * p2p: fix imports * turn off default-features * p2p: fix imports * misc fixes * Update net/wire/Cargo.toml Co-authored-by: Boog900 <boog900@tutanota.com> * distribution: module doc * wire: re-export types * test-utils: add `crate::rpc::types` module * test-utils: conditional json doc-tests * bin: use enum for `GetBlocksResponse` * misc: use lowercase for stringify * json: add test data, fix macro doc tests * json: add all data * other: add all data * bin: add skeleton * docs * move type to correct file * remove duplicated fields for custom epee * rpc: `client/{client,constants}.rs` -> `client.rs` * lib.rs: remove `clippy::module_inception` * macros: add json doc test macro * json: add some tests * json: add doc-test for all types * add all other JSON doc-tests * move doc-test macros to files * base: add doc-tests * rpc: add `cuprate-rpc-interface` skeleton files * traits * json_rpc_method: add `.is_restricted()` * add route fn signatures * types: add rpc enums * interface: routes, types * interface: simplify routes * rewrite interface fns * types: remove `()` type alias, add `(restricted)` * types: add `other::InPeers` * interface: routes * types: fix `is_restricted()` * interface: reorder short-circuit bool * clean up traits/bounds * types: remove `axum` feature * interface: cleanup unused imports * interface: call handler in routes * json: TODO distribution test * interface: readme intro * combine `RpcHandler` + `RpcService`, add `RpcDummyHandler` * interface: readme docs + test * `IsRestricted` -> `RpcCall` * fix no input route problem * interface: `RpcHandlerDummy` docs * interface: crate docs * replace `create_router` with `RouterBuilder` * types: docs * types: doc `JsonRpc{Request,Response}` * types: readme docs * interface: doc `route/` * interface: fix `todo!()` * interface: allow customizing HTTP method on route functions * interface: fix tests * fix derives * Update rpc/interface/README.md Co-authored-by: Boog900 <boog900@tutanota.com> * Update rpc/interface/README.md Co-authored-by: Boog900 <boog900@tutanota.com> * interface: make `RpcHandler`'s `Future` generic * interface: add JSON-RPC notification todo * formatting * interface: use associated type bound for `RpcHandler`'s `Future` --------- Co-authored-by: Boog900 <boog900@tutanota.com>
This commit is contained in:
parent
1a178381dd
commit
27767690ca
15 changed files with 1334 additions and 0 deletions
196
Cargo.lock
generated
196
Cargo.lock
generated
|
@ -94,12 +94,73 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
|
@ -374,6 +435,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
|
@ -764,6 +834,20 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cuprate-rpc-interface"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"cuprate-epee-encoding",
|
||||
"cuprate-helper",
|
||||
"cuprate-json-rpc",
|
||||
"cuprate-rpc-types",
|
||||
"futures",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuprate-rpc-types"
|
||||
|
@ -1030,6 +1114,16 @@ version = "0.2.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flexible-transcript"
|
||||
version = "0.3.2"
|
||||
|
@ -1179,6 +1273,25 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.2.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
|
@ -1299,6 +1412,12 @@ 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"
|
||||
|
@ -1308,9 +1427,11 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
|
@ -1610,6 +1731,12 @@ version = "0.4.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
@ -1638,6 +1765,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.3"
|
||||
|
@ -2204,6 +2337,7 @@ version = "0.23.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
@ -2351,6 +2485,28 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"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"
|
||||
|
@ -2486,6 +2642,18 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
|
||||
[[package]]
|
||||
name = "synchronoise"
|
||||
version = "1.0.1"
|
||||
|
@ -2706,6 +2874,7 @@ 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",
|
||||
|
@ -2770,6 +2939,24 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.1"
|
||||
|
@ -2877,6 +3064,15 @@ version = "0.2.92"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
@ -9,7 +9,24 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte
|
|||
keywords = ["cuprate", "rpc", "interface"]
|
||||
|
||||
[features]
|
||||
default = ["dummy", "serde"]
|
||||
dummy = []
|
||||
|
||||
[dependencies]
|
||||
cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false }
|
||||
cuprate-json-rpc = { path = "../json-rpc", default-features = false }
|
||||
cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false }
|
||||
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false }
|
||||
|
||||
axum = { version = "0.7.5", features = ["json"], default-features = false }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
tower = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { version = "0.7.5", features = ["json", "tokio", "http2"] }
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
ureq = { version = "2.10.0", features = ["json"] }
|
161
rpc/interface/README.md
Normal file
161
rpc/interface/README.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# `cuprate-rpc-interface`
|
||||
This crate provides Cuprate's RPC _interface_.
|
||||
|
||||
This crate is _not_ a standalone RPC server, it is just the interface.
|
||||
|
||||
```text
|
||||
cuprate-rpc-interface provides these parts
|
||||
│ │
|
||||
┌───────────────────────────┤ ├───────────────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
CLIENT ─► ROUTE ─► REQUEST ─► HANDLER ─► RESPONSE ─► CLIENT
|
||||
▲ ▲
|
||||
└───┬───┘
|
||||
│
|
||||
You provide this part
|
||||
```
|
||||
|
||||
Everything coming _in_ from a client is handled by this crate.
|
||||
|
||||
This is where your [`RpcHandler`] turns this [`RpcRequest`] into a [`RpcResponse`].
|
||||
|
||||
You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client.
|
||||
|
||||
The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate;
|
||||
it implements the standard RPC handlers modeled after `monerod`.
|
||||
|
||||
# Purpose
|
||||
`cuprate-rpc-interface` is built on-top of [`axum`],
|
||||
which is the crate _actually_ handling everything.
|
||||
|
||||
This crate simply handles:
|
||||
- Registering endpoint routes (e.g. `/get_block.bin`)
|
||||
- Defining handler function signatures
|
||||
- (De)serialization of requests/responses (JSON-RPC, binary, JSON)
|
||||
|
||||
The actual server details are all handled by the [`axum`] and [`tower`] ecosystem.
|
||||
|
||||
The proper usage of this crate is to:
|
||||
1. Implement a [`RpcHandler`]
|
||||
2. Use it with [`RouterBuilder`] to generate an
|
||||
[`axum::Router`] with all Monero RPC routes set
|
||||
3. Do whatever with it
|
||||
|
||||
# The [`RpcHandler`]
|
||||
This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s,
|
||||
i.e. the "inner handler".
|
||||
|
||||
Said concretely, `RpcHandler` is a `tower::Service` where the associated types are from this crate:
|
||||
- [`RpcRequest`]
|
||||
- [`RpcResponse`]
|
||||
- [`RpcError`]
|
||||
|
||||
`RpcHandler`'s [`Future`](std::future::Future) is generic, _although_,
|
||||
it must output `Result<RpcResponse, RpcError>`.
|
||||
|
||||
The `RpcHandler` must also hold some state that is required
|
||||
for RPC server operation.
|
||||
|
||||
The only state currently needed is [`RpcHandler::restricted`], which determines if an RPC
|
||||
server is restricted or not, and thus, if some endpoints/methods are allowed or not.
|
||||
|
||||
# Unknown endpoint behavior
|
||||
TODO: decide what this crate should return (per different endpoint)
|
||||
when a request is received to an unknown endpoint, including HTTP stuff, e.g. status code.
|
||||
|
||||
# Unknown JSON-RPC method behavior
|
||||
TODO: decide what this crate returns when a `/json_rpc`
|
||||
request is received with an unknown method, including HTTP stuff, e.g. status code.
|
||||
|
||||
# Example
|
||||
Example usage of this crate + starting an RPC server.
|
||||
|
||||
This uses `RpcHandlerDummy` as the handler; it always responds with the
|
||||
correct response type, but set to a default value regardless of the request.
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::{net::TcpListener, sync::Barrier};
|
||||
|
||||
use cuprate_json_rpc::{Request, Response, Id};
|
||||
use cuprate_rpc_types::{
|
||||
json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse},
|
||||
other::{OtherRequest, OtherResponse},
|
||||
};
|
||||
use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy, RpcRequest};
|
||||
|
||||
// Send a `/get_height` request. This endpoint has no inputs.
|
||||
async fn get_height(port: u16) -> OtherResponse {
|
||||
let url = format!("http://127.0.0.1:{port}/get_height");
|
||||
ureq::get(&url)
|
||||
.set("Content-Type", "application/json")
|
||||
.call()
|
||||
.unwrap()
|
||||
.into_json()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Send a JSON-RPC request with the `get_block_count` method.
|
||||
//
|
||||
// The returned [`String`] is JSON.
|
||||
async fn get_block_count(port: u16) -> String {
|
||||
let url = format!("http://127.0.0.1:{port}/json_rpc");
|
||||
let method = JsonRpcRequest::GetBlockCount(Default::default());
|
||||
let request = Request::new(method);
|
||||
ureq::get(&url)
|
||||
.set("Content-Type", "application/json")
|
||||
.send_json(request)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Start a local RPC server.
|
||||
let port = {
|
||||
// Create the router.
|
||||
let state = RpcHandlerDummy { restricted: false };
|
||||
let router = RouterBuilder::new().all().build().with_state(state);
|
||||
|
||||
// Start a server.
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
|
||||
// Run the server with `axum`.
|
||||
tokio::task::spawn(async move {
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
});
|
||||
|
||||
port
|
||||
};
|
||||
|
||||
// Assert the response is the default.
|
||||
let response = get_height(port).await;
|
||||
let expected = OtherResponse::GetHeight(Default::default());
|
||||
assert_eq!(response, expected);
|
||||
|
||||
// Assert the response JSON is correct.
|
||||
let response = get_block_count(port).await;
|
||||
let expected = r#"{"jsonrpc":"2.0","id":null,"result":{"status":"OK","untrusted":false,"count":0}}"#;
|
||||
assert_eq!(response, expected);
|
||||
|
||||
// Assert that (de)serialization works.
|
||||
let expected = Response::ok(Id::Null, Default::default());
|
||||
let response: Response<GetBlockCountResponse> = serde_json::from_str(&response).unwrap();
|
||||
assert_eq!(response, expected);
|
||||
}
|
||||
```
|
||||
|
||||
# Feature flags
|
||||
List of feature flags for `cuprate-rpc-interface`.
|
||||
|
||||
All are enabled by default.
|
||||
|
||||
| Feature flag | Does what |
|
||||
|--------------|-----------|
|
||||
| `serde` | Enables serde on applicable types
|
||||
| `dummy` | Enables the `RpcHandlerDummy` type
|
|
@ -1 +1,123 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
//---------------------------------------------------------------------------------------------------- Lints
|
||||
// Forbid lints.
|
||||
// Our code, and code generated (e.g macros) cannot overrule these.
|
||||
#![forbid(
|
||||
// `unsafe` is allowed but it _must_ be
|
||||
// commented with `SAFETY: reason`.
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
|
||||
// Never.
|
||||
unused_unsafe,
|
||||
redundant_semicolons,
|
||||
unused_allocation,
|
||||
coherence_leak_check,
|
||||
while_true,
|
||||
|
||||
// Maybe can be put into `#[deny]`.
|
||||
unconditional_recursion,
|
||||
for_loops_over_fallibles,
|
||||
unused_braces,
|
||||
unused_labels,
|
||||
keyword_idents,
|
||||
non_ascii_idents,
|
||||
variant_size_differences,
|
||||
single_use_lifetimes,
|
||||
|
||||
// Probably can be put into `#[deny]`.
|
||||
future_incompatible,
|
||||
let_underscore,
|
||||
break_with_label_and_loop,
|
||||
duplicate_macro_attributes,
|
||||
exported_private_dependencies,
|
||||
large_assignments,
|
||||
overlapping_range_endpoints,
|
||||
semicolon_in_expressions_from_macros,
|
||||
noop_method_call,
|
||||
)]
|
||||
// Deny lints.
|
||||
// Some of these are `#[allow]`'ed on a per-case basis.
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::correctness,
|
||||
clippy::suspicious,
|
||||
clippy::style,
|
||||
clippy::complexity,
|
||||
clippy::perf,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo,
|
||||
unused_doc_comments,
|
||||
unused_mut,
|
||||
missing_docs,
|
||||
deprecated,
|
||||
unused_comparisons,
|
||||
nonstandard_style,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![allow(
|
||||
// FIXME: this lint affects crates outside of
|
||||
// `database/` for some reason, allow for now.
|
||||
clippy::cargo_common_metadata,
|
||||
|
||||
// FIXME: adding `#[must_use]` onto everything
|
||||
// might just be more annoying than useful...
|
||||
// although it is sometimes nice.
|
||||
clippy::must_use_candidate,
|
||||
|
||||
// FIXME: good lint but too many false positives
|
||||
// with our `Env` + `RwLock` setup.
|
||||
clippy::significant_drop_tightening,
|
||||
|
||||
// FIXME: good lint but is less clear in most cases.
|
||||
clippy::items_after_statements,
|
||||
|
||||
// TODO
|
||||
rustdoc::bare_urls,
|
||||
|
||||
clippy::module_name_repetitions,
|
||||
clippy::module_inception,
|
||||
clippy::redundant_pub_crate,
|
||||
clippy::option_if_let_else,
|
||||
)]
|
||||
// Allow some lints when running in debug mode.
|
||||
#![cfg_attr(
|
||||
debug_assertions,
|
||||
allow(
|
||||
clippy::todo,
|
||||
clippy::multiple_crate_versions,
|
||||
unused_imports,
|
||||
unused_variables
|
||||
)
|
||||
)]
|
||||
// Allow some lints in tests.
|
||||
#![cfg_attr(
|
||||
test,
|
||||
allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::too_many_lines
|
||||
)
|
||||
)]
|
||||
// TODO: remove me after finishing impl
|
||||
#![allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Mod
|
||||
mod route;
|
||||
mod router_builder;
|
||||
mod rpc_error;
|
||||
mod rpc_handler;
|
||||
#[cfg(feature = "dummy")]
|
||||
mod rpc_handler_dummy;
|
||||
mod rpc_request;
|
||||
mod rpc_response;
|
||||
|
||||
pub use router_builder::RouterBuilder;
|
||||
pub use rpc_error::RpcError;
|
||||
pub use rpc_handler::RpcHandler;
|
||||
#[cfg(feature = "dummy")]
|
||||
pub use rpc_handler_dummy::RpcHandlerDummy;
|
||||
pub use rpc_request::RpcRequest;
|
||||
pub use rpc_response::RpcResponse;
|
||||
|
|
108
rpc/interface/src/route/bin.rs
Normal file
108
rpc/interface/src/route/bin.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
//! Binary route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::{body::Bytes, extract::State, http::StatusCode};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_epee_encoding::from_bytes;
|
||||
use cuprate_rpc_types::bin::{BinRequest, BinResponse, GetTransactionPoolHashesRequest};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// This macro generates route functions that expect input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { 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 = BinRequest::$variant(
|
||||
from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
);
|
||||
|
||||
generate_endpoints_inner!($variant, handler, request)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// This macro generates route functions that expect _no_ input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_no_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type (that is empty)
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
/// TODO
|
||||
#[allow(unused_mut)]
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
) -> Result<Bytes, StatusCode> {
|
||||
const REQUEST: BinRequest = BinRequest::$variant([<$variant Request>] {});
|
||||
generate_endpoints_inner!($variant, handler, REQUEST)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// De-duplicated inner function body for:
|
||||
/// - [`generate_endpoints_with_input`]
|
||||
/// - [`generate_endpoints_with_no_input`]
|
||||
macro_rules! generate_endpoints_inner {
|
||||
($variant:ident, $handler:ident, $request:expr) => {
|
||||
paste::paste! {
|
||||
{
|
||||
// Send request.
|
||||
let request = RpcRequest::Binary($request);
|
||||
let channel = $handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::Binary(response) = channel else {
|
||||
panic!("RPC handler did not return a binary response");
|
||||
};
|
||||
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_endpoints_with_input! {
|
||||
get_blocks => GetBlocks,
|
||||
get_blocks_by_height => GetBlocksByHeight,
|
||||
get_hashes => GetHashes,
|
||||
get_o_indexes => GetOutputIndexes,
|
||||
get_outs => GetOuts,
|
||||
get_output_distribution => GetOutputDistribution
|
||||
}
|
||||
|
||||
generate_endpoints_with_no_input! {
|
||||
get_transaction_pool_hashes => GetTransactionPoolHashes
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
18
rpc/interface/src/route/fallback.rs
Normal file
18
rpc/interface/src/route/fallback.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! Fallback route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::http::StatusCode;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// Fallback route function.
|
||||
///
|
||||
/// This is used as the fallback endpoint in [`crate::RouterBuilder`].
|
||||
pub(crate) async fn fallback() -> StatusCode {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
68
rpc/interface/src/route/json_rpc.rs
Normal file
68
rpc/interface/src/route/json_rpc.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
//! JSON-RPC 2.0 endpoint route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::borrow::Cow;
|
||||
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_json_rpc::{
|
||||
error::{ErrorCode, ErrorObject},
|
||||
Id,
|
||||
};
|
||||
use cuprate_rpc_types::{
|
||||
json::{JsonRpcRequest, JsonRpcResponse},
|
||||
RpcCallValue,
|
||||
};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// The `/json_rpc` route function used in [`crate::RouterBuilder`].
|
||||
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> {
|
||||
// TODO: <https://www.jsonrpc.org/specification#notification>
|
||||
//
|
||||
// JSON-RPC notifications (requests without `id`)
|
||||
// must not be responded too, although, the request's side-effects
|
||||
// must remain. How to do this considering this function will
|
||||
// always return and cause `axum` to respond?
|
||||
|
||||
// Return early if this RPC server is restricted and
|
||||
// the requested method is only for non-restricted RPC.
|
||||
if request.body.is_restricted() && handler.restricted() {
|
||||
let error_object = ErrorObject {
|
||||
code: ErrorCode::ServerError(-1 /* TODO */),
|
||||
message: Cow::Borrowed("Restricted. TODO: mimic monerod message"),
|
||||
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);
|
||||
|
||||
return Ok(Json(response));
|
||||
}
|
||||
|
||||
// Send request.
|
||||
let request = RpcRequest::JsonRpc(request);
|
||||
let channel = handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::JsonRpc(response) = channel else {
|
||||
panic!("RPC handler returned incorrect response");
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
9
rpc/interface/src/route/mod.rs
Normal file
9
rpc/interface/src/route/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
//! Routing functions.
|
||||
//!
|
||||
//! These are the function signatures passed to
|
||||
//! [`crate::RouterBuilder`] when registering routes.
|
||||
|
||||
pub(crate) mod bin;
|
||||
pub(crate) mod fallback;
|
||||
pub(crate) mod json_rpc;
|
||||
pub(crate) mod other;
|
138
rpc/interface/src/route/other.rs
Normal file
138
rpc/interface/src/route/other.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
//! Other JSON endpoint route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_rpc_types::{
|
||||
other::{
|
||||
GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse,
|
||||
GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest,
|
||||
GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest,
|
||||
GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
|
||||
GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest,
|
||||
GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse,
|
||||
InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse,
|
||||
MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest,
|
||||
OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse,
|
||||
SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest,
|
||||
SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest,
|
||||
SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse,
|
||||
SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, StartMiningResponse,
|
||||
StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse,
|
||||
UpdateRequest, UpdateResponse,
|
||||
},
|
||||
RpcCall,
|
||||
};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// This macro generates route functions that expect input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
Json(request): Json<[<$variant Request>]>,
|
||||
) -> Result<Json<[<$variant Response>]>, StatusCode> {
|
||||
generate_endpoints_inner!($variant, handler, request)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// This macro generates route functions that expect _no_ input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_no_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type (that is empty)
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
) -> Result<Json<[<$variant Response>]>, StatusCode> {
|
||||
generate_endpoints_inner!($variant, handler, [<$variant Request>] {})
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// De-duplicated inner function body for:
|
||||
/// - [`generate_endpoints_with_input`]
|
||||
/// - [`generate_endpoints_with_no_input`]
|
||||
macro_rules! generate_endpoints_inner {
|
||||
($variant:ident, $handler:ident, $request:expr) => {
|
||||
paste::paste! {
|
||||
{
|
||||
// Check if restricted.
|
||||
if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() {
|
||||
// TODO: mimic `monerod` behavior.
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
// Send request.
|
||||
let request = RpcRequest::Other(OtherRequest::$variant($request));
|
||||
let channel = $handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::Other(response) = channel else {
|
||||
panic!("RPC handler did not return a binary response");
|
||||
};
|
||||
let OtherResponse::$variant(response) = response else {
|
||||
panic!("RPC handler returned incorrect response")
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_endpoints_with_input! {
|
||||
get_transactions => GetTransactions,
|
||||
is_key_image_spent => IsKeyImageSpent,
|
||||
send_raw_transaction => SendRawTransaction,
|
||||
start_mining => StartMining,
|
||||
get_peer_list => GetPeerList,
|
||||
set_log_hash_rate => SetLogHashRate,
|
||||
set_log_level => SetLogLevel,
|
||||
set_log_categories => SetLogCategories,
|
||||
set_bootstrap_daemon => SetBootstrapDaemon,
|
||||
set_limit => SetLimit,
|
||||
out_peers => OutPeers,
|
||||
in_peers => InPeers,
|
||||
get_outs => GetOuts,
|
||||
update => Update,
|
||||
pop_blocks => PopBlocks,
|
||||
get_public_nodes => GetPublicNodes
|
||||
}
|
||||
|
||||
generate_endpoints_with_no_input! {
|
||||
get_height => GetHeight,
|
||||
get_alt_blocks_hashes => GetAltBlocksHashes,
|
||||
stop_mining => StopMining,
|
||||
mining_status => MiningStatus,
|
||||
save_bc => SaveBc,
|
||||
get_transaction_pool => GetTransactionPool,
|
||||
get_transaction_pool_stats => GetTransactionPoolStats,
|
||||
stop_daemon => StopDaemon,
|
||||
get_limit => GetLimit,
|
||||
get_net_stats => GetNetStats,
|
||||
get_transaction_pool_hashes => GetTransactionPoolHashes
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
198
rpc/interface/src/router_builder.rs
Normal file
198
rpc/interface/src/router_builder.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
//! Free functions.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use axum::{
|
||||
routing::{method_routing::get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
route::{bin, fallback, json_rpc, other},
|
||||
rpc_handler::RpcHandler,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RouterBuilder
|
||||
/// Generate the `RouterBuilder` struct.
|
||||
macro_rules! generate_router_builder {
|
||||
($(
|
||||
// Syntax:
|
||||
// $BUILDER_FUNCTION_NAME =>
|
||||
// $ACTUAL_ENDPOINT_STRING =>
|
||||
// $ENDPOINT_FUNCTION_MODULE::$ENDPOINT_FUNCTION =>
|
||||
// ($HTTP_METHOD(s))
|
||||
$endpoint_ident:ident =>
|
||||
$endpoint_string:literal =>
|
||||
$endpoint_module:ident::$endpoint_fn:ident =>
|
||||
($($http_method:ident),*)
|
||||
),* $(,)?) => {
|
||||
/// Builder for creating the RPC router.
|
||||
///
|
||||
/// This builder allows you to selectively enable endpoints for the router,
|
||||
/// and a [`fallback`](RouterBuilder::fallback) route.
|
||||
///
|
||||
/// The [`default`](RouterBuilder::default) is to enable [`all`](RouterBuilder::all) routes.
|
||||
///
|
||||
/// # Routes
|
||||
/// Functions that enable routes are separated into 3 groups:
|
||||
/// - `json_rpc` (enables all of JSON RPC 2.0)
|
||||
/// - `other_` (e.g. [`other_get_height`](RouterBuilder::other_get_height))
|
||||
/// - `bin_` (e.g. [`bin_get_blocks`](RouterBuilder::bin_get_blocks))
|
||||
///
|
||||
/// For a list of all `monerod` routes, see
|
||||
/// [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189),
|
||||
/// or the source file of this type.
|
||||
///
|
||||
/// # Aliases
|
||||
/// Some routes have aliases, such as [`/get_height`](RouterBuilder::other_get_height)
|
||||
/// and [`/getheight`](RouterBuilder::other_getheight).
|
||||
///
|
||||
/// These both route to the same handler function, but they do not enable each other.
|
||||
///
|
||||
/// If desired, you can enable `/get_height` but not `/getheight`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy};
|
||||
///
|
||||
/// // Create a router with _only_ `/json_rpc` enabled.
|
||||
/// let only_json_rpc = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .json_rpc()
|
||||
/// .build();
|
||||
///
|
||||
/// // Create a router with:
|
||||
/// // - `/get_outs.bin` enabled
|
||||
/// // - A fallback enabled
|
||||
/// let get_outs_bin_and_fallback = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .bin_get_outs()
|
||||
/// .fallback()
|
||||
/// .build();
|
||||
///
|
||||
/// // Create a router with all endpoints enabled.
|
||||
/// let all = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .all()
|
||||
/// .build();
|
||||
/// ```
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Clone)]
|
||||
pub struct RouterBuilder<H: RpcHandler> {
|
||||
router: Router<H>,
|
||||
}
|
||||
|
||||
impl<H: RpcHandler> RouterBuilder<H> {
|
||||
/// Create a new [`Self`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
router: Router::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build [`Self`] into a [`Router`].
|
||||
///
|
||||
/// All endpoints enabled in [`RouterBuilder`]
|
||||
/// will be enabled in this [`Router`].
|
||||
pub fn build(self) -> Router<H> {
|
||||
self.router
|
||||
}
|
||||
|
||||
/// Enable all endpoints, including [`Self::fallback`].
|
||||
#[must_use]
|
||||
pub fn all(mut self) -> Self {
|
||||
$(
|
||||
self = self.$endpoint_ident();
|
||||
)*
|
||||
|
||||
self.fallback()
|
||||
}
|
||||
|
||||
/// Enable the catch-all fallback route.
|
||||
///
|
||||
/// Any unknown or disabled route will route here, e.g.:
|
||||
/// - `get_info`
|
||||
/// - `getinfo`
|
||||
/// - `asdf`
|
||||
#[must_use]
|
||||
pub fn fallback(self) -> Self {
|
||||
Self {
|
||||
router: self.router.fallback(fallback::fallback),
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[doc = concat!(
|
||||
"Enable the `",
|
||||
$endpoint_string,
|
||||
"` endpoint.",
|
||||
)]
|
||||
#[must_use]
|
||||
pub fn $endpoint_ident(self) -> Self {
|
||||
Self {
|
||||
router: self.router.route(
|
||||
$endpoint_string,
|
||||
::axum::routing::method_routing::MethodRouter::new()
|
||||
$(.$http_method($endpoint_module::$endpoint_fn::<H>))*
|
||||
),
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_router_builder! {
|
||||
// JSON-RPC 2.0 route.
|
||||
json_rpc => "/json_rpc" => json_rpc::json_rpc => (get, post),
|
||||
|
||||
// Other JSON routes.
|
||||
other_get_height => "/get_height" => other::get_height => (get, post),
|
||||
other_getheight => "/getheight" => other::get_height => (get, post),
|
||||
other_get_transactions => "/get_transactions" => other::get_transactions => (get, post),
|
||||
other_gettransactions => "/gettransactions" => other::get_transactions => (get, post),
|
||||
other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => other::get_alt_blocks_hashes => (get, post),
|
||||
other_is_key_image_spent => "/is_key_image_spent" => other::is_key_image_spent => (get, post),
|
||||
other_send_raw_transaction => "/send_raw_transaction" => other::send_raw_transaction => (get, post),
|
||||
other_sendrawtransaction => "/sendrawtransaction" => other::send_raw_transaction => (get, post),
|
||||
other_start_mining => "/start_mining" => other::start_mining => (get, post),
|
||||
other_stop_mining => "/stop_mining" => other::stop_mining => (get, post),
|
||||
other_mining_status => "/mining_status" => other::mining_status => (get, post),
|
||||
other_save_bc => "/save_bc" => other::save_bc => (get, post),
|
||||
other_get_peer_list => "/get_peer_list" => other::get_peer_list => (get, post),
|
||||
other_get_public_nodes => "/get_public_nodes" => other::get_public_nodes => (get, post),
|
||||
other_set_log_hash_rate => "/set_log_hash_rate" => other::set_log_hash_rate => (get, post),
|
||||
other_set_log_level => "/set_log_level" => other::set_log_level => (get, post),
|
||||
other_set_log_categories => "/set_log_categories" => other::set_log_categories => (get, post),
|
||||
other_get_transaction_pool => "/get_transaction_pool" => other::get_transaction_pool => (get, post),
|
||||
other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => other::get_transaction_pool_hashes => (get, post),
|
||||
other_get_transaction_pool_stats => "/get_transaction_pool_stats" => other::get_transaction_pool_stats => (get, post),
|
||||
other_set_bootstrap_daemon => "/set_bootstrap_daemon" => other::set_bootstrap_daemon => (get, post),
|
||||
other_stop_daemon => "/stop_daemon" => other::stop_daemon => (get, post),
|
||||
other_get_net_stats => "/get_net_stats" => other::get_net_stats => (get, post),
|
||||
other_get_limit => "/get_limit" => other::get_limit => (get, post),
|
||||
other_set_limit => "/set_limit" => other::set_limit => (get, post),
|
||||
other_out_peers => "/out_peers" => other::out_peers => (get, post),
|
||||
other_in_peers => "/in_peers" => other::in_peers => (get, post),
|
||||
other_get_outs => "/get_outs" => other::get_outs => (get, post),
|
||||
other_update => "/update" => other::update => (get, post),
|
||||
other_pop_blocks => "/pop_blocks" => other::pop_blocks => (get, post),
|
||||
|
||||
// Binary routes.
|
||||
bin_get_blocks => "/get_blocks.bin" => bin::get_blocks => (get, post),
|
||||
bin_getblocks => "/getblocks.bin" => bin::get_blocks => (get, post),
|
||||
bin_get_blocks_by_height => "/get_blocks_by_height.bin" => bin::get_blocks_by_height => (get, post),
|
||||
bin_getblocks_by_height => "/getblocks_by_height.bin" => bin::get_blocks_by_height => (get, post),
|
||||
bin_get_hashes => "/get_hashes.bin" => bin::get_hashes => (get, post),
|
||||
bin_gethashes => "/gethashes.bin" => bin::get_hashes => (get, post),
|
||||
bin_get_o_indexes => "/get_o_indexes.bin" => bin::get_o_indexes => (get, post),
|
||||
bin_get_outs => "/get_outs.bin" => bin::get_outs => (get, post),
|
||||
bin_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => bin::get_transaction_pool_hashes => (get, post),
|
||||
bin_get_output_distribution => "/get_output_distribution.bin" => bin::get_output_distribution => (get, post),
|
||||
}
|
||||
|
||||
impl<H: RpcHandler> Default for RouterBuilder<H> {
|
||||
/// Uses [`Self::all`].
|
||||
fn default() -> Self {
|
||||
Self::new().all()
|
||||
}
|
||||
}
|
34
rpc/interface/src/rpc_error.rs
Normal file
34
rpc/interface/src/rpc_error.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
//! RPC errors.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::http::StatusCode;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcError
|
||||
/// Possible errors during RPC operation.
|
||||
///
|
||||
/// These are any errors that can happen _during_ a handler function.
|
||||
/// I.e. if this error surfaces, it happened _after_ the request was
|
||||
/// deserialized.
|
||||
///
|
||||
/// This is the `Error` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
///
|
||||
/// TODO: This is empty as possible errors will be
|
||||
/// enumerated when the handler functions are created.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcError {}
|
||||
|
||||
impl From<RpcError> for StatusCode {
|
||||
fn from(value: RpcError) -> Self {
|
||||
// TODO
|
||||
Self::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
57
rpc/interface/src/rpc_handler.rs
Normal file
57
rpc/interface/src/rpc_handler.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
//! RPC handler trait.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use std::{future::Future, task::Poll};
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse};
|
||||
use futures::{channel::oneshot::channel, FutureExt};
|
||||
use tower::Service;
|
||||
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_json_rpc::Id;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest;
|
||||
|
||||
use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcHandler
|
||||
/// An RPC handler.
|
||||
///
|
||||
/// This trait represents a type that can turn [`RpcRequest`]s into [`RpcResponse`]s.
|
||||
///
|
||||
/// Implementors of this trait must be [`tower::Service`]s that use:
|
||||
/// - [`RpcRequest`] as the generic `Request` type
|
||||
/// - [`RpcResponse`] as the associated `Response` type
|
||||
/// - [`RpcError`] as the associated `Error` type
|
||||
/// - A generic [`Future`] that outputs `Result<RpcResponse, RpcError>`
|
||||
///
|
||||
/// See this crate's `RpcHandlerDummy` for an implementation example of this trait.
|
||||
///
|
||||
/// # Panics
|
||||
/// Your [`RpcHandler`] must reply to [`RpcRequest`]s with the correct
|
||||
/// [`RpcResponse`] or else this crate will panic during routing functions.
|
||||
///
|
||||
/// For example, upon a [`RpcRequest::Binary`] must be replied with
|
||||
/// [`RpcRequest::Binary`]. If an [`RpcRequest::Other`] were returned instead,
|
||||
/// this crate would panic.
|
||||
pub trait RpcHandler:
|
||||
Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
+ Service<
|
||||
RpcRequest,
|
||||
Response = RpcResponse,
|
||||
Error = RpcError,
|
||||
Future: Future<Output = Result<RpcResponse, RpcError>> + Send + Sync + 'static,
|
||||
>
|
||||
{
|
||||
/// Is this [`RpcHandler`] restricted?
|
||||
///
|
||||
/// If this returns `true`, restricted methods and endpoints such as:
|
||||
/// - `/json_rpc`'s `relay_tx` method
|
||||
/// - The `/pop_blocks` endpoint
|
||||
///
|
||||
/// will automatically be denied access when using the
|
||||
/// [`axum::Router`] provided by [`RouterBuilder`](crate::RouterBuilder).
|
||||
fn restricted(&self) -> bool;
|
||||
}
|
142
rpc/interface/src/rpc_handler_dummy.rs
Normal file
142
rpc/interface/src/rpc_handler_dummy.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
//! Dummy implementation of [`RpcHandler`].
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{channel::oneshot::channel, FutureExt};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower::Service;
|
||||
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_json_rpc::Id;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest;
|
||||
|
||||
use crate::{
|
||||
rpc_error::RpcError, rpc_handler::RpcHandler, rpc_request::RpcRequest,
|
||||
rpc_response::RpcResponse,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcHandlerDummy
|
||||
/// An [`RpcHandler`] that always returns [`Default::default`].
|
||||
///
|
||||
/// This `struct` implements [`RpcHandler`], and always responds
|
||||
/// with the response `struct` set to [`Default::default`].
|
||||
///
|
||||
/// See the [`crate`] documentation for example usage.
|
||||
///
|
||||
/// This is mostly used for testing purposes and can
|
||||
/// be disabled by disable the `dummy` feature flag.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct RpcHandlerDummy {
|
||||
/// Should this RPC server be [restricted](RpcHandler::restricted)?
|
||||
///
|
||||
/// The dummy will honor this [`bool`]
|
||||
/// on restricted methods/endpoints.
|
||||
pub restricted: bool,
|
||||
}
|
||||
|
||||
impl RpcHandler for RpcHandlerDummy {
|
||||
fn restricted(&self) -> bool {
|
||||
self.restricted
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<RpcRequest> for RpcHandlerDummy {
|
||||
type Response = RpcResponse;
|
||||
type Error = RpcError;
|
||||
type Future = InfallibleOneshotReceiver<Result<RpcResponse, RpcError>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: RpcRequest) -> Self::Future {
|
||||
use cuprate_rpc_types::bin::BinRequest as BReq;
|
||||
use cuprate_rpc_types::bin::BinResponse as BResp;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest as JReq;
|
||||
use cuprate_rpc_types::json::JsonRpcResponse as JResp;
|
||||
use cuprate_rpc_types::other::OtherRequest as OReq;
|
||||
use cuprate_rpc_types::other::OtherResponse as OResp;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(clippy::default_trait_access)]
|
||||
let resp = match req {
|
||||
RpcRequest::JsonRpc(j) => RpcResponse::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body {
|
||||
JReq::GetBlockCount(_) => JResp::GetBlockCount(Default::default()),
|
||||
JReq::OnGetBlockHash(_) => JResp::OnGetBlockHash(Default::default()),
|
||||
JReq::SubmitBlock(_) => JResp::SubmitBlock(Default::default()),
|
||||
JReq::GenerateBlocks(_) => JResp::GenerateBlocks(Default::default()),
|
||||
JReq::GetLastBlockHeader(_) => JResp::GetLastBlockHeader(Default::default()),
|
||||
JReq::GetBlockHeaderByHash(_) => JResp::GetBlockHeaderByHash(Default::default()),
|
||||
JReq::GetBlockHeaderByHeight(_) => JResp::GetBlockHeaderByHeight(Default::default()),
|
||||
JReq::GetBlockHeadersRange(_) => JResp::GetBlockHeadersRange(Default::default()),
|
||||
JReq::GetBlock(_) => JResp::GetBlock(Default::default()),
|
||||
JReq::GetConnections(_) => JResp::GetConnections(Default::default()),
|
||||
JReq::GetInfo(_) => JResp::GetInfo(Default::default()),
|
||||
JReq::HardForkInfo(_) => JResp::HardForkInfo(Default::default()),
|
||||
JReq::SetBans(_) => JResp::SetBans(Default::default()),
|
||||
JReq::GetBans(_) => JResp::GetBans(Default::default()),
|
||||
JReq::Banned(_) => JResp::Banned(Default::default()),
|
||||
JReq::FlushTransactionPool(_) => JResp::FlushTransactionPool(Default::default()),
|
||||
JReq::GetOutputHistogram(_) => JResp::GetOutputHistogram(Default::default()),
|
||||
JReq::GetCoinbaseTxSum(_) => JResp::GetCoinbaseTxSum(Default::default()),
|
||||
JReq::GetVersion(_) => JResp::GetVersion(Default::default()),
|
||||
JReq::GetFeeEstimate(_) => JResp::GetFeeEstimate(Default::default()),
|
||||
JReq::GetAlternateChains(_) => JResp::GetAlternateChains(Default::default()),
|
||||
JReq::RelayTx(_) => JResp::RelayTx(Default::default()),
|
||||
JReq::SyncInfo(_) => JResp::SyncInfo(Default::default()),
|
||||
JReq::GetTransactionPoolBacklog(_) => JResp::GetTransactionPoolBacklog(Default::default()),
|
||||
JReq::GetMinerData(_) => JResp::GetMinerData(Default::default()),
|
||||
JReq::PruneBlockchain(_) => JResp::PruneBlockchain(Default::default()),
|
||||
JReq::CalcPow(_) => JResp::CalcPow(Default::default()),
|
||||
JReq::FlushCache(_) => JResp::FlushCache(Default::default()),
|
||||
JReq::AddAuxPow(_) => JResp::AddAuxPow(Default::default()),
|
||||
JReq::GetTxIdsLoose(_) => JResp::GetTxIdsLoose(Default::default()),
|
||||
})),
|
||||
RpcRequest::Binary(b) => RpcResponse::Binary(match b {
|
||||
BReq::GetBlocks(_) => BResp::GetBlocks(Default::default()),
|
||||
BReq::GetBlocksByHeight(_) => BResp::GetBlocksByHeight(Default::default()),
|
||||
BReq::GetHashes(_) => BResp::GetHashes(Default::default()),
|
||||
BReq::GetOutputIndexes(_) => BResp::GetOutputIndexes(Default::default()),
|
||||
BReq::GetOuts(_) => BResp::GetOuts(Default::default()),
|
||||
BReq::GetTransactionPoolHashes(_) => BResp::GetTransactionPoolHashes(Default::default()),
|
||||
BReq::GetOutputDistribution(_) => BResp::GetOutputDistribution(Default::default()),
|
||||
}),
|
||||
RpcRequest::Other(o) => RpcResponse::Other(match o {
|
||||
OReq::GetHeight(_) => OResp::GetHeight(Default::default()),
|
||||
OReq::GetTransactions(_) => OResp::GetTransactions(Default::default()),
|
||||
OReq::GetAltBlocksHashes(_) => OResp::GetAltBlocksHashes(Default::default()),
|
||||
OReq::IsKeyImageSpent(_) => OResp::IsKeyImageSpent(Default::default()),
|
||||
OReq::SendRawTransaction(_) => OResp::SendRawTransaction(Default::default()),
|
||||
OReq::StartMining(_) => OResp::StartMining(Default::default()),
|
||||
OReq::StopMining(_) => OResp::StopMining(Default::default()),
|
||||
OReq::MiningStatus(_) => OResp::MiningStatus(Default::default()),
|
||||
OReq::SaveBc(_) => OResp::SaveBc(Default::default()),
|
||||
OReq::GetPeerList(_) => OResp::GetPeerList(Default::default()),
|
||||
OReq::SetLogHashRate(_) => OResp::SetLogHashRate(Default::default()),
|
||||
OReq::SetLogLevel(_) => OResp::SetLogLevel(Default::default()),
|
||||
OReq::SetLogCategories(_) => OResp::SetLogCategories(Default::default()),
|
||||
OReq::SetBootstrapDaemon(_) => OResp::SetBootstrapDaemon(Default::default()),
|
||||
OReq::GetTransactionPool(_) => OResp::GetTransactionPool(Default::default()),
|
||||
OReq::GetTransactionPoolStats(_) => OResp::GetTransactionPoolStats(Default::default()),
|
||||
OReq::StopDaemon(_) => OResp::StopDaemon(Default::default()),
|
||||
OReq::GetLimit(_) => OResp::GetLimit(Default::default()),
|
||||
OReq::SetLimit(_) => OResp::SetLimit(Default::default()),
|
||||
OReq::OutPeers(_) => OResp::OutPeers(Default::default()),
|
||||
OReq::InPeers(_) => OResp::InPeers(Default::default()),
|
||||
OReq::GetNetStats(_) => OResp::GetNetStats(Default::default()),
|
||||
OReq::GetOuts(_) => OResp::GetOuts(Default::default()),
|
||||
OReq::Update(_) => OResp::Update(Default::default()),
|
||||
OReq::PopBlocks(_) => OResp::PopBlocks(Default::default()),
|
||||
OReq::GetTransactionPoolHashes(_) => OResp::GetTransactionPoolHashes(Default::default()),
|
||||
OReq::GetPublicNodes(_) => OResp::GetPublicNodes(Default::default()),
|
||||
})
|
||||
};
|
||||
|
||||
let (tx, rx) = channel();
|
||||
drop(tx.send(Ok(resp)));
|
||||
InfallibleOneshotReceiver::from(rx)
|
||||
}
|
||||
}
|
33
rpc/interface/src/rpc_request.rs
Normal file
33
rpc/interface/src/rpc_request.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//! RPC requests.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcRequest
|
||||
/// All possible RPC requests.
|
||||
///
|
||||
/// This enum encapsulates all possible RPC requests:
|
||||
/// - JSON RPC 2.0 requests
|
||||
/// - Binary requests
|
||||
/// - Other JSON requests
|
||||
///
|
||||
/// It is the `Request` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcRequest {
|
||||
/// JSON-RPC 2.0 requests.
|
||||
JsonRpc(cuprate_json_rpc::Request<JsonRpcRequest>),
|
||||
/// Binary requests.
|
||||
Binary(BinRequest),
|
||||
/// Other JSON requests.
|
||||
Other(OtherRequest),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
33
rpc/interface/src/rpc_response.rs
Normal file
33
rpc/interface/src/rpc_response.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//! RPC responses.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcResponse
|
||||
/// All possible RPC responses.
|
||||
///
|
||||
/// This enum encapsulates all possible RPC responses:
|
||||
/// - JSON RPC 2.0 responses
|
||||
/// - Binary responses
|
||||
/// - Other JSON responses
|
||||
///
|
||||
/// It is the `Response` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcResponse {
|
||||
/// JSON RPC 2.0 responses.
|
||||
JsonRpc(cuprate_json_rpc::Response<JsonRpcResponse>),
|
||||
/// Binary responses.
|
||||
Binary(BinResponse),
|
||||
/// Other JSON responses.
|
||||
Other(OtherResponse),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
Loading…
Reference in a new issue