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 <boog900@tutanota.com>

* 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 <boog900@tutanota.com>
This commit is contained in:
hinto-janai 2024-07-25 11:46:41 -04:00 committed by GitHub
parent aa718e224f
commit 929d19c450
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 949 additions and 236 deletions

2
Cargo.lock generated
View file

@ -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",
]

View file

@ -16,6 +16,7 @@ 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 }
@ -23,3 +24,4 @@ serde = { workspace = true, optional = true }
[dev-dependencies]
serde_json = { workspace = true }
pretty_assertions = { workspace = true }

View file

@ -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`.

View file

@ -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.

View file

@ -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.
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L242-L259>
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]>,
}
}
//---------------------------------------------------------------------------------------------------- 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::<u8>(), "default_zero",
// FIXME: This is a `std::list` in `monerod` because...?
block_ids: ByteArrayVec<32>,
start_height: u64,
prune: bool,
no_miner_tx: bool = default_false(), "default_false",
pool_info_since: u64 = default_zero::<u64>(), "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<BlockCompleteEntry>,
pub start_height: u64,
pub current_height: u64,
pub output_indices: Vec<BlockOutputIndices>,
pub daemon_time: u64,
}
#[cfg(feature = "epee")]
epee_object! {
GetBlocksResponsePoolInfoNone,
status: Status,
untrusted: bool,
blocks: Vec<BlockCompleteEntry>,
start_height: u64,
current_height: u64,
output_indices: Vec<BlockOutputIndices>,
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<BlockCompleteEntry>,
pub start_height: u64,
pub current_height: u64,
pub output_indices: Vec<BlockOutputIndices>,
pub daemon_time: u64,
pub added_pool_txs: Vec<PoolTxInfo>,
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<BlockCompleteEntry>,
start_height: u64,
current_height: u64,
output_indices: Vec<BlockOutputIndices>,
daemon_time: u64,
added_pool_txs: Vec<PoolTxInfo>,
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<BlockCompleteEntry>,
pub start_height: u64,
pub current_height: u64,
pub output_indices: Vec<BlockOutputIndices>,
pub daemon_time: u64,
pub added_pool_txs: Vec<PoolTxInfo>,
pub remaining_added_pool_txids: ByteArrayVec<32>,
}
#[cfg(feature = "epee")]
epee_object! {
GetBlocksResponsePoolInfoFull,
status: Status,
untrusted: bool,
blocks: Vec<BlockCompleteEntry>,
start_height: u64,
current_height: u64,
output_indices: Vec<BlockOutputIndices>,
daemon_time: u64,
added_pool_txs: Vec<PoolTxInfo>,
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<Status>,
pub untrusted: Option<bool>,
pub blocks: Option<Vec<BlockCompleteEntry>>,
pub start_height: Option<u64>,
pub current_height: Option<u64>,
pub output_indices: Option<Vec<BlockOutputIndices>>,
pub daemon_time: Option<u64>,
pub pool_info_extent: Option<PoolInfoExtent>,
pub added_pool_txs: Option<Vec<PoolTxInfo>>,
pub remaining_added_pool_txids: Option<ByteArrayVec<32>>,
pub removed_pool_txids: Option<ByteArrayVec<32>>,
}
#[cfg(feature = "epee")]
impl EpeeObjectBuilder<GetBlocksResponse> for __GetBlocksResponseEpeeBuilder {
fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> error::Result<bool> {
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<GetBlocksResponse> {
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<B: BufMut>(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 {

View file

@ -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.

View file

@ -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<String>,
}
@ -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<String> = default_vec::<String>(), "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::<u64>(), "default_zero",
#[serde(skip_serializing_if = "is_zero")]
target_height: u64 = default_zero(), "default_zero",
target_height: u64 = default_zero::<u64>(), "default_zero",
#[serde(skip_serializing_if = "Vec::is_empty")]
hard_forks: Vec<HardforkEntry> = default_vec(), "default_vec",
}
@ -520,7 +517,6 @@ define_request_and_response! {
Request {
txids: Vec<String>,
},
#[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:
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L2468-L2508>
AccessResponseBase {
distributions: Vec<OutputDistributionData>,
distributions: Vec<Distribution>,
}
}
@ -607,7 +601,6 @@ define_request_and_response! {
Request {
check: bool = default_false(), "default_false",
},
#[derive(Copy)]
ResponseBase {
pruned: bool,
pruning_seed: u32,

View file

@ -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,
};

View file

@ -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.

View file

@ -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<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>,
}

View file

@ -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: <https://github.com/Cuprate/cuprate/pull/229#discussion_r1690531904>.
///
/// 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<Vec<u8>> {
todo!()
}
/// TODO: <https://github.com/Cuprate/cuprate/pull/229#discussion_r1690531904>.
///
/// 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<u64> {
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<u64>,
pub amount: u64,
pub binary: bool,
}
#[cfg(feature = "epee")]
epee_object! {
DistributionUncompressed,
start_height: u64,
base: u64,
distribution: Vec<u64>,
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<u64>,
pub amount: u64,
}
#[cfg(feature = "epee")]
epee_object! {
DistributionCompressedBinary,
start_height: u64,
base: u64,
distribution: Vec<u64>,
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<S>(v: &Vec<u64>, s: S) -> Result<S::Ok, S::Error>
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<Vec<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
Vec::<u8>::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<u64>,
pub base: Option<u64>,
pub distribution: Option<Vec<u64>>,
pub amount: Option<u64>,
pub compressed_data: Option<Vec<u8>>,
pub binary: Option<bool>,
pub compress: Option<bool>,
}
#[cfg(feature = "epee")]
impl EpeeObjectBuilder<Distribution> for __DistributionEpeeBuilder {
fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> error::Result<bool> {
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<Distribution> {
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<B: BufMut>(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);
// }
}

View file

@ -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,
}
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,
}
}
/// 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<Self> {
Some(match u {
0 => Self::Unspent,
1 => Self::SpentInBlockchain,
2 => Self::SpentInPool,
_ => return None,
})
}
}
#[cfg(feature = "epee")]
impl EpeeValue for KeyImageSpentStatus {
const MARKER: Marker = <String as EpeeValue>::MARKER;
const MARKER: Marker = u8::MARKER;
fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> {
todo!()
fn read<B: Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
let u = u8::read(r, marker)?;
Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2"))
}
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!()
fn write<B: BufMut>(self, w: &mut B) -> error::Result<()> {
let u = self.to_u8();
u8::write(u, w)?;
Ok(())
}
}

View file

@ -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,

View file

@ -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;

View file

@ -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,
}
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_protocol/cryptonote_protocol_defs.h#L138-L163>
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,
}
}
/// 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<Self> {
Some(match u {
0 => Self::None,
1 => Self::Incremental,
2 => Self::Full,
_ => return None,
})
}
}
#[cfg(feature = "epee")]
impl EpeeValue for PoolInfoExtent {
const MARKER: Marker = <String as EpeeValue>::MARKER;
const MARKER: Marker = <u8 as EpeeValue>::MARKER;
fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> {
todo!()
fn read<B: Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
let u = u8::read(r, marker)?;
Self::from_u8(u).ok_or(error::Error::Format("u8 was greater than 2"))
}
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!()
fn write<B: BufMut>(self, w: &mut B) -> error::Result<()> {
let u = self.to_u8();
u8::write(u, w)?;
Ok(())
}
}

View file

@ -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<String> for Status {
@ -106,7 +101,7 @@ impl From<String> 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<str> 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<Self> {
// <https://github.com/Cuprate/cuprate/pull/147#discussion_r1654992559>
Some(Self::Unknown)
Some(Self::Other(String::new()))
}
fn write<B: BufMut>(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![];
<Status as EpeeValue>::write(status, &mut buf).unwrap();
<Status as EpeeValue>::write(status.clone(), &mut buf).unwrap();
let status2 =
<Status as EpeeValue>::read(&mut buf.as_slice(), &<Status as EpeeValue>::MARKER)
.unwrap();

View file

@ -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::<u64>::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::<TxEntry>(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::<TxEntry>(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<u64>,
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,
}
// TODO: custom epee
// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L406-L427>
#[cfg(feature = "epee")]
epee_object! {
TxEntry,
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum TxEntry {
/// This entry exists in the transaction pool.
InPool {
as_hex: String,
as_json: String, // TODO: should be its own struct
as_json: String,
block_height: u64,
block_timestamp: u64,
confirmations: u64,
double_spend_seen: bool,
in_pool: bool,
output_indices: Vec<u64>,
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,
},
}
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<TxEntry> for () {
fn add_field<B: Buf>(&mut self, name: &str, r: &mut B) -> error::Result<bool> {
unreachable!()
}
fn finish(self) -> error::Result<TxEntry> {
unreachable!()
}
}
#[cfg(feature = "epee")]
impl EpeeObject for TxEntry {
type Builder = ();
fn number_of_fields(&self) -> u64 {
unreachable!()
}
fn write_fields<B: BufMut>(self, w: &mut B) -> error::Result<()> {
unreachable!()
}
}

View file

@ -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<String>,
},
AccessResponseBase {
spent_status: Vec<u8>, // TODO: should be `KeyImageSpentStatus`.
/// FIXME: These are [`KeyImageSpentStatus`] in [`u8`] form.
spent_status: Vec<u8>,
}
}
@ -219,7 +220,6 @@ define_request_and_response! {
password: String,
proxy: String,
},
#[derive(Copy)]
Response {
status: Status,
}

32
rpc/types/src/serde.rs Normal file
View file

@ -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<S>(_: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(true)
}
/// Always serializes `false`.
#[inline]
pub(crate) fn serde_false<S>(_: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(false)
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use super::*;
}