diff --git a/Cargo.lock b/Cargo.lock
index 7a9d0744..543eea96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -769,8 +769,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/Cargo.toml b/Cargo.toml
index b1ee6a6a..3ebaefd9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -276,7 +276,8 @@ clone_on_ref_ptr = "deny"
# Cold
absolute_paths_not_starting_with_crate = "deny"
explicit_outlives_requirements = "deny"
-keyword_idents = "deny"
+keyword_idents_2018 = "deny"
+keyword_idents_2024 = "deny"
missing_abi = "deny"
non_ascii_idents = "deny"
non_local_definitions = "deny"
diff --git a/helper/src/time.rs b/helper/src/time.rs
index 7bc155f6..28aff7f5 100644
--- a/helper/src/time.rs
+++ b/helper/src/time.rs
@@ -55,7 +55,7 @@ pub const fn unix_clock(seconds_after_unix_epoch: u64) -> u32 {
/// - The seconds returned is guaranteed to be `0..=59`
/// - The minutes returned is guaranteed to be `0..=59`
/// - The hours returned can be over `23`, as this is not a clock function,
-/// see [`secs_to_clock`] for clock-like behavior that wraps around on `24`
+/// see [`secs_to_clock`] for clock-like behavior that wraps around on `24`
///
/// ```rust
/// # use cuprate_helper::time::*;
diff --git a/p2p/dandelion-tower/src/pool.rs b/p2p/dandelion-tower/src/pool.rs
index 68f79451..5f4f7346 100644
--- a/p2p/dandelion-tower/src/pool.rs
+++ b/p2p/dandelion-tower/src/pool.rs
@@ -51,7 +51,7 @@ use crate::{
///
/// - `buffer_size` is the size of the channel's buffer between the [`DandelionPoolService`] and [`DandelionPool`].
/// - `dandelion_router` is the router service, kept generic instead of [`DandelionRouter`](crate::DandelionRouter) to allow
-/// user to customise routing functionality.
+/// user to customise routing functionality.
/// - `backing_pool` is the backing transaction storage service
/// - `config` is [`DandelionConfig`].
pub fn start_dandelion_pool
(
diff --git a/p2p/p2p/src/block_downloader.rs b/p2p/p2p/src/block_downloader.rs
index 81640e90..5f530546 100644
--- a/p2p/p2p/src/block_downloader.rs
+++ b/p2p/p2p/src/block_downloader.rs
@@ -194,7 +194,7 @@ where
/// - download the next batch of blocks
/// - request the next chain entry
/// - download an already requested batch of blocks (this might happen due to an error in the previous request
-/// or because the queue of ready blocks is too large, so we need the oldest block to clear it).
+/// or because the queue of ready blocks is too large, so we need the oldest block to clear it).
struct BlockDownloader {
/// The client pool.
client_pool: Arc>,
diff --git a/p2p/p2p/src/block_downloader/block_queue.rs b/p2p/p2p/src/block_downloader/block_queue.rs
index d846c22c..708eb3ed 100644
--- a/p2p/p2p/src/block_downloader/block_queue.rs
+++ b/p2p/p2p/src/block_downloader/block_queue.rs
@@ -140,6 +140,7 @@ mod tests {
proptest! {
#[test]
+ #[allow(clippy::mutable_key_type)]
fn block_queue_returns_items_in_order(batches in vec(ready_batch_strategy(), 0..10_000)) {
block_on(async move {
let (buffer_tx, mut buffer_rx) = cuprate_async_buffer::new_buffer(usize::MAX);
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