From 929d19c4508a84d886ece03009a6fcdc5edea5c2 Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Thu, 25 Jul 2024 11:46:41 -0400 Subject: [PATCH] rpc: custom epee for misc/bin types (#229) * 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 * distribution: module doc * wire: re-export types * bin: use enum for `GetBlocksResponse` * misc: use lowercase for stringify * remove duplicated fields for custom epee * types: remove `should_write()` for custom epee * bin: split `GetBlocksResponse` variant fields into structs * misc: split `Distribution` variant fields into structs * small fixes * put all fields in `read_epee_field!` * distribution: (de)compress during epee/serde (de)serialization * distribution: leave (de)compression functions as `todo!()` --------- Co-authored-by: Boog900 --- Cargo.lock | 2 + rpc/types/Cargo.toml | 4 +- rpc/types/README.md | 16 + rpc/types/src/base.rs | 2 +- rpc/types/src/bin.rs | 329 +++++++++++++++++-- rpc/types/src/constants.rs | 3 - rpc/types/src/json.rs | 21 +- rpc/types/src/lib.rs | 7 +- rpc/types/src/macros.rs | 44 ++- rpc/types/src/misc/block_complete_entry.rs | 37 --- rpc/types/src/misc/distribution.rs | 309 +++++++++++++++++ rpc/types/src/misc/key_image_spent_status.rs | 71 +++- rpc/types/src/misc/misc.rs | 15 +- rpc/types/src/misc/mod.rs | 6 +- rpc/types/src/misc/pool_info_extent.rs | 73 +++- rpc/types/src/misc/status.rs | 41 +-- rpc/types/src/misc/tx_entry.rs | 161 ++++++--- rpc/types/src/other.rs | 10 +- rpc/types/src/serde.rs | 32 ++ types/README.md | 2 +- 20 files changed, 949 insertions(+), 236 deletions(-) delete mode 100644 rpc/types/src/misc/block_complete_entry.rs create mode 100644 rpc/types/src/misc/distribution.rs create mode 100644 rpc/types/src/serde.rs diff --git a/Cargo.lock b/Cargo.lock index 426ccc2f..ac296628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -764,8 +764,10 @@ version = "0.0.0" dependencies = [ "cuprate-epee-encoding", "cuprate-fixed-bytes", + "cuprate-types", "monero-serai", "paste", + "pretty_assertions", "serde", "serde_json", ] diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 1176526a..fcec4536 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -16,10 +16,12 @@ epee = ["dep:cuprate-epee-encoding"] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true } cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } +cuprate-types = { path = "../../types" } monero-serai = { workspace = true } paste = { workspace = true } serde = { workspace = true, optional = true } [dev-dependencies] -serde_json = { workspace = true } +serde_json = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/rpc/types/README.md b/rpc/types/README.md index eb8da013..566cca7e 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -78,6 +78,22 @@ will be used instead of a more typical [`String`] for optimization reasons. --> +# (De)serialization invariants +Due to how types are defined in this library internally (all through a single macro), +most types implement both `serde` and `epee`. + +However, some of the types will panic with [`unimplemented`] +or will otherwise have undefined implementation in the incorrect context. + +In other words: +- The epee (de)serialization of [`json`] & [`other`] types should **not** be relied upon +- The JSON (de)serialization of [`bin`] types should **not** be relied upon + +The invariants that can be relied upon: +- Types in [`json`] & [`other`] will implement `serde` correctly +- Types in [`bin`] will implement `epee` correctly +- Misc types will implement `serde/epee` correctly as needed + # Feature flags List of feature flags for `cuprate-rpc-types`. diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index f13ac403..4990cdd6 100644 --- a/rpc/types/src/base.rs +++ b/rpc/types/src/base.rs @@ -46,7 +46,7 @@ epee_object! { //---------------------------------------------------------------------------------------------------- Responses #[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 101..=112)] /// The most common base for responses. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ResponseBase { /// General RPC error code. [`Status::Ok`] means everything looks good. diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 3dcfb967..c801c69e 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -5,51 +5,32 @@ //---------------------------------------------------------------------------------------------------- Import use cuprate_fixed_bytes::ByteArrayVec; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[cfg(feature = "epee")] -use cuprate_epee_encoding::container_as_blob::ContainerAsBlob; +use cuprate_epee_encoding::{ + container_as_blob::ContainerAsBlob, + epee_object, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, +}; + +use cuprate_types::BlockCompleteEntry; use crate::{ base::{AccessResponseBase, ResponseBase}, defaults::{default_false, default_height, default_string, default_vec, default_zero}, free::{is_one, is_zero}, - macros::define_request_and_response, + macros::{define_request, define_request_and_response, define_request_and_response_doc}, misc::{ - AuxPow, BlockCompleteEntry, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, - GetBan, GetOutputsOut, HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, - Peer, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, + AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, + HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolInfoExtent, + PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, }; -//---------------------------------------------------------------------------------------------------- TODO -define_request_and_response! { - get_blocksbin, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 162..=262, - GetBlocks, - Request { - requested_info: u8 = default_zero(), "default_zero", - // FIXME: This is a `std::list` in `monerod` because...? - block_ids: ByteArrayVec<32>, - start_height: u64, - prune: bool, - no_miner_tx: bool = default_false(), "default_false", - pool_info_since: u64 = default_zero(), "default_zero", - }, - // TODO: this has custom epee (de)serialization. - // - ResponseBase { - blocks: Vec, - start_height: u64, - current_height: u64, - output_indices: Vec, - daemon_time: u64, - pool_info_extent: u8, - added_pool_txs: Vec, - remaining_added_pool_txids: Vec<[u8; 32]>, - removed_pool_txids: Vec<[u8; 32]>, - } -} - +//---------------------------------------------------------------------------------------------------- Definitions define_request_and_response! { get_blocks_by_heightbin, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -134,6 +115,284 @@ define_request_and_response! { } } +//---------------------------------------------------------------------------------------------------- GetBlocks +define_request! { + #[doc = define_request_and_response_doc!( + "response" => GetBlocksResponse, + get_blocksbin, + cc73fe71162d564ffda8e549b79a350bca53c454, + core_rpc_server_commands_defs, h, 162, 262, + )] + GetBlocksRequest { + requested_info: u8 = default_zero::(), "default_zero", + // FIXME: This is a `std::list` in `monerod` because...? + block_ids: ByteArrayVec<32>, + start_height: u64, + prune: bool, + no_miner_tx: bool = default_false(), "default_false", + pool_info_since: u64 = default_zero::(), "default_zero", + } +} + +#[doc = define_request_and_response_doc!( + "request" => GetBlocksRequest, + get_blocksbin, + cc73fe71162d564ffda8e549b79a350bca53c454, + core_rpc_server_commands_defs, h, 162, 262, +)] +/// +/// This response's variant depends upon [`PoolInfoExtent`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum GetBlocksResponse { + /// Will always serialize a [`PoolInfoExtent::None`] field. + PoolInfoNone(GetBlocksResponsePoolInfoNone), + /// Will always serialize a [`PoolInfoExtent::Incremental`] field. + PoolInfoIncremental(GetBlocksResponsePoolInfoIncremental), + /// Will always serialize a [`PoolInfoExtent::Full`] field. + PoolInfoFull(GetBlocksResponsePoolInfoFull), +} + +impl Default for GetBlocksResponse { + fn default() -> Self { + Self::PoolInfoNone(GetBlocksResponsePoolInfoNone::default()) + } +} + +/// Data within [`GetBlocksResponse::PoolInfoNone`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GetBlocksResponsePoolInfoNone { + pub status: Status, + pub untrusted: bool, + pub blocks: Vec, + pub start_height: u64, + pub current_height: u64, + pub output_indices: Vec, + pub daemon_time: u64, +} + +#[cfg(feature = "epee")] +epee_object! { + GetBlocksResponsePoolInfoNone, + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, +} + +/// Data within [`GetBlocksResponse::PoolInfoIncremental`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GetBlocksResponsePoolInfoIncremental { + pub status: Status, + pub untrusted: bool, + pub blocks: Vec, + pub start_height: u64, + pub current_height: u64, + pub output_indices: Vec, + pub daemon_time: u64, + pub added_pool_txs: Vec, + pub remaining_added_pool_txids: ByteArrayVec<32>, + pub removed_pool_txids: ByteArrayVec<32>, +} + +#[cfg(feature = "epee")] +epee_object! { + GetBlocksResponsePoolInfoIncremental, + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, + removed_pool_txids: ByteArrayVec<32>, +} + +/// Data within [`GetBlocksResponse::PoolInfoFull`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GetBlocksResponsePoolInfoFull { + pub status: Status, + pub untrusted: bool, + pub blocks: Vec, + pub start_height: u64, + pub current_height: u64, + pub output_indices: Vec, + pub daemon_time: u64, + pub added_pool_txs: Vec, + pub remaining_added_pool_txids: ByteArrayVec<32>, +} + +#[cfg(feature = "epee")] +epee_object! { + GetBlocksResponsePoolInfoFull, + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, +} + +#[cfg(feature = "epee")] +/// [`EpeeObjectBuilder`] for [`GetBlocksResponse`]. +/// +/// Not for public usage. +#[allow(dead_code, missing_docs)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __GetBlocksResponseEpeeBuilder { + pub status: Option, + pub untrusted: Option, + pub blocks: Option>, + pub start_height: Option, + pub current_height: Option, + pub output_indices: Option>, + pub daemon_time: Option, + pub pool_info_extent: Option, + pub added_pool_txs: Option>, + pub remaining_added_pool_txids: Option>, + pub removed_pool_txids: Option>, +} + +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __GetBlocksResponseEpeeBuilder { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + macro_rules! read_epee_field { + ($($field:ident),*) => { + match name { + $( + stringify!($field) => { self.$field = Some(read_epee_value(r)?); }, + )* + _ => return Ok(false), + } + }; + } + + read_epee_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + removed_pool_txids + } + + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + + let status = self.status.ok_or(ELSE)?; + let untrusted = self.untrusted.ok_or(ELSE)?; + let blocks = self.blocks.ok_or(ELSE)?; + let start_height = self.start_height.ok_or(ELSE)?; + let current_height = self.current_height.ok_or(ELSE)?; + let output_indices = self.output_indices.ok_or(ELSE)?; + let daemon_time = self.daemon_time.ok_or(ELSE)?; + let pool_info_extent = self.pool_info_extent.ok_or(ELSE)?; + + let this = match pool_info_extent { + PoolInfoExtent::None => { + GetBlocksResponse::PoolInfoNone(GetBlocksResponsePoolInfoNone { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + }) + } + PoolInfoExtent::Incremental => { + GetBlocksResponse::PoolInfoIncremental(GetBlocksResponsePoolInfoIncremental { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + added_pool_txs: self.added_pool_txs.ok_or(ELSE)?, + remaining_added_pool_txids: self.remaining_added_pool_txids.ok_or(ELSE)?, + removed_pool_txids: self.removed_pool_txids.ok_or(ELSE)?, + }) + } + PoolInfoExtent::Full => { + GetBlocksResponse::PoolInfoFull(GetBlocksResponsePoolInfoFull { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + added_pool_txs: self.added_pool_txs.ok_or(ELSE)?, + remaining_added_pool_txids: self.remaining_added_pool_txids.ok_or(ELSE)?, + }) + } + }; + + Ok(this) + } +} + +#[cfg(feature = "epee")] +#[allow(clippy::cognitive_complexity)] +impl EpeeObject for GetBlocksResponse { + type Builder = __GetBlocksResponseEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + // [`PoolInfoExtent`] + inner struct fields. + let inner_fields = match self { + Self::PoolInfoNone(s) => s.number_of_fields(), + Self::PoolInfoIncremental(s) => s.number_of_fields(), + Self::PoolInfoFull(s) => s.number_of_fields(), + }; + + 1 + inner_fields + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + match self { + Self::PoolInfoNone(s) => { + s.write_fields(w)?; + write_field(PoolInfoExtent::None.to_u8(), "pool_info_extent", w)?; + } + Self::PoolInfoIncremental(s) => { + s.write_fields(w)?; + write_field(PoolInfoExtent::Incremental.to_u8(), "pool_info_extent", w)?; + } + Self::PoolInfoFull(s) => { + s.write_fields(w)?; + write_field(PoolInfoExtent::Full.to_u8(), "pool_info_extent", w)?; + } + } + + Ok(()) + } +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/constants.rs b/rpc/types/src/constants.rs index e5802836..8c6120ba 100644 --- a/rpc/types/src/constants.rs +++ b/rpc/types/src/constants.rs @@ -36,9 +36,6 @@ pub const CORE_RPC_STATUS_NOT_MINING: &str = "NOT MINING"; #[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 81)] 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 #[doc = monero_definition_link!(cc73fe71162d564ffda8e549b79a350bca53c454, "/rpc/core_rpc_server_commands_defs.h", 90)] /// RPC major version. diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 2e7aa82a..f4bca993 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -9,12 +9,12 @@ use crate::{ free::{is_one, is_zero}, macros::define_request_and_response, misc::{ - AuxPow, BlockHeader, ChainInfo, ConnectionInfo, GetBan, HardforkEntry, HistogramEntry, - OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, + AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, HardforkEntry, + HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, }, }; -//---------------------------------------------------------------------------------------------------- Struct definitions +//---------------------------------------------------------------------------------------------------- Definitions // This generates 2 structs: // // - `GetBlockTemplateRequest` @@ -131,7 +131,6 @@ define_request_and_response! { // type alias to `()` instead of a `struct`. Request {}, - #[derive(Copy)] ResponseBase { count: u64, } @@ -292,7 +291,7 @@ define_request_and_response! { AccessResponseBase { blob: String, block_header: BlockHeader, - json: String, // TODO: this should be defined in a struct, it has many fields. + json: String, // FIXME: this should be defined in a struct, it has many fields. miner_tx_hash: String, tx_hashes: Vec, } @@ -409,7 +408,6 @@ define_request_and_response! { Request { address: String, }, - #[derive(Copy)] Response { banned: bool, seconds: u32, @@ -425,7 +423,6 @@ define_request_and_response! { Request { txids: Vec = default_vec::(), "default_vec", }, - #[derive(Copy)] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] Response { @@ -479,9 +476,9 @@ define_request_and_response! { version: u32, release: bool, #[serde(skip_serializing_if = "is_zero")] - current_height: u64 = default_zero(), "default_zero", + current_height: u64 = default_zero::(), "default_zero", #[serde(skip_serializing_if = "is_zero")] - target_height: u64 = default_zero(), "default_zero", + target_height: u64 = default_zero::(), "default_zero", #[serde(skip_serializing_if = "Vec::is_empty")] hard_forks: Vec = default_vec(), "default_vec", } @@ -520,7 +517,6 @@ define_request_and_response! { Request { txids: Vec, }, - #[derive(Copy)] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] Response { @@ -574,10 +570,8 @@ define_request_and_response! { from_height: u64, to_height: u64, }, - /// TODO: this request has custom serde: - /// AccessResponseBase { - distributions: Vec, + distributions: Vec, } } @@ -607,7 +601,6 @@ define_request_and_response! { Request { check: bool = default_false(), "default_false", }, - #[derive(Copy)] ResponseBase { pruned: bool, pruning_seed: u32, diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 45cca69c..d0d1e00d 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -113,6 +113,9 @@ mod defaults; mod free; mod macros; +#[cfg(feature = "serde")] +mod serde; + pub mod base; pub mod bin; pub mod json; @@ -121,6 +124,6 @@ pub mod other; 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, + CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, + CORE_RPC_VERSION_MINOR, }; diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index e1301387..fa0d5188 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -16,11 +16,11 @@ /// /// # Macro internals /// This macro uses: -/// - [`__define_request`] -/// - [`__define_response`] -/// - [`__define_request_and_response_doc`] +/// - [`define_request`] +/// - [`define_response`] +/// - [`define_request_and_response_doc`] /// -/// # `__define_request` +/// # `define_request` /// This macro has 2 branches. If the caller provides /// `Request {}`, i.e. no fields, it will generate: /// ``` @@ -34,7 +34,7 @@ /// means they are not compatible and it makes it cumbersome for end-users. /// Really, they semantically are empty types, so `()` is used. /// -/// # `__define_response` +/// # `define_response` /// This macro has 2 branches. If the caller provides `Response` /// it will generate a normal struct with no additional fields. /// @@ -86,8 +86,8 @@ macro_rules! define_request_and_response { )* } ) => { paste::paste! { - $crate::macros::__define_request! { - #[doc = $crate::macros::__define_request_and_response_doc!( + $crate::macros::define_request! { + #[doc = $crate::macros::define_request_and_response_doc!( "response" => [<$type_name Response>], $monero_daemon_rpc_doc_link, $monero_code_commit, @@ -110,12 +110,12 @@ macro_rules! define_request_and_response { } } - $crate::macros::__define_response! { + $crate::macros::define_response! { #[allow(dead_code)] #[allow(missing_docs)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[doc = $crate::macros::__define_request_and_response_doc!( + #[doc = $crate::macros::define_request_and_response_doc!( "request" => [<$type_name Request>], $monero_daemon_rpc_doc_link, $monero_code_commit, @@ -145,9 +145,7 @@ pub(crate) use define_request_and_response; /// Define a request type. /// /// This is only used in [`define_request_and_response`], see it for docs. -/// -/// `__` is used to notate that this shouldn't be called directly. -macro_rules! __define_request { +macro_rules! define_request { //------------------------------------------------------------------------------ // This branch will generate a type alias to `()` if only given `{}` as input. ( @@ -206,15 +204,13 @@ macro_rules! __define_request { } }; } -pub(crate) use __define_request; +pub(crate) use define_request; //---------------------------------------------------------------------------------------------------- define_response /// Define a response type. /// -/// This is only used in [`define_request_and_response`], see it for docs. -/// -/// `__` is used to notate that this shouldn't be called directly. -macro_rules! __define_response { +/// This is used in [`define_request_and_response`], see it for docs. +macro_rules! define_response { //------------------------------------------------------------------------------ // This version of the macro expects the literal ident // `Response` => $response_type_name. @@ -228,7 +224,7 @@ macro_rules! __define_response { // The response type. Response => $t:ident { // And any fields. - // See [`__define_request`] for docs, this does the same thing. + // See [`define_request`] for docs, this does the same thing. $( $( #[$field_attr:meta] )* $field:ident: $field_type:ty @@ -265,7 +261,7 @@ macro_rules! __define_response { // The response base type => actual name of the struct $base:ty => $t:ident { // And any fields. - // See [`__define_request`] for docs, this does the same thing. + // See [`define_request`] for docs, this does the same thing. $( $( #[$field_attr:meta] )* $field:ident: $field_type:ty @@ -298,16 +294,14 @@ macro_rules! __define_response { } }; } -pub(crate) use __define_response; +pub(crate) use define_response; //---------------------------------------------------------------------------------------------------- define_request_and_response_doc /// Generate documentation for the types generated -/// by the [`__define_request_and_response`] macro. +/// by the [`define_request_and_response`] macro. /// /// See it for more info on inputs. -/// -/// `__` is used to notate that this shouldn't be called directly. -macro_rules! __define_request_and_response_doc { +macro_rules! define_request_and_response_doc { ( // This labels the last `[request]` or `[response]` // hyperlink in documentation. Input is either: @@ -351,7 +345,7 @@ macro_rules! __define_request_and_response_doc { ) }; } -pub(crate) use __define_request_and_response_doc; +pub(crate) use define_request_and_response_doc; //---------------------------------------------------------------------------------------------------- Macro /// Output a string link to `monerod` source code. diff --git a/rpc/types/src/misc/block_complete_entry.rs b/rpc/types/src/misc/block_complete_entry.rs deleted file mode 100644 index ca791b0a..00000000 --- a/rpc/types/src/misc/block_complete_entry.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Use -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "epee")] -use cuprate_epee_encoding::epee_object; - -use crate::misc::TxBlobEntry; - -//---------------------------------------------------------------------------------------------------- BlockCompleteEntry -#[doc = crate::macros::monero_definition_link!( - cc73fe71162d564ffda8e549b79a350bca53c454, - "rpc/core_rpc_server_commands_defs.h", - 210..=221 -)] -/// Used in [`crate::bin::GetBlocksResponse`]. -#[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, -} - -// TODO: custom epee -// -#[cfg(feature = "epee")] -epee_object! { - BlockCompleteEntry, - pruned: bool, - block: String, - block_weight: u64, - txs: Vec, -} diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs new file mode 100644 index 00000000..1a488d44 --- /dev/null +++ b/rpc/types/src/misc/distribution.rs @@ -0,0 +1,309 @@ +//! Output distributions for [`crate::json::GetOutputDistributionResponse`]. + +//---------------------------------------------------------------------------------------------------- Use +use std::mem::size_of; + +#[cfg(feature = "serde")] +use serde::{ser::SerializeStruct, Deserialize, Serialize}; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, read_varint, write_field, write_varint, EpeeObject, EpeeObjectBuilder, + EpeeValue, Marker, +}; + +//---------------------------------------------------------------------------------------------------- Free +/// TODO: . +/// +/// Used for [`Distribution::CompressedBinary::distribution`]. +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 45..=55 +)] +#[cfg(feature = "epee")] +fn compress_integer_array(array: &[u64]) -> error::Result> { + todo!() +} + +/// TODO: . +/// +/// Used for [`Distribution::CompressedBinary::distribution`]. +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 57..=72 +)] +fn decompress_integer_array(array: &[u8]) -> Vec { + todo!() +} + +//---------------------------------------------------------------------------------------------------- Distribution +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2468..=2508 +)] +/// Used in [`crate::json::GetOutputDistributionResponse`]. +/// +/// # Internals +/// This type's (de)serialization depends on `monerod`'s (de)serialization. +/// +/// During serialization: +/// [`Self::Uncompressed`] will emit: +/// - `compress: false` +/// +/// [`Self::CompressedBinary`] will emit: +/// - `binary: true` +/// - `compress: true` +/// +/// Upon deserialization, the presence of a `compressed_data` +/// field signifies that the [`Self::CompressedBinary`] should +/// be selected. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum Distribution { + /// Distribution data will be (de)serialized as either JSON or binary (uncompressed). + Uncompressed(DistributionUncompressed), + /// Distribution data will be (de)serialized as compressed binary. + CompressedBinary(DistributionCompressedBinary), +} + +impl Default for Distribution { + fn default() -> Self { + Self::Uncompressed(DistributionUncompressed::default()) + } +} + +/// Data within [`Distribution::Uncompressed`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DistributionUncompressed { + pub start_height: u64, + pub base: u64, + /// TODO: this is a binary JSON string if `binary == true`. + pub distribution: Vec, + pub amount: u64, + pub binary: bool, +} + +#[cfg(feature = "epee")] +epee_object! { + DistributionUncompressed, + start_height: u64, + base: u64, + distribution: Vec, + amount: u64, + binary: bool, +} + +/// Data within [`Distribution::CompressedBinary`]. +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DistributionCompressedBinary { + pub start_height: u64, + pub base: u64, + #[cfg_attr( + feature = "serde", + serde(serialize_with = "serialize_distribution_as_compressed_data") + )] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "deserialize_compressed_data_as_distribution") + )] + #[cfg_attr(feature = "serde", serde(rename = "compressed_data"))] + pub distribution: Vec, + pub amount: u64, +} + +#[cfg(feature = "epee")] +epee_object! { + DistributionCompressedBinary, + start_height: u64, + base: u64, + distribution: Vec, + amount: u64, +} + +/// Serializer function for [`DistributionCompressedBinary::distribution`]. +/// +/// 1. Compresses the distribution array +/// 2. Serializes the compressed data +#[cfg(feature = "serde")] +#[allow(clippy::ptr_arg)] +fn serialize_distribution_as_compressed_data(v: &Vec, s: S) -> Result +where + S: serde::Serializer, +{ + match compress_integer_array(v) { + Ok(compressed_data) => compressed_data.serialize(s), + Err(_) => Err(serde::ser::Error::custom( + "error compressing distribution array", + )), + } +} + +/// Deserializer function for [`DistributionCompressedBinary::distribution`]. +/// +/// 1. Deserializes as `compressed_data` field. +/// 2. Decompresses and returns the data +#[cfg(feature = "serde")] +fn deserialize_compressed_data_as_distribution<'de, D>(d: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + Vec::::deserialize(d).map(|v| decompress_integer_array(&v)) +} + +//---------------------------------------------------------------------------------------------------- Epee +#[cfg(feature = "epee")] +/// [`EpeeObjectBuilder`] for [`Distribution`]. +/// +/// Not for public usage. +#[allow(dead_code, missing_docs)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __DistributionEpeeBuilder { + pub start_height: Option, + pub base: Option, + pub distribution: Option>, + pub amount: Option, + pub compressed_data: Option>, + pub binary: Option, + pub compress: Option, +} + +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __DistributionEpeeBuilder { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + macro_rules! read_epee_field { + ($($field:ident),*) => { + match name { + $( + stringify!($field) => { self.$field = Some(read_epee_value(r)?); }, + )* + _ => return Ok(false), + } + }; + } + + read_epee_field! { + start_height, + base, + amount, + binary, + compress, + compressed_data, + distribution + } + + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + + let start_height = self.start_height.ok_or(ELSE)?; + let base = self.base.ok_or(ELSE)?; + let amount = self.amount.ok_or(ELSE)?; + + let distribution = if let Some(compressed_data) = self.compressed_data { + let distribution = decompress_integer_array(&compressed_data); + Distribution::CompressedBinary(DistributionCompressedBinary { + start_height, + base, + distribution, + amount, + }) + } else if let Some(distribution) = self.distribution { + Distribution::Uncompressed(DistributionUncompressed { + binary: self.binary.ok_or(ELSE)?, + distribution, + start_height, + base, + amount, + }) + } else { + return Err(ELSE); + }; + + Ok(distribution) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for Distribution { + type Builder = __DistributionEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + match self { + // Inner struct fields + `compress`. + Self::Uncompressed(s) => s.number_of_fields() + 1, + // Inner struct fields + `compress` + `binary`. + Self::CompressedBinary(s) => s.number_of_fields() + 2, + } + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + match self { + Self::Uncompressed(s) => { + s.write_fields(w)?; + write_field(false, "compress", w)?; + } + + Self::CompressedBinary(DistributionCompressedBinary { + start_height, + base, + distribution, + amount, + }) => { + let compressed_data = compress_integer_array(&distribution)?; + + start_height.write(w)?; + base.write(w)?; + compressed_data.write(w)?; + amount.write(w)?; + + write_field(true, "binary", w)?; + write_field(true, "compress", w)?; + } + } + + Ok(()) + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + // TODO: re-enable tests after (de)compression functions are implemented. + + // /// Tests that [`compress_integer_array`] outputs as expected. + // #[test] + // fn compress() { + // let varints = &[16_384, 16_383, 16_382, 16_381]; + // let bytes = compress_integer_array(varints).unwrap(); + + // let expected = [2, 0, 1, 0, 253, 255, 249, 255, 245, 255]; + // assert_eq!(expected, *bytes); + // } + + // /// Tests that [`decompress_integer_array`] outputs as expected. + // #[test] + // fn decompress() { + // let bytes = &[2, 0, 1, 0, 253, 255, 249, 255, 245, 255]; + // let varints = decompress_integer_array(bytes); + + // let expected = vec![16_384, 16_383, 16_382, 16_381]; + // assert_eq!(expected, varints); + // } +} diff --git a/rpc/types/src/misc/key_image_spent_status.rs b/rpc/types/src/misc/key_image_spent_status.rs index d075e64e..4b2eb535 100644 --- a/rpc/types/src/misc/key_image_spent_status.rs +++ b/rpc/types/src/misc/key_image_spent_status.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] use cuprate_epee_encoding::{ + error, macros::bytes::{Buf, BufMut}, EpeeValue, Marker, }; @@ -17,7 +18,7 @@ use cuprate_epee_encoding::{ 456..=460 )] /// Used in [`crate::other::IsKeyImageSpentResponse`]. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] pub enum KeyImageSpentStatus { @@ -26,23 +27,59 @@ pub enum KeyImageSpentStatus { SpentInPool = 2, } -#[cfg(feature = "epee")] -impl EpeeValue for KeyImageSpentStatus { - const MARKER: Marker = ::MARKER; - - fn read(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result { - todo!() +impl KeyImageSpentStatus { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// + /// assert_eq!(K::Unspent.to_u8(), 0); + /// assert_eq!(K::SpentInBlockchain.to_u8(), 1); + /// assert_eq!(K::SpentInPool.to_u8(), 2); + /// ``` + pub const fn to_u8(self) -> u8 { + match self { + Self::Unspent => 0, + Self::SpentInBlockchain => 1, + Self::SpentInPool => 2, + } } - fn should_write(&self) -> bool { - todo!() - } - - fn epee_default_value() -> Option { - todo!() - } - - fn write(self, w: &mut B) -> cuprate_epee_encoding::Result<()> { - todo!() + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 2`. + /// + /// ```rust + /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// + /// assert_eq!(K::from_u8(0), Some(K::Unspent)); + /// assert_eq!(K::from_u8(1), Some(K::SpentInBlockchain)); + /// assert_eq!(K::from_u8(2), Some(K::SpentInPool)); + /// assert_eq!(K::from_u8(3), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::Unspent, + 1 => Self::SpentInBlockchain, + 2 => Self::SpentInPool, + _ => return None, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for KeyImageSpentStatus { + const MARKER: Marker = u8::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) } } diff --git a/rpc/types/src/misc/misc.rs b/rpc/types/src/misc/misc.rs index 31719a34..4643ecc5 100644 --- a/rpc/types/src/misc/misc.rs +++ b/rpc/types/src/misc/misc.rs @@ -20,7 +20,7 @@ use cuprate_epee_encoding::{ 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, + CORE_RPC_STATUS_PAYMENT_REQUIRED, }, defaults::default_zero, macros::monero_definition_link, @@ -352,19 +352,6 @@ define_struct_and_impl_epee! { } } -define_struct_and_impl_epee! { - #[doc = monero_definition_link!( - cc73fe71162d564ffda8e549b79a350bca53c454, - "cryptonote_protocol/cryptonote_protocol_defs.h", - 121..=131 - )] - /// Used in [`crate::bin::GetBlocksResponse`]. - TxBlobEntry { - blob: String, - prunable_hash: [u8; 32], - } -} - define_struct_and_impl_epee! { #[doc = monero_definition_link!( cc73fe71162d564ffda8e549b79a350bca53c454, diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs index 31dba353..bd6454dd 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -13,7 +13,7 @@ //---------------------------------------------------------------------------------------------------- Mod mod binary_string; -mod block_complete_entry; +mod distribution; mod key_image_spent_status; mod misc; mod pool_info_extent; @@ -21,13 +21,13 @@ mod status; mod tx_entry; pub use binary_string::BinaryString; -pub use block_complete_entry::BlockCompleteEntry; +pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed}; pub use key_image_spent_status::KeyImageSpentStatus; pub use misc::{ AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, GetBan, GetMinerDataTxBacklogEntry, GetOutputsOut, HardforkEntry, HistogramEntry, OutKey, OutKeyBin, OutputDistributionData, Peer, PoolTxInfo, PublicNode, SetBan, Span, SpentKeyImageInfo, - SyncInfoPeer, TxBacklogEntry, TxBlobEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats, + SyncInfoPeer, TxBacklogEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats, }; pub use pool_info_extent::PoolInfoExtent; pub use status::Status; diff --git a/rpc/types/src/misc/pool_info_extent.rs b/rpc/types/src/misc/pool_info_extent.rs index 09b6c96f..6372cd65 100644 --- a/rpc/types/src/misc/pool_info_extent.rs +++ b/rpc/types/src/misc/pool_info_extent.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] use cuprate_epee_encoding::{ + error, macros::bytes::{Buf, BufMut}, EpeeValue, Marker, }; @@ -17,33 +18,69 @@ use cuprate_epee_encoding::{ 223..=228 )] /// Used in [`crate::bin::GetBlocksResponse`]. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(u8)] pub enum PoolInfoExtent { + #[default] None = 0, Incremental = 1, Full = 2, } -// -#[cfg(feature = "epee")] -impl EpeeValue for PoolInfoExtent { - const MARKER: Marker = ::MARKER; - - fn read(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result { - todo!() +impl PoolInfoExtent { + /// Convert [`Self`] to a [`u8`]. + /// + /// ```rust + /// use cuprate_rpc_types::misc::PoolInfoExtent as P; + /// + /// assert_eq!(P::None.to_u8(), 0); + /// assert_eq!(P::Incremental.to_u8(), 1); + /// assert_eq!(P::Full.to_u8(), 2); + /// ``` + pub const fn to_u8(self) -> u8 { + match self { + Self::None => 0, + Self::Incremental => 1, + Self::Full => 2, + } } - fn should_write(&self) -> bool { - todo!() - } - - fn epee_default_value() -> Option { - todo!() - } - - fn write(self, w: &mut B) -> cuprate_epee_encoding::Result<()> { - todo!() + /// Convert a [`u8`] to a [`Self`]. + /// + /// # Errors + /// This returns [`None`] if `u > 2`. + /// + /// ```rust + /// use cuprate_rpc_types::misc::PoolInfoExtent as P; + /// + /// assert_eq!(P::from_u8(0), Some(P::None)); + /// assert_eq!(P::from_u8(1), Some(P::Incremental)); + /// assert_eq!(P::from_u8(2), Some(P::Full)); + /// assert_eq!(P::from_u8(3), None); + /// ``` + pub const fn from_u8(u: u8) -> Option { + Some(match u { + 0 => Self::None, + 1 => Self::Incremental, + 2 => Self::Full, + _ => return None, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeValue for PoolInfoExtent { + const MARKER: Marker = ::MARKER; + + fn read(r: &mut B, marker: &Marker) -> error::Result { + let u = u8::read(r, marker)?; + Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2")) + } + + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) } } diff --git a/rpc/types/src/misc/status.rs b/rpc/types/src/misc/status.rs index f2dff1a8..79725cff 100644 --- a/rpc/types/src/misc/status.rs +++ b/rpc/types/src/misc/status.rs @@ -14,7 +14,7 @@ use cuprate_epee_encoding::{ 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, + CORE_RPC_STATUS_PAYMENT_REQUIRED, }; //---------------------------------------------------------------------------------------------------- Status @@ -33,37 +33,37 @@ use crate::constants::{ /// 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 +/// CORE_RPC_STATUS_PAYMENT_REQUIRED /// }; /// use serde_json::to_string; /// -/// let unknown = Status::Unknown; +/// let other = Status::Other("OTHER".into()); /// /// 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!(to_string(&other).unwrap(), r#""OTHER""#); /// /// 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!(other.as_ref(), "OTHER"); /// /// 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!("{}", other), "OTHER"); /// /// 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"); +/// assert_eq!(format!("{:?}", other), r#"Other("OTHER")"#); /// ``` -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Status { // FIXME: @@ -86,17 +86,12 @@ pub enum Status { #[cfg_attr(feature = "serde", serde(rename = "PAYMENT REQUIRED"))] PaymentRequired, - /// Some unknown other string; [`CORE_RPC_STATUS_UNKNOWN`]. + /// Some unknown other string. /// - /// 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, + /// This exists to act as a catch-all for all of + /// `monerod`'s other strings it puts in the `status` field. + #[cfg_attr(feature = "serde", serde(rename = "OTHER"), serde(untagged))] + Other(String), } impl From for Status { @@ -106,7 +101,7 @@ impl From for Status { CORE_RPC_STATUS_BUSY => Self::Busy, CORE_RPC_STATUS_NOT_MINING => Self::NotMining, CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired, - _ => Self::Unknown, + _ => Self::Other(s), } } } @@ -118,7 +113,7 @@ impl AsRef for Status { 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, + Self::Other(s) => s, } } } @@ -150,7 +145,7 @@ impl EpeeValue for Status { fn epee_default_value() -> Option { // - Some(Self::Unknown) + Some(Self::Other(String::new())) } fn write(self, w: &mut B) -> cuprate_epee_encoding::Result<()> { @@ -172,11 +167,11 @@ mod test { Status::Busy, Status::NotMining, Status::PaymentRequired, - Status::Unknown, + Status::Other(String::new()), ] { let mut buf = vec![]; - ::write(status, &mut buf).unwrap(); + ::write(status.clone(), &mut buf).unwrap(); let status2 = ::read(&mut buf.as_slice(), &::MARKER) .unwrap(); diff --git a/rpc/types/src/misc/tx_entry.rs b/rpc/types/src/misc/tx_entry.rs index 70fbdff3..e643076d 100644 --- a/rpc/types/src/misc/tx_entry.rs +++ b/rpc/types/src/misc/tx_entry.rs @@ -2,13 +2,15 @@ //---------------------------------------------------------------------------------------------------- Use #[cfg(feature = "serde")] +use crate::serde::{serde_false, serde_true}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] use cuprate_epee_encoding::{ - epee_object, + epee_object, error, macros::bytes::{Buf, BufMut}, - EpeeValue, Marker, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, }; //---------------------------------------------------------------------------------------------------- TxEntry @@ -18,42 +20,127 @@ use cuprate_epee_encoding::{ 389..=428 )] /// Used in [`crate::other::GetTransactionsResponse`]. -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// +/// # Epee +/// This type is only used in a JSON endpoint, so the +/// epee implementation on this type only panics. +/// +/// It is only implemented to satisfy the RPC type generator +/// macro, which requires all objects to be serde + epee. +/// +/// # Example +/// ```rust +/// use cuprate_rpc_types::misc::*; +/// use serde_json::{json, from_value}; +/// +/// let json = json!({ +/// "as_hex": String::default(), +/// "as_json": String::default(), +/// "block_height": u64::default(), +/// "block_timestamp": u64::default(), +/// "confirmations": u64::default(), +/// "double_spend_seen": bool::default(), +/// "output_indices": Vec::::default(), +/// "prunable_as_hex": String::default(), +/// "prunable_hash": String::default(), +/// "pruned_as_hex": String::default(), +/// "tx_hash": String::default(), +/// "in_pool": bool::default(), +/// }); +/// let tx_entry = from_value::(json).unwrap(); +/// assert!(matches!(tx_entry, TxEntry::InPool { .. })); +/// +/// let json = json!({ +/// "as_hex": String::default(), +/// "as_json": String::default(), +/// "double_spend_seen": bool::default(), +/// "prunable_as_hex": String::default(), +/// "prunable_hash": String::default(), +/// "pruned_as_hex": String::default(), +/// "received_timestamp": u64::default(), +/// "relayed": bool::default(), +/// "tx_hash": String::default(), +/// "in_pool": bool::default(), +/// }); +/// let tx_entry = from_value::(json).unwrap(); +/// assert!(matches!(tx_entry, TxEntry::NotInPool { .. })); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TxEntry { - pub as_hex: String, - pub as_json: String, - pub block_height: u64, - pub block_timestamp: u64, - pub confirmations: u64, - pub double_spend_seen: bool, - pub in_pool: bool, - pub output_indices: Vec, - pub prunable_as_hex: String, - pub prunable_hash: String, - pub pruned_as_hex: String, - pub received_timestamp: u64, - pub relayed: bool, - pub tx_hash: String, +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum TxEntry { + /// This entry exists in the transaction pool. + InPool { + as_hex: String, + as_json: String, + block_height: u64, + block_timestamp: u64, + confirmations: u64, + double_spend_seen: bool, + output_indices: Vec, + prunable_as_hex: String, + prunable_hash: String, + pruned_as_hex: String, + tx_hash: String, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] + /// Will always be serialized as `true`. + in_pool: bool, + }, + /// This entry _does not_ exist in the transaction pool. + NotInPool { + as_hex: String, + as_json: String, + double_spend_seen: bool, + prunable_as_hex: String, + prunable_hash: String, + pruned_as_hex: String, + received_timestamp: u64, + relayed: bool, + tx_hash: String, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] + /// Will always be serialized as `false`. + in_pool: bool, + }, } -// TODO: custom epee -// -#[cfg(feature = "epee")] -epee_object! { - TxEntry, - as_hex: String, - as_json: String, // TODO: should be its own struct - block_height: u64, - block_timestamp: u64, - confirmations: u64, - double_spend_seen: bool, - in_pool: bool, - output_indices: Vec, - prunable_as_hex: String, - prunable_hash: String, - pruned_as_hex: String, - received_timestamp: u64, - relayed: bool, - tx_hash: String, +impl Default for TxEntry { + fn default() -> Self { + Self::NotInPool { + as_hex: String::default(), + as_json: String::default(), + double_spend_seen: bool::default(), + prunable_as_hex: String::default(), + prunable_hash: String::default(), + pruned_as_hex: String::default(), + received_timestamp: u64::default(), + relayed: bool::default(), + tx_hash: String::default(), + in_pool: false, + } + } +} + +//---------------------------------------------------------------------------------------------------- Epee +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for () { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + unreachable!() + } + + fn finish(self) -> error::Result { + unreachable!() + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for TxEntry { + type Builder = (); + + fn number_of_fields(&self) -> u64 { + unreachable!() + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + unreachable!() + } } diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 5ad2caac..41530cba 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -8,12 +8,12 @@ use crate::{ defaults::{default_false, default_string, default_true}, macros::define_request_and_response, misc::{ - GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, - TxpoolStats, + GetOutputsOut, KeyImageSpentStatus, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, + TxEntry, TxInfo, TxpoolStats, }, }; -//---------------------------------------------------------------------------------------------------- TODO +//---------------------------------------------------------------------------------------------------- Definitions define_request_and_response! { get_height, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -68,7 +68,8 @@ define_request_and_response! { key_images: Vec, }, AccessResponseBase { - spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. + /// FIXME: These are [`KeyImageSpentStatus`] in [`u8`] form. + spent_status: Vec, } } @@ -219,7 +220,6 @@ define_request_and_response! { password: String, proxy: String, }, - #[derive(Copy)] Response { status: Status, } diff --git a/rpc/types/src/serde.rs b/rpc/types/src/serde.rs new file mode 100644 index 00000000..70885e09 --- /dev/null +++ b/rpc/types/src/serde.rs @@ -0,0 +1,32 @@ +//! Custom (de)serialization functions for serde. + +//---------------------------------------------------------------------------------------------------- Lints +#![allow(clippy::trivially_copy_pass_by_ref)] // serde fn signature + +//---------------------------------------------------------------------------------------------------- Import +use serde::Serializer; + +//---------------------------------------------------------------------------------------------------- Free functions +/// Always serializes `true`. +#[inline] +pub(crate) fn serde_true(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(true) +} + +/// Always serializes `false`. +#[inline] +pub(crate) fn serde_false(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(false) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use super::*; +} diff --git a/types/README.md b/types/README.md index 4023e9ff..876931fd 100644 --- a/types/README.md +++ b/types/README.md @@ -8,4 +8,4 @@ This crate is a kitchen-sink for data types that are shared across Cuprate. |--------------|-----------| | `blockchain` | Enables the `blockchain` module, containing the blockchain database request/response types | `serde` | Enables `serde` on types where applicable -| `epee` | Enables `cuprate-epee-encoding` on types where applicable \ No newline at end of file +| `epee` | Enables `cuprate-epee-encoding` on types where applicable