rpc: start cuprate-rpc-types (#147)

* rpc: add `monero-rpc-types`

* lib.rs: add lints

* add base files, deps

* fix macro generation, doc test

* add `strum`, add `misc` module

* document struct generation macro

* add `GetHeight`

* lib.rs: create re-export macro

* macro changes, add few more types

* docs

* `monero-rpc-types` -> `cuprate-rpc-types`

* fix modules

* specify commit in macro, add () type aliases

* macro docs, fixes

* add `Status::Other(String)`

* add TODO for `strum`

* Update rpc/types/Cargo.toml

Co-authored-by: Boog900 <boog900@tutanota.com>

* add `BinaryString`

* add `ResponseBase`

* add `CORE_RPC_*` constants

* fix status; use `CORE_RPC_*` constants

* cargo.toml: add `epee_encoding`

* rpc: add epee_encoding impl for `Status`

* macro: add epee_encoding for every type

* remove `strum`

* add response bases

* add `CORE_RPC_STATUS_UNKNOWN`

* add response/request bases for epee

* create `base` module

* use different type for macro example

* move base / root types around

* docs, status serde test

* status: use `Status::Unknown` for `epee_default_value`

* json: add missing fields to `GetBlockTemplateRequest`

not sure I missed these

cc73fe7116/src/rpc/core_rpc_server_commands_defs.h (L947-L950)

---------

Co-authored-by: Boog900 <boog900@tutanota.com>
This commit is contained in:
hinto-janai 2024-06-26 17:24:05 -04:00 committed by GitHub
parent 5c08d1a0e2
commit e405786a73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1035 additions and 5 deletions

7
Cargo.lock generated
View file

@ -745,6 +745,13 @@ version = "0.0.0"
[[package]]
name = "cuprate-rpc-types"
version = "0.0.0"
dependencies = [
"cuprate-epee-encoding",
"monero-serai",
"paste",
"serde",
"serde_json",
]
[[package]]
name = "cuprate-test-utils"

View file

@ -23,8 +23,8 @@ members = [
"test-utils",
"types",
"rpc/json-rpc",
"rpc/rpc-types",
"rpc/rpc-interface",
"rpc/types",
"rpc/interface",
]
[profile.release]

View file

@ -1 +0,0 @@

View file

@ -5,11 +5,18 @@ edition = "2021"
description = "Monero RPC types"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/monero-rpc-types"
keywords = ["monero", "rpc", "types"]
repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/types"
keywords = ["cuprate", "rpc", "types", "monero"]
[features]
default = []
[dependencies]
cuprate-epee-encoding = { path = "../../net/epee-encoding" }
monero-serai = { workspace = true }
paste = { workspace = true }
serde = { workspace = true }
[dev-dependencies]
serde_json = { workspace = true }

62
rpc/types/README.md Normal file
View file

@ -0,0 +1,62 @@
Monero RPC types.
# What
This crate ports the types used in Monero's RPC interface, including:
- JSON types
- Binary (epee) types
- Mixed types
- Other commonly used RPC types
# Modules
This crate's types are split in the following manner:
This crate has 4 modules:
- The root module; `cuprate_rpc_types`
- [`json`] module; JSON types from the `/json_rpc` endpoint
- [`bin`] module; Binary types from the binary endpoints
- [`other`] module; Misc JSON types from other endpoints
Miscellaneous types are found in the root module, e.g. [`crate::Status`].
Each type in `{json,bin,other}` come in pairs and have identical names, but are suffixed with either `Request` or `Response`. e.g. [`GetBlockCountRequest`](crate::json::GetBlockCountRequest) & [`GetBlockCountResponse`](crate::json::GetBlockCountResponse).
# Documentation
The documentation for types within `{json,bin,other}` are omitted, as they can be found in [Monero's RPC documentation](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html).
However, each type will document:
- **Definition**: the exact type definition location in `monerod`
- **Documentation**: the Monero RPC documentation link
- **Request/response**: the other side of this type, either the request or response
# Naming
The naming for types within `{json,bin,other}` follow the following scheme:
- Convert the endpoint or method name into `UpperCamelCase`
- Remove any suffix extension
For example:
| Endpoint/method | Crate location and name |
|-----------------|-------------------------|
| [`get_block_count`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count) | [`json::GetBlockCountRequest`] & [`json::GetBlockCountResponse`]
| [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blockbin) | `bin::GetBlocksRequest` & `bin::GetBlocksResponse`
| [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) | `other::GetHeightRequest` & `other::GetHeightResponse`
TODO: fix doc links when types are ready.
# Mixed types
Note that some types within [`other`] mix JSON & binary together, i.e.,
the message overall is JSON, however some fields contain binary
values inside JSON strings, for example:
```json
{
"string": "",
"float": 30.0,
"integer": 30,
"binary": "<serialized binary>"
}
```
`binary` here is (de)serialized as a normal [`String`]. In order to be clear on which fields contain binary data, the struct fields that have them will use [`crate::BinaryString`] instead of [`String`].
TODO: list the specific types.

125
rpc/types/src/base.rs Normal file
View file

@ -0,0 +1,125 @@
//! The base data that appear in many RPC request/responses.
//!
//! These are the common "headers" or "base" types that are
//! [`flattened`](https://serde.rs/field-attrs.html#flatten)
//! into many of Monero's RPC types.
//!
//! The `Access*` structs (e.g. [`AccessResponseBase`]
//! are pseudo-deprecated structs for the RPC payment system, see:
//!
//! - <https://github.com/monero-project/monero/commit/2899379791b7542e4eb920b5d9d58cf232806937>
//! - <https://github.com/monero-project/monero/issues/8722>
//! - <https://github.com/monero-project/monero/pull/8843>
//---------------------------------------------------------------------------------------------------- Import
use serde::{Deserialize, Serialize};
use cuprate_epee_encoding::epee_object;
use crate::Status;
//---------------------------------------------------------------------------------------------------- Macro
/// Link the original `monerod` definition for RPC base types.
macro_rules! monero_rpc_base_link {
($start:literal..=$end:literal) => {
concat!(
"[Definition](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L",
stringify!($start),
"-L",
stringify!($end),
")."
)
};
}
//---------------------------------------------------------------------------------------------------- Requests
/// The most common base for responses (nothing).
///
#[doc = monero_rpc_base_link!(95..=99)]
#[derive(
Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct EmptyRequestBase;
cuprate_epee_encoding::epee_object! {
EmptyRequestBase,
}
/// A base for RPC request types that support RPC payment.
///
#[doc = monero_rpc_base_link!(114..=122)]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AccessRequestBase {
/// The RPC payment client.
pub client: String,
}
cuprate_epee_encoding::epee_object! {
AccessRequestBase,
client: String,
}
//---------------------------------------------------------------------------------------------------- Responses
/// An empty response base.
///
/// This is for response types that do not contain
/// any extra fields, e.g. TODO.
// [`CalcPowResponse`](crate::json::CalcPowResponse).
#[derive(
Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct EmptyResponseBase;
cuprate_epee_encoding::epee_object! {
EmptyResponseBase,
}
/// The most common base for responses.
///
#[doc = monero_rpc_base_link!(101..=112)]
#[derive(
Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct ResponseBase {
/// General RPC error code. [`Status::Ok`] means everything looks good.
pub status: Status,
/// States if the result is obtained using the bootstrap mode,
/// and is therefore not trusted (`true`), or when the daemon
/// is fully synced and thus handles the RPC locally (`false`).
pub untrusted: bool,
}
epee_object! {
ResponseBase,
status: Status,
untrusted: bool,
}
/// A base for RPC response types that support RPC payment.
///
#[doc = monero_rpc_base_link!(124..=136)]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AccessResponseBase {
/// A flattened [`ResponseBase`].
#[serde(flatten)]
pub response_base: ResponseBase,
/// If payment for RPC is enabled, the number of credits
/// available to the requesting client. Otherwise, `0`.
pub credits: u64,
/// If payment for RPC is enabled, the hash of the
/// highest block in the chain. Otherwise, empty.
pub top_hash: String,
}
epee_object! {
AccessResponseBase,
credits: u64,
top_hash: String,
!flatten: response_base: ResponseBase,
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

11
rpc/types/src/bin.rs Normal file
View file

@ -0,0 +1,11 @@
//! Binary types from [binary](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin) endpoints.
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- TODO
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -0,0 +1,29 @@
//! TODO
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- BinaryString
/// TODO
///
/// ```rust
/// use serde::Deserialize;
/// use serde_json::from_str;
/// use cuprate_rpc_types::BinaryString;
///
/// #[derive(Deserialize)]
/// struct Key {
/// key: BinaryString,
/// }
///
/// let binary = r"<22>\b<><62><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
/// let json = format!("{{\"key\":\"{binary}\"}}");
/// let key = from_str::<Key>(&json).unwrap();
/// let binary: BinaryString = key.key;
/// ```
pub type BinaryString = String;
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

View file

@ -0,0 +1,65 @@
//! TODO
// From: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L83-L89>
//
// ```
// When making *any* change here, bump minor
// If the change is incompatible, then bump major and set minor to 0
// This ensures CORE_RPC_VERSION always increases, that every change
// has its own version, and that clients can just test major to see
// whether they can talk to a given daemon without having to know in
// advance which version they will stop working with
// Don't go over 32767 for any of these
// ```
//
// What this means for Cuprate: just follow `monerod`.
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- Status
// Common RPC status strings:
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L78-L81>.
//
// Note that these are _distinct_ from the ones in ZMQ:
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/message.cpp#L40-L44>.
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L78>
pub const CORE_RPC_STATUS_OK: &str = "OK";
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L79>
pub const CORE_RPC_STATUS_BUSY: &str = "BUSY";
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L80>
pub const CORE_RPC_STATUS_NOT_MINING: &str = "NOT MINING";
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L81>
pub const CORE_RPC_STATUS_PAYMENT_REQUIRED: &str = "PAYMENT REQUIRED";
/// Custom `CORE_RPC_STATUS` for usage in Cuprate.
pub const CORE_RPC_STATUS_UNKNOWN: &str = "UNKNOWN";
//---------------------------------------------------------------------------------------------------- Versions
/// RPC major version.
///
/// See: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L90>.
pub const CORE_RPC_VERSION_MAJOR: u32 = 3;
/// RPC miror version.
///
/// See: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L91>.
pub const CORE_RPC_VERSION_MINOR: u32 = 14;
/// RPC version.
///
/// See: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L92-L93>.
///
/// ```rust
/// assert_eq!(cuprate_rpc_types::CORE_RPC_VERSION, 196_622);
/// ```
pub const CORE_RPC_VERSION: u32 = (CORE_RPC_VERSION_MAJOR << 16) | CORE_RPC_VERSION_MINOR;
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

129
rpc/types/src/json.rs Normal file
View file

@ -0,0 +1,129 @@
//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint.
//!
//! <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/daemon_messages.h>.
//---------------------------------------------------------------------------------------------------- Import
use crate::{
base::{EmptyRequestBase, EmptyResponseBase, ResponseBase},
macros::define_request_and_response,
};
//---------------------------------------------------------------------------------------------------- Struct definitions
// This generates 2 structs:
//
// - `GetBlockTemplateRequest`
// - `GetBlockTemplateResponse`
//
// with some interconnected documentation.
define_request_and_response! {
// The markdown tag for Monero RPC documentation. Not necessarily the endpoint.
get_block_template,
// The commit hash and `$file.$extension` in which this type is defined in
// the Monero codebase in the `rpc/` directory, followed by the specific lines.
cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994,
// The base type name.
GetBlockTemplate,
// The base request type.
//
// This must be a type found in [`crate::base`].
// It acts as a "base" that gets flattened into
// the actually request type.
//
// "Flatten" means the field(s) of a struct gets inlined
// directly into the struct during (de)serialization, see:
// <https://serde.rs/field-attrs.html#flatten>.
//
// For example here, we're using [`crate::base::EmptyRequestBase`],
// which means that there is no extra fields flattened.
//
// If a request is not specified here, it will create a `type alias YOUR_REQUEST_TYPE = ()`
// instead of a `struct`, see below in other macro definitions for an example.
EmptyRequestBase {
reserve_size: u64,
wallet_address: String,
prev_block: String,
extra_nonce: String,
},
// The base response type.
//
// This is the same as the request base type,
// it must be a type found in [`crate::base`].
//
// If there are any additional attributes (`/// docs` or `#[derive]`s)
// for the struct, they go here, e.g.:
// #[derive(Copy)]
ResponseBase {
// This is using `crate::base::ResponseBase`,
// so the type we generate will contain this field:
// ```
// base: crate::base::ResponseBase,
// ```
//
// This is flattened with serde and epee, so during
// (de)serialization, it will act as if there are 2 extra fields here:
// ```
// status: crate::Status,
// untrusted: bool,
// ```
// Within the `{}` is an infinite matching pattern of:
// ```
// $ATTRIBUTES
// $FIELD_NAME: $FIELD_TYPE,
// ```
// The struct generated and all fields are `pub`.
difficulty: u64,
wide_difficulty: String,
difficulty_top64: u64,
height: u64,
reserved_offset: u64,
expected_reward: u64,
prev_hash: String,
seed_height: u64,
seed_hash: String,
next_seed_hash: String,
blocktemplate_blob: String,
blockhashing_blob: String,
}
}
define_request_and_response! {
get_block_count,
cc73fe71162d564ffda8e549b79a350bca53c454 =>
core_rpc_server_commands_defs.h => 919..=933,
GetBlockCount,
// There is no request type specified,
// this will cause the macro to generate a
// type alias to `()` instead of a `struct`.
ResponseBase {
count: u64,
}
}
define_request_and_response! {
on_get_block_hash,
cc73fe71162d564ffda8e549b79a350bca53c454 =>
core_rpc_server_commands_defs.h => 935..=939,
OnGetBlockHash,
#[derive(Copy)]
EmptyRequestBase {
#[serde(flatten)]
block_height: u64,
},
EmptyResponseBase {
#[serde(flatten)]
block_hash: String,
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

116
rpc/types/src/lib.rs Normal file
View file

@ -0,0 +1,116 @@
#![doc = include_str!("../README.md")]
//---------------------------------------------------------------------------------------------------- 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,
clippy::missing_docs_in_private_items,
// 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))]
// 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)]
//---------------------------------------------------------------------------------------------------- Use
mod binary_string;
mod constants;
mod macros;
mod status;
pub use binary_string::BinaryString;
pub use constants::{
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN, CORE_RPC_VERSION,
CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR,
};
pub use status::Status;
pub mod base;
pub mod bin;
pub mod json;
pub mod other;

277
rpc/types/src/macros.rs Normal file
View file

@ -0,0 +1,277 @@
//! Macros.
//---------------------------------------------------------------------------------------------------- Struct definition
/// A template for generating 2 `struct`s with a bunch of information filled out.
///
/// These are the RPC request and response `struct`s.
///
/// These `struct`s automatically implement:
/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash`
/// - `serde::{Serialize, Deserialize}`
/// - `epee_encoding::EpeeObject`
///
/// It's best to see the output of this macro via the documentation
/// of the generated structs via `cargo doc`s to see which parts
/// generate which docs.
///
/// See the [`crate::json`] module for example usage.
///
/// # Macro internals
/// This macro has 2 branches with almost the same output:
/// 1. An empty `Request` type
/// 2. An `Request` type with fields
///
/// The first branch is the same as the second with the exception
/// that if the caller of this macro provides no fields, it will
/// generate:
/// ```
/// pub type Request = ();
/// ```
/// instead of:
/// ```
/// pub struct Request {/* fields */}
/// ```
///
/// This is because having a bunch of types that are all empty structs
/// means they are not compatible and it makes it cumbersome for end-users.
/// Really, they semantically are empty types, so `()` is used.
///
/// Again, other than this, the 2 branches do (should) not differ.
///
/// FIXME: there's probably a less painful way to branch here on input
/// without having to duplicate 80% of the macro. Sub-macros were attempted
/// but they ended up unreadable. So for now, make sure to fix the other
/// branch as well when making changes. The only de-duplicated part is
/// the doc generation with [`define_request_and_response_doc`].
macro_rules! define_request_and_response {
//------------------------------------------------------------------------------
// This version of the macro expects a `Request` type with no fields, i.e. `Request {}`.
(
// The markdown tag for Monero RPC documentation. Not necessarily the endpoint.
$monero_daemon_rpc_doc_link:ident,
// The commit hash and `$file.$extension` in which this type is defined in
// the Monero codebase in the `rpc/` directory, followed by the specific lines.
$monero_code_commit:ident =>
$monero_code_filename:ident.
$monero_code_filename_extension:ident =>
$monero_code_line_start:literal..=
$monero_code_line_end:literal,
// The base `struct` name.
$type_name:ident,
// The response type (and any doc comments, derives, etc).
$( #[$response_type_attr:meta] )*
$response_base_type:ty {
// And any fields.
$(
$( #[$response_field_attr:meta] )*
$response_field:ident: $response_field_type:ty,
)*
}
) => { paste::paste! {
#[doc = $crate::macros::define_request_and_response_doc!(
"response",
$monero_daemon_rpc_doc_link,
$monero_code_commit,
$monero_code_filename,
$monero_code_filename_extension,
$monero_code_line_start,
$monero_code_line_end,
[<$type_name Request>],
)]
///
/// This request has no inputs.
pub type [<$type_name Request>] = ();
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
$( #[$response_type_attr] )*
#[doc = $crate::macros::define_request_and_response_doc!(
"request",
$monero_daemon_rpc_doc_link,
$monero_code_commit,
$monero_code_filename,
$monero_code_filename_extension,
$monero_code_line_start,
$monero_code_line_end,
[<$type_name Response>],
)]
pub struct [<$type_name Response>] {
#[serde(flatten)]
pub base: $response_base_type,
$(
$( #[$response_field_attr] )*
pub $response_field: $response_field_type,
)*
}
::cuprate_epee_encoding::epee_object! {
[<$type_name Response>],
$(
$response_field: $response_field_type,
)*
!flatten: base: $response_base_type,
}
}};
//------------------------------------------------------------------------------
// This version of the macro expects a `Request` type with fields.
(
// The markdown tag for Monero RPC documentation. Not necessarily the endpoint.
$monero_daemon_rpc_doc_link:ident,
// The commit hash and `$file.$extension` in which this type is defined in
// the Monero codebase in the `rpc/` directory, followed by the specific lines.
$monero_code_commit:ident =>
$monero_code_filename:ident.
$monero_code_filename_extension:ident =>
$monero_code_line_start:literal..=
$monero_code_line_end:literal,
// The base `struct` name.
$type_name:ident,
// The request type (and any doc comments, derives, etc).
$( #[$request_type_attr:meta] )*
$request_base_type:ty {
// And any fields.
$(
$( #[$request_field_attr:meta] )*
$request_field:ident: $request_field_type:ty,
)*
},
// The response type (and any doc comments, derives, etc).
$( #[$response_type_attr:meta] )*
$response_base_type:ty {
// And any fields.
$(
$( #[$response_field_attr:meta] )*
$response_field:ident: $response_field_type:ty,
)*
}
) => { paste::paste! {
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
$( #[$request_type_attr] )*
#[doc = $crate::macros::define_request_and_response_doc!(
"response",
$monero_daemon_rpc_doc_link,
$monero_code_commit,
$monero_code_filename,
$monero_code_filename_extension,
$monero_code_line_start,
$monero_code_line_end,
[<$type_name Request>],
)]
pub struct [<$type_name Request>] {
#[serde(flatten)]
pub base: $request_base_type,
$(
$( #[$request_field_attr] )*
pub $request_field: $request_field_type,
)*
}
::cuprate_epee_encoding::epee_object! {
[<$type_name Request>],
$(
$request_field: $request_field_type,
)*
!flatten: base: $request_base_type,
}
#[allow(dead_code)]
#[allow(missing_docs)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
$( #[$response_type_attr] )*
#[doc = $crate::macros::define_request_and_response_doc!(
"request",
$monero_daemon_rpc_doc_link,
$monero_code_commit,
$monero_code_filename,
$monero_code_filename_extension,
$monero_code_line_start,
$monero_code_line_end,
[<$type_name Response>],
)]
pub struct [<$type_name Response>] {
#[serde(flatten)]
pub base: $response_base_type,
$(
$( #[$response_field_attr] )*
pub $response_field: $response_field_type,
)*
}
::cuprate_epee_encoding::epee_object! {
[<$type_name Response>],
$(
$response_field: $response_field_type,
)*
!flatten: base: $response_base_type,
}
}};
}
pub(crate) use define_request_and_response;
/// Generate documentation for the types generated
/// by the [`define_request_and_response`] macro.
///
/// See it for more info on inputs.
macro_rules! define_request_and_response_doc {
(
// This labels the last `[request]` or `[response]`
// hyperlink in documentation. Input is either:
// - "request"
// - "response"
//
// Remember this is linking to the _other_ type,
// so if defining a `Request` type, input should
// be "response".
$request_or_response:literal,
$monero_daemon_rpc_doc_link:ident,
$monero_code_commit:ident,
$monero_code_filename:ident,
$monero_code_filename_extension:ident,
$monero_code_line_start:literal,
$monero_code_line_end:literal,
$type_name:ident,
) => {
concat!(
"",
"[Definition](",
"https://github.com/monero-project/monero/blob/",
stringify!($monero_code_commit),
"/src/rpc/",
stringify!($monero_code_filename),
".",
stringify!($monero_code_filename_extension),
"#L",
stringify!($monero_code_line_start),
"-L",
stringify!($monero_code_line_end),
"), [documentation](",
"https://www.getmonero.org/resources/developer-guides/daemon-rpc.html",
"#",
stringify!($monero_daemon_rpc_doc_link),
"), [",
$request_or_response,
"](",
stringify!($type_name),
")."
)
};
}
pub(crate) use define_request_and_response_doc;

21
rpc/types/src/other.rs Normal file
View file

@ -0,0 +1,21 @@
//! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints.
//!
//! <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/daemon_messages.h>.
//---------------------------------------------------------------------------------------------------- Import
use crate::{base::ResponseBase, macros::define_request_and_response};
//---------------------------------------------------------------------------------------------------- TODO
define_request_and_response! {
save_bc,
cc73fe71162d564ffda8e549b79a350bca53c454 =>
core_rpc_server_commands_defs.h => 898..=916,
SaveBc,
ResponseBase {}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
// use super::*;
}

182
rpc/types/src/status.rs Normal file
View file

@ -0,0 +1,182 @@
//! RPC response status type.
//---------------------------------------------------------------------------------------------------- Import
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use cuprate_epee_encoding::{
macros::bytes::{Buf, BufMut},
EpeeValue, Marker,
};
use crate::constants::{
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN,
};
//---------------------------------------------------------------------------------------------------- Status
/// RPC response status.
///
/// This type represents `monerod`'s frequently appearing string field, `status`.
///
/// This field appears within RPC [JSON response](crate::json) types.
///
/// Reference: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L78-L81>.
///
/// ## Serialization and string formatting
/// ```rust
/// use cuprate_rpc_types::{
/// Status,
/// CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
/// CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN
/// };
/// use serde_json::to_string;
///
/// let unknown = Status::Unknown;
///
/// assert_eq!(to_string(&Status::Ok).unwrap(), r#""OK""#);
/// assert_eq!(to_string(&Status::Busy).unwrap(), r#""BUSY""#);
/// assert_eq!(to_string(&Status::NotMining).unwrap(), r#""NOT MINING""#);
/// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#);
/// assert_eq!(to_string(&unknown).unwrap(), r#""UNKNOWN""#);
///
/// assert_eq!(Status::Ok.as_ref(), CORE_RPC_STATUS_OK);
/// assert_eq!(Status::Busy.as_ref(), CORE_RPC_STATUS_BUSY);
/// assert_eq!(Status::NotMining.as_ref(), CORE_RPC_STATUS_NOT_MINING);
/// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED);
/// assert_eq!(unknown.as_ref(), CORE_RPC_STATUS_UNKNOWN);
///
/// assert_eq!(format!("{}", Status::Ok), CORE_RPC_STATUS_OK);
/// assert_eq!(format!("{}", Status::Busy), CORE_RPC_STATUS_BUSY);
/// assert_eq!(format!("{}", Status::NotMining), CORE_RPC_STATUS_NOT_MINING);
/// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED);
/// assert_eq!(format!("{}", unknown), CORE_RPC_STATUS_UNKNOWN);
///
/// assert_eq!(format!("{:?}", Status::Ok), "Ok");
/// assert_eq!(format!("{:?}", Status::Busy), "Busy");
/// assert_eq!(format!("{:?}", Status::NotMining), "NotMining");
/// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired");
/// assert_eq!(format!("{:?}", unknown), "Unknown");
/// ```
#[derive(
Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub enum Status {
// FIXME:
// `#[serde(rename = "")]` only takes raw string literals?
// We have to re-type the constants here...
/// Successful RPC response, everything is OK; [`CORE_RPC_STATUS_OK`].
#[serde(rename = "OK")]
#[default]
Ok,
/// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`].
#[serde(rename = "BUSY")]
Busy,
/// The daemon is not mining; [`CORE_RPC_STATUS_NOT_MINING`].
#[serde(rename = "NOT MINING")]
NotMining,
/// Payment is required for RPC; [`CORE_RPC_STATUS_PAYMENT_REQUIRED`].
#[serde(rename = "PAYMENT REQUIRED")]
PaymentRequired,
/// Some unknown other string; [`CORE_RPC_STATUS_UNKNOWN`].
///
/// This exists to act as a catch-all if `monerod` adds
/// a string and a Cuprate node hasn't updated yet.
///
/// The reason this isn't `Unknown(String)` is because that
/// disallows [`Status`] to be [`Copy`], and thus other types
/// that contain it.
#[serde(other)]
#[serde(rename = "UNKNOWN")]
Unknown,
}
impl From<String> for Status {
fn from(s: String) -> Self {
match s.as_str() {
CORE_RPC_STATUS_OK => Self::Ok,
CORE_RPC_STATUS_BUSY => Self::Busy,
CORE_RPC_STATUS_NOT_MINING => Self::NotMining,
CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired,
_ => Self::Unknown,
}
}
}
impl AsRef<str> for Status {
fn as_ref(&self) -> &str {
match self {
Self::Ok => CORE_RPC_STATUS_OK,
Self::Busy => CORE_RPC_STATUS_BUSY,
Self::NotMining => CORE_RPC_STATUS_NOT_MINING,
Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED,
Self::Unknown => CORE_RPC_STATUS_UNKNOWN,
}
}
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_ref())
}
}
// [`Status`] is essentially a [`String`] when it comes to
// (de)serialization, except when writing we usually have
// access to a `&'static str` and don't need to allocate.
//
// See below for more impl info:
// <https://github.com/Cuprate/cuprate/blob/bef2a2cbd4e1194991751d1fbc96603cba8c7a51/net/epee-encoding/src/value.rs#L366-L392>.
impl EpeeValue for Status {
const MARKER: Marker = <String as EpeeValue>::MARKER;
fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> {
let string = <String as EpeeValue>::read(r, marker)?;
Ok(Self::from(string))
}
fn should_write(&self) -> bool {
true
}
fn epee_default_value() -> Option<Self> {
// <https://github.com/Cuprate/cuprate/pull/147#discussion_r1654992559>
Some(Self::Unknown)
}
fn write<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::Result<()> {
cuprate_epee_encoding::write_bytes(self.as_ref(), w)
}
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use super::*;
// Test epee (de)serialization works.
#[test]
fn epee() {
for status in [
Status::Ok,
Status::Busy,
Status::NotMining,
Status::PaymentRequired,
Status::Unknown,
] {
let mut buf = vec![];
<Status as EpeeValue>::write(status, &mut buf).unwrap();
let status2 =
<Status as EpeeValue>::read(&mut buf.as_slice(), &<Status as EpeeValue>::MARKER)
.unwrap();
assert_eq!(status, status2);
}
}
}