From 3557ee63bf90c6a2a2cdfd337dbdf79d25796e77 Mon Sep 17 00:00:00 2001 From: "hinto.janai" <hinto.janai@protonmail.com> Date: Sun, 7 Jul 2024 16:29:24 -0400 Subject: [PATCH] add `misc` module, start `bin` and `other` --- rpc/types/README.md | 21 ++- rpc/types/src/base.rs | 2 +- rpc/types/src/bin.rs | 63 ++++++++ rpc/types/src/defaults.rs | 6 + rpc/types/src/json.rs | 3 +- rpc/types/src/lib.rs | 19 +-- rpc/types/src/misc.rs | 301 +++++++++++++++++++++++++++++++++++++- rpc/types/src/other.rs | 15 +- 8 files changed, 401 insertions(+), 29 deletions(-) diff --git a/rpc/types/README.md b/rpc/types/README.md index 0b067436..ad1e0e80 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -10,17 +10,17 @@ This crate ports the types used in Monero's RPC interface, including: # Modules This crate's types are split in the following manner: -This crate has 5 modules: -- The root module - miscellaneous items -- [`json`] - JSON types from the `/json_rpc` endpoint -- [`bin`] - Binary types from the binary endpoints -- [`other`] - Misc JSON types from other endpoints -- [`base`] - Base response types +| Module | Purpose | +|--------|---------| +| The root module | Miscellaneous items, e.g. constants. +| [`json`] | Contains JSON request/response (some mixed with binary) that all share the common `/json_rpc` endpoint. | +| [`bin`] | Contains request/response types that are expected to be fully in binary (`cuprate_epee_encoding`) in `monerod` and `cuprated`'s RPC interface. These are called at a custom endpoint instead of `/json_rpc`, e.g. `/get_blocks.bin`. | +| [`other`] | Contains request/response types that are JSON, but aren't called at `/json_rpc` (e.g. [`crate::other::GetHeightRequest`]). | +| [`misc`] | Contains miscellaneous types, e.g. [`crate::misc::Status`]. Many of types here are found and used in request/response types, for example, [`crate::misc::BlockHeader`] is used in [`crate::json::GetLastBlockHeaderResponse`]. | +| [`base`] | Contains base types flattened into many request/response types. 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). -Miscellaneous types are found in the root module, e.g. [`crate::Status`]. Many of types here are found and used in request/response types, for example, [`crate::BlockHeader`] is used in [`crate::json::GetLastBlockHeaderResponse`]. - # 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). @@ -45,9 +45,8 @@ For example: 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: +Note that some types mix JSON & binary together, i.e., the message overall is JSON, +however some fields contain binary values inside JSON strings, for example: ```json { diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index 38f857d1..d55c0ef1 100644 --- a/rpc/types/src/base.rs +++ b/rpc/types/src/base.rs @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] use cuprate_epee_encoding::epee_object; -use crate::{macros::monero_definition_link, Status}; +use crate::{macros::monero_definition_link, misc::Status}; //---------------------------------------------------------------------------------------------------- Requests /* no types here... yet */ diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index f327847f..66e02a03 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -1,8 +1,71 @@ //! Binary types from [binary](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin) endpoints. +//! +//! Most (if not all) of these types are defined here: +//! - <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h> //---------------------------------------------------------------------------------------------------- Import +use crate::{ + base::{AccessResponseBase, ResponseBase}, + defaults::{ + default_bool, default_height, default_string, default_u64, default_u8, default_vec, + }, + free::{is_one, is_zero}, + macros::define_request_and_response, + misc::{ + AuxPow, BlockCompleteEntry, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, + GetBan, HardforkEntry, HistogramEntry, OutputDistributionData, Peer, PoolTxInfo, SetBan, + Span, Status, TxBacklogEntry, + }, +}; //---------------------------------------------------------------------------------------------------- TODO +define_request_and_response! { + get_blocks_bin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 162..=262, + GetBlocksBin, + Request { + #[cfg_attr(feature = "serde", serde(default = "default_u8"))] + requested_info: u8 = default_u8(), + // TODO: This is a `std::list` in `monerod` because...? + block_ids: Vec<[u8; 32]>, + start_height: u64, + prune: bool, + #[cfg_attr(feature = "serde", serde(default = "default_bool"))] + no_miner_tx: bool = default_bool(), + #[cfg_attr(feature = "serde", serde(default = "default_u64"))] + pool_info_since: u64 = default_u64(), + }, + ResponseBase { + blocks: Vec<BlockCompleteEntry>, + start_height: u64, + current_height: u64, + output_indices: Vec<BlockOutputIndices>, + daemon_time: u64, + pool_info_extent: u8, + added_pool_txs: Vec<PoolTxInfo>, + remaining_added_pool_txids: Vec<[u8; 32]>, + removed_pool_txids: Vec<[u8; 32]>, + } +} + +define_request_and_response! { + add_aux_pow, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1068..=1112, + AddAuxPow, + Request { + blocktemplate_blob: String, + aux_pow: Vec<AuxPow>, + }, + ResponseBase { + blocktemplate_blob: String, + blockhashing_blob: String, + merkle_root: String, + merkle_tree_depth: u64, + aux_pow: Vec<AuxPow>, + } +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/types/src/defaults.rs b/rpc/types/src/defaults.rs index 833f33ef..27aac90a 100644 --- a/rpc/types/src/defaults.rs +++ b/rpc/types/src/defaults.rs @@ -47,6 +47,12 @@ pub(crate) const fn default_u64() -> u64 { 0 } +/// Default [`u8`] used in request/response types. +#[inline] +pub(crate) const fn default_u8() -> u8 { + 0 +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index b5ae493c..dd2d9f16 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -11,9 +11,8 @@ use crate::{ macros::define_request_and_response, misc::{ AuxPow, BlockHeader, ChainInfo, ConnectionInfo, GetBan, HardforkEntry, HistogramEntry, - Peer, SetBan, Span, TxBacklogEntry, + OutputDistributionData, Peer, SetBan, Span, Status, TxBacklogEntry, }, - OutputDistributionData, Status, }; //---------------------------------------------------------------------------------------------------- Struct definitions diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 42c6b551..268e3875 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -82,7 +82,15 @@ clippy::option_if_let_else, )] // Allow some lints when running in debug mode. -#![cfg_attr(debug_assertions, allow(clippy::todo, clippy::multiple_crate_versions))] +#![cfg_attr( + debug_assertions, + allow( + clippy::todo, + clippy::multiple_crate_versions, + unused_imports, + unused_variables + ) +)] // Allow some lints in tests. #![cfg_attr( test, @@ -102,7 +110,6 @@ mod constants; mod defaults; mod free; mod macros; -mod status; pub use binary_string::BinaryString; pub use constants::{ @@ -110,7 +117,6 @@ pub use constants::{ 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; #[cfg(feature = "bin")] @@ -119,12 +125,7 @@ pub mod bin; #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub mod json; +pub mod misc; #[cfg(feature = "other")] #[cfg_attr(docsrs, doc(cfg(feature = "other")))] pub mod other; - -mod misc; -pub use misc::{ - AuxPow, BlockHeader, ChainInfo, ConnectionInfo, GetBan, GetMinerDataTxBacklogEntry, - HardforkEntry, HistogramEntry, OutputDistributionData, Peer, SetBan, Span, TxBacklogEntry, -}; diff --git a/rpc/types/src/misc.rs b/rpc/types/src/misc.rs index 2c95d74b..5974aaaf 100644 --- a/rpc/types/src/misc.rs +++ b/rpc/types/src/misc.rs @@ -3,8 +3,6 @@ //! These are `struct`s that appear in request/response types. //! For example, [`crate::json::GetConnectionsResponse`] contains //! the [`crate::misc::ConnectionInfo`] struct defined here. -//! -//! These types are re-exported to the root module. //---------------------------------------------------------------------------------------------------- Lints #![allow( @@ -13,13 +11,25 @@ )] //---------------------------------------------------------------------------------------------------- Import +use std::fmt::Display; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] -use cuprate_epee_encoding::epee_object; +use cuprate_epee_encoding::{ + epee_object, + macros::bytes::{Buf, BufMut}, + EpeeValue, Marker, +}; -use crate::macros::monero_definition_link; +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, + }, + macros::monero_definition_link, +}; //---------------------------------------------------------------------------------------------------- Macros /// This macro (local to this file) defines all the misc types. @@ -328,8 +338,289 @@ define_struct_and_impl_epee! { } } +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 192..=199 + )] + /// Used in [`crate::bin::GetBlocksBinResponse`]. + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + TxOutputIndices { + indices: Vec<u64>, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 201..=208 + )] + /// Used in [`crate::bin::GetBlocksBinResponse`]. + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + BlockOutputIndices { + indices: Vec<TxOutputIndices>, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 210..=221 + )] + /// Used in [`crate::bin::GetBlocksBinResponse`]. + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + PoolTxInfo { + tx_hash: [u8; 32], + tx_blob: String, + double_spend_seen: bool, + } +} + +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "cryptonote_protocol/cryptonote_protocol_defs.h", + 121..=131 + )] + /// Used in [`crate::bin::GetBlocksBinResponse`]. + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + TxBlobEntry { + blob: String, + prunable_hash: [u8; 32], + } +} + +//---------------------------------------------------------------------------------------------------- TODO +// TODO - weird types. + +#[doc = monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 210..=221 +)] +/// Used in [`crate::bin::GetBlocksBinResponse`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BlockCompleteEntry { + pub pruned: bool, + pub block: String, + pub block_weight: u64, + pub txs: Vec<TxBlobEntry>, +} + +// TODO: custom epee +// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_protocol/cryptonote_protocol_defs.h#L138-L163> +#[cfg(feature = "epee")] +epee_object! { + BlockCompleteEntry, + pruned: bool, + block: String, + block_weight: u64, + txs: Vec<TxBlobEntry>, +} + +/// Used in [`crate::bin::GetBlocksBinResponse`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] +pub enum PoolInfoExtent { + None = 0, + Incremental = 1, + Full = 2, +} + +// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_protocol/cryptonote_protocol_defs.h#L138-L163> +#[cfg(feature = "epee")] +impl EpeeValue for PoolInfoExtent { + const MARKER: Marker = <String as EpeeValue>::MARKER; + + fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> { + todo!() + } + + fn should_write(&self) -> bool { + todo!() + } + + fn epee_default_value() -> Option<Self> { + todo!() + } + + fn write<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::Result<()> { + todo!() + } +} + +//---------------------------------------------------------------------------------------------------- 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::{ +/// misc::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)] +#[cfg_attr(feature = "serde", derive(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`]. + #[cfg_attr(feature = "serde", serde(rename = "OK"))] + #[default] + Ok, + + /// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`]. + #[cfg_attr(feature = "serde", serde(rename = "BUSY"))] + Busy, + + /// The daemon is not mining; [`CORE_RPC_STATUS_NOT_MINING`]. + #[cfg_attr(feature = "serde", serde(rename = "NOT MINING"))] + NotMining, + + /// Payment is required for RPC; [`CORE_RPC_STATUS_PAYMENT_REQUIRED`]. + #[cfg_attr(feature = "serde", 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. + #[cfg_attr(feature = "serde", serde(other))] + #[cfg_attr(feature = "serde", 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>. +#[cfg(feature = "epee")] +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::*; + use super::*; + + // Test epee (de)serialization works. + #[test] + #[cfg(feature = "epee")] + 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); + } + } } diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index ac55e1de..c8bc6208 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -1,11 +1,24 @@ //! 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>. +//! Most (if not all) of these types are defined here: +//! - <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h> //---------------------------------------------------------------------------------------------------- Import use crate::{base::ResponseBase, macros::define_request_and_response}; //---------------------------------------------------------------------------------------------------- TODO +define_request_and_response! { + get_height, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 138..=160, + GetHeight, + Request {}, + ResponseBase { + hash: String, + height: u64, + } +} + define_request_and_response! { save_bc, cc73fe71162d564ffda8e549b79a350bca53c454 =>