From ff8f816e264c619496823136fefdcf4aa564030a Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 10 Jul 2024 17:07:30 -0400 Subject: [PATCH 01/93] fixed-bytes: add `serde`, document feature flags --- Cargo.lock | 4 ++++ net/fixed-bytes/Cargo.toml | 6 ++++-- net/fixed-bytes/README.md | 10 ++++++++++ net/fixed-bytes/src/lib.rs | 8 ++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 net/fixed-bytes/README.md diff --git a/Cargo.lock b/Cargo.lock index e5b795eb2..df8907f44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,9 @@ name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -641,6 +644,7 @@ name = "cuprate-fixed-bytes" version = "0.1.0" dependencies = [ "bytes", + "serde", "thiserror", ] diff --git a/net/fixed-bytes/Cargo.toml b/net/fixed-bytes/Cargo.toml index b592a09ec..b7bac3a36 100644 --- a/net/fixed-bytes/Cargo.toml +++ b/net/fixed-bytes/Cargo.toml @@ -6,9 +6,11 @@ license = "MIT" authors = ["Boog900"] [features] -default = ["std"] +default = ["std", "serde"] std = ["bytes/std", "dep:thiserror"] +serde = ["bytes/serde", "dep:serde"] [dependencies] thiserror = { workspace = true, optional = true } -bytes = { workspace = true } \ No newline at end of file +bytes = { workspace = true } +serde = { workspace = true, optional = true } \ No newline at end of file diff --git a/net/fixed-bytes/README.md b/net/fixed-bytes/README.md new file mode 100644 index 000000000..b96c9fc3c --- /dev/null +++ b/net/fixed-bytes/README.md @@ -0,0 +1,10 @@ +# `cuprate-fixed-bytes` +TODO + +# Feature flags +| Feature flag | Does what | +|--------------|-----------| +| `std` | TODO +| `serde` | Enables `serde` on applicable types + +`serde` is enabled by default. \ No newline at end of file diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index 8776d309d..3cbd38a0d 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + use core::{ fmt::{Debug, Formatter}, ops::{Deref, Index}, @@ -5,7 +7,11 @@ use core::{ use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[cfg_attr(feature = "std", derive(thiserror::Error))] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum FixedByteError { #[cfg_attr( feature = "std", @@ -43,6 +49,7 @@ impl Debug for FixedByteError { /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to `N`. /// This implements [`Deref`] with the target being `[u8; N]`. #[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ByteArray(Bytes); impl ByteArray { @@ -88,6 +95,7 @@ impl TryFrom> for ByteArray { } #[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ByteArrayVec(Bytes); impl ByteArrayVec { From 9a91a30584814a28600d7d1b5e51c684b4c3ec55 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 10 Jul 2024 20:22:14 -0400 Subject: [PATCH 02/93] fixed-bytes: add derives --- net/fixed-bytes/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index 3cbd38a0d..7fe9cd617 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "std", derive(thiserror::Error))] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum FixedByteError { #[cfg_attr( feature = "std", @@ -48,7 +49,7 @@ impl Debug for FixedByteError { /// /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to `N`. /// This implements [`Deref`] with the target being `[u8; N]`. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ByteArray(Bytes); @@ -94,7 +95,7 @@ impl TryFrom> for ByteArray { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct ByteArrayVec(Bytes); From 950ef253177d8cee0a1bdfada85162d6a7ff7117 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 10 Jul 2024 20:22:37 -0400 Subject: [PATCH 03/93] rpc: add `as _` syntax to macro --- rpc/types/src/macros.rs | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index 31bc6bedf..dd75bbae5 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -68,7 +68,9 @@ macro_rules! define_request_and_response { // And any fields. $( $( #[$request_field_attr:meta] )* - $request_field:ident: $request_field_type:ty $(= $request_field_type_default:expr)?, + $request_field:ident: $request_field_type:ty + $(as $request_field_type_as:ty)? + $(= $request_field_type_default:expr)?, )* }, @@ -78,7 +80,9 @@ macro_rules! define_request_and_response { // And any fields. $( $( #[$response_field_attr:meta] )* - $response_field:ident: $response_field_type:ty $(= $response_field_type_default:expr)?, + $response_field:ident: $response_field_type:ty + $(as $response_field_type_as:ty)? + $(= $response_field_type_default:expr)?, )* } ) => { paste::paste! { @@ -99,7 +103,9 @@ macro_rules! define_request_and_response { [<$type_name Request>] { $( $( #[$request_field_attr] )* - $request_field: $request_field_type $(= $request_field_type_default)?, + $request_field: $request_field_type + $(as $request_field_type_as)? + $(= $request_field_type_default)?, )* } } @@ -125,7 +131,9 @@ macro_rules! define_request_and_response { $response_base_type => [<$type_name Response>] { $( $( #[$response_field_attr] )* - $response_field: $response_field_type $(= $response_field_type_default)?, + $response_field: $response_field_type + $(as $response_field_type_as)? + $(= $response_field_type_default)?, )* } } @@ -166,7 +174,9 @@ macro_rules! __define_request { $( $( #[$field_attr:meta] )* // field attributes // field_name: FieldType - $field:ident: $field_type:ty $(= $field_default:expr)?, + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr)?, // The $field_default is an optional extra token that represents // a default value to pass to [`cuprate_epee_encoding::epee_object`], // see it for usage. @@ -188,7 +198,9 @@ macro_rules! __define_request { ::cuprate_epee_encoding::epee_object! { $t, $( - $field: $field_type $(= $field_default)?, + $field: $field_type + $(as $field_as)? + $(= $field_default)?, )* } }; @@ -218,7 +230,9 @@ macro_rules! __define_response { // See [`__define_request`] for docs, this does the same thing. $( $( #[$field_attr:meta] )* - $field:ident: $field_type:ty $(= $field_default:expr)?, + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr)?, )* } ) => { @@ -234,7 +248,9 @@ macro_rules! __define_response { ::cuprate_epee_encoding::epee_object! { $t, $( - $field: $field_type $($field_default)?, + $field: $field_type + $(as $field_as)? + $(= $field_default)?, )* } }; @@ -250,7 +266,9 @@ macro_rules! __define_response { // See [`__define_request`] for docs, this does the same thing. $( $( #[$field_attr:meta] )* - $field:ident: $field_type:ty $(= $field_default:expr)?, + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr)?, )* } ) => { @@ -269,7 +287,9 @@ macro_rules! __define_response { ::cuprate_epee_encoding::epee_object! { $t, $( - $field: $field_type $(= $field_default)?, + $field: $field_type + $(as $field_as)? + $(= $field_default)?, )* !flatten: base: $base, } From ce7ad2eb13825b034768f8d36387d2246e119cdf Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 10 Jul 2024 20:23:03 -0400 Subject: [PATCH 04/93] rpc: use `ByteArrayVec` and `ContainerAsBlob` for binary types --- Cargo.lock | 1 + rpc/types/Cargo.toml | 3 ++- rpc/types/src/bin.rs | 29 +++++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df8907f44..3bebaf8c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,6 +761,7 @@ name = "cuprate-rpc-types" version = "0.0.0" dependencies = [ "cuprate-epee-encoding", + "cuprate-fixed-bytes", "monero-serai", "paste", "serde", diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index c088e4df2..1176526ad 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -10,11 +10,12 @@ keywords = ["cuprate", "rpc", "types", "monero"] [features] default = ["serde", "epee"] -serde = ["dep:serde"] +serde = ["dep:serde", "cuprate-fixed-bytes/serde"] epee = ["dep:cuprate-epee-encoding"] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true } +cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } monero-serai = { workspace = true } paste = { workspace = true } diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 02be19389..646da0f2d 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -3,6 +3,11 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +use cuprate_fixed_bytes::ByteArrayVec; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::container_as_blob::ContainerAsBlob; + use crate::{ base::{AccessResponseBase, ResponseBase}, defaults::{default_false, default_height, default_string, default_vec, default_zero}, @@ -25,7 +30,7 @@ define_request_and_response! { #[cfg_attr(feature = "serde", serde(default = "default_zero"))] requested_info: u8 = default_zero(), // TODO: This is a `std::list` in `monerod` because...? - block_ids: Vec<[u8; 32]>, + block_ids: ByteArrayVec<32>, start_height: u64, prune: bool, #[cfg_attr(feature = "serde", serde(default = "default_false"))] @@ -67,16 +72,17 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 309..=338, GetHashes, Request { - block_ids: Vec<[u8; 32]>, + block_ids: ByteArrayVec<32>, start_height: u64, }, AccessResponseBase { - m_blocks_ids: Vec<[u8; 32]>, + m_blocks_ids: ByteArrayVec<32>, start_height: u64, current_height: u64, } } +#[cfg(not(feature = "epee"))] define_request_and_response! { get_o_indexesbin, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -91,6 +97,21 @@ define_request_and_response! { } } +#[cfg(feature = "epee")] +define_request_and_response! { + get_o_indexesbin, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 487..=510, + GetOutputIndexes, + #[derive(Copy)] + Request { + txid: [u8; 32], + }, + AccessResponseBase { + o_indexes: Vec as ContainerAsBlob, + } +} + define_request_and_response! { get_outsbin, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -113,7 +134,7 @@ define_request_and_response! { GetTransactionPoolHashes, Request {}, AccessResponseBase { - tx_hashes: Vec<[u8; 32]>, + tx_hashes: ByteArrayVec<32>, } } From a97af42ca25360471fdda744607ce03d480c5e9d Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 11 Jul 2024 16:36:20 -0400 Subject: [PATCH 05/93] fixed-bytes: re-add derives --- net/fixed-bytes/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index 370b8817e..2e8f1bc58 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Deserializer, Serialize}; #[cfg_attr(feature = "std", derive(thiserror::Error))] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum FixedByteError { #[cfg_attr( feature = "std", @@ -48,7 +49,7 @@ impl Debug for FixedByteError { /// /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to `N`. /// This implements [`Deref`] with the target being `[u8; N]`. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] @@ -115,7 +116,7 @@ impl TryFrom> for ByteArray { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] From 66bb3fc1db1d7532378922f1cda470672c04dcd4 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 11 Jul 2024 16:42:39 -0400 Subject: [PATCH 06/93] rpc-types: dedup default value within macro --- rpc/types/src/bin.rs | 14 +++----- rpc/types/src/json.rs | 80 ++++++++++++++++++++++++----------------- rpc/types/src/macros.rs | 25 +++++++------ rpc/types/src/other.rs | 39 +++++++------------- 4 files changed, 79 insertions(+), 79 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 646da0f2d..3dcfb9674 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -27,16 +27,13 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 162..=262, GetBlocks, Request { - #[cfg_attr(feature = "serde", serde(default = "default_zero"))] - requested_info: u8 = default_zero(), - // TODO: This is a `std::list` in `monerod` because...? + 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, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - no_miner_tx: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_zero"))] - pool_info_since: u64 = default_zero(), + no_miner_tx: bool = default_false(), "default_false", + pool_info_since: u64 = default_zero(), "default_zero", }, // TODO: this has custom epee (de)serialization. // @@ -119,8 +116,7 @@ define_request_and_response! { GetOuts, Request { outputs: Vec, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - get_txid: bool = default_false(), + get_txid: bool = default_false(), "default_false", }, AccessResponseBase { outs: Vec, diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index b5b53c9a5..2e7aa82ac 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -49,8 +49,33 @@ define_request_and_response! { // $FIELD_NAME: $FIELD_TYPE, // ``` // The struct generated and all fields are `pub`. - extra_nonce: String, - prev_block: String, + + // This optional expression can be placed after + // a `field: field_type`. this indicates to the + // macro to (de)serialize this field using this + // default expression if it doesn't exist in epee. + // + // See `cuprate_epee_encoding::epee_object` for info. + // + // The default function must be specified twice: + // + // 1. As an expression + // 2. As a string literal + // + // For example: `extra_nonce: String /* = default_string(), "default_string" */,` + // + // This is a HACK since `serde`'s default attribute only takes in + // string literals and macros (stringify) within attributes do not work. + extra_nonce: String /* = default_expression, "default_literal" */, + + // Another optional expression: + // This indicates to the macro to (de)serialize + // this field as another type in epee. + // + // See `cuprate_epee_encoding::epee_object` for info. + prev_block: String /* as Type */, + + // Regular fields. reserve_size: u64, wallet_address: String, }, @@ -197,8 +222,7 @@ define_request_and_response! { GetLastBlockHeader, #[derive(Copy)] Request { - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - fill_pow_hash: bool = default_false(), + fill_pow_hash: bool = default_false(), "default_false", }, AccessResponseBase { block_header: BlockHeader, @@ -213,8 +237,7 @@ define_request_and_response! { Request { hash: String, hashes: Vec, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - fill_pow_hash: bool = default_false(), + fill_pow_hash: bool = default_false(), "default_false", }, AccessResponseBase { block_header: BlockHeader, @@ -230,8 +253,7 @@ define_request_and_response! { #[derive(Copy)] Request { height: u64, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - fill_pow_hash: bool = default_false(), + fill_pow_hash: bool = default_false(), "default_false", }, AccessResponseBase { block_header: BlockHeader, @@ -247,8 +269,7 @@ define_request_and_response! { Request { start_height: u64, end_height: u64, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - fill_pow_hash: bool = default_false(), + fill_pow_hash: bool = default_false(), "default_false", }, AccessResponseBase { headers: Vec, @@ -264,12 +285,9 @@ define_request_and_response! { // `monerod` has both `hash` and `height` fields. // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. // - #[cfg_attr(feature = "serde", serde(default = "default_string"))] - hash: String = default_string(), - #[cfg_attr(feature = "serde", serde(default = "default_height"))] - height: u64 = default_height(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - fill_pow_hash: bool = default_false(), + hash: String = default_string(), "default_string", + height: u64 = default_height(), "default_height", + fill_pow_hash: bool = default_false(), "default_false", }, AccessResponseBase { blob: String, @@ -287,7 +305,7 @@ define_request_and_response! { GetConnections, Request {}, ResponseBase { - // TODO: This is a `std::list` in `monerod` because...? + // FIXME: This is a `std::list` in `monerod` because...? connections: Vec, } } @@ -405,8 +423,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 2096..=2116, FlushTransactionPool, Request { - #[cfg_attr(feature = "serde", serde(default = "default_vec"))] - txids: Vec = default_vec::(), + txids: Vec = default_vec::(), "default_vec", }, #[derive(Copy)] #[cfg_attr(feature = "serde", serde(transparent))] @@ -461,12 +478,12 @@ define_request_and_response! { ResponseBase { version: u32, release: bool, - #[serde(skip_serializing_if = "is_zero", default = "default_zero")] - current_height: u64 = default_zero(), - #[serde(skip_serializing_if = "is_zero", default = "default_zero")] - target_height: u64 = default_zero(), - #[serde(skip_serializing_if = "Vec::is_empty", default = "default_vec")] - hard_forks: Vec = default_vec(), + #[serde(skip_serializing_if = "is_zero")] + current_height: u64 = default_zero(), "default_zero", + #[serde(skip_serializing_if = "is_zero")] + target_height: u64 = default_zero(), "default_zero", + #[serde(skip_serializing_if = "Vec::is_empty")] + hard_forks: Vec = default_vec(), "default_vec", } } @@ -521,9 +538,9 @@ define_request_and_response! { height: u64, next_needed_pruning_seed: u32, overview: String, - // TODO: This is a `std::list` in `monerod` because...? + // FIXME: This is a `std::list` in `monerod` because...? peers: Vec, - // TODO: This is a `std::list` in `monerod` because...? + // FIXME: This is a `std::list` in `monerod` because...? spans: Vec, target_height: u64, } @@ -588,8 +605,7 @@ define_request_and_response! { PruneBlockchain, #[derive(Copy)] Request { - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - check: bool = default_false(), + check: bool = default_false(), "default_false", }, #[derive(Copy)] ResponseBase { @@ -623,10 +639,8 @@ define_request_and_response! { FlushCache, #[derive(Copy)] Request { - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - bad_txs: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - bad_blocks: bool = default_false(), + bad_txs: bool = default_false(), "default_false", + bad_blocks: bool = default_false(), "default_false", }, ResponseBase {} } diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index dd75bbae5..e13013872 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -45,7 +45,7 @@ /// would trigger the different branches. macro_rules! define_request_and_response { ( - // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. $monero_daemon_rpc_doc_link:ident, // The commit hash and `$file.$extension` in which this type is defined in @@ -67,10 +67,10 @@ macro_rules! define_request_and_response { Request { // And any fields. $( - $( #[$request_field_attr:meta] )* - $request_field:ident: $request_field_type:ty - $(as $request_field_type_as:ty)? - $(= $request_field_type_default:expr)?, + $( #[$request_field_attr:meta] )* // Field attribute. + $request_field:ident: $request_field_type:ty // field_name: field type + $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization + $(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value )* }, @@ -82,7 +82,7 @@ macro_rules! define_request_and_response { $( #[$response_field_attr:meta] )* $response_field:ident: $response_field_type:ty $(as $response_field_type_as:ty)? - $(= $response_field_type_default:expr)?, + $(= $response_field_type_default:expr, $response_field_type_default_string:literal)?, )* } ) => { paste::paste! { @@ -105,7 +105,7 @@ macro_rules! define_request_and_response { $( #[$request_field_attr] )* $request_field: $request_field_type $(as $request_field_type_as)? - $(= $request_field_type_default)?, + $(= $request_field_type_default, $request_field_type_default_string)?, )* } } @@ -133,7 +133,7 @@ macro_rules! define_request_and_response { $( #[$response_field_attr] )* $response_field: $response_field_type $(as $response_field_type_as)? - $(= $response_field_type_default)?, + $(= $response_field_type_default, $response_field_type_default_string)?, )* } } @@ -176,7 +176,7 @@ macro_rules! __define_request { // field_name: FieldType $field:ident: $field_type:ty $(as $field_as:ty)? - $(= $field_default:expr)?, + $(= $field_default:expr, $field_default_string:literal)?, // The $field_default is an optional extra token that represents // a default value to pass to [`cuprate_epee_encoding::epee_object`], // see it for usage. @@ -190,6 +190,7 @@ macro_rules! __define_request { pub struct $t { $( $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? pub $field: $field_type, )* } @@ -232,7 +233,7 @@ macro_rules! __define_response { $( #[$field_attr:meta] )* $field:ident: $field_type:ty $(as $field_as:ty)? - $(= $field_default:expr)?, + $(= $field_default:expr, $field_default_string:literal)?, )* } ) => { @@ -240,6 +241,7 @@ macro_rules! __define_response { pub struct $t { $( $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? pub $field: $field_type, )* } @@ -268,7 +270,7 @@ macro_rules! __define_response { $( #[$field_attr:meta] )* $field:ident: $field_type:ty $(as $field_as:ty)? - $(= $field_default:expr)?, + $(= $field_default:expr, $field_default_string:literal)?, )* } ) => { @@ -279,6 +281,7 @@ macro_rules! __define_response { $( $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? pub $field: $field_type, )* } diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 03cb05dd4..5ad2caacd 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -36,12 +36,9 @@ define_request_and_response! { // FIXME: this is documented as optional but it isn't serialized as an optional // but it is set _somewhere_ to false in `monerod` // - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - decode_as_json: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - prune: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - split: bool = default_false(), + decode_as_json: bool = default_false(), "default_false", + prune: bool = default_false(), "default_false", + split: bool = default_false(), "default_false", }, AccessResponseBase { txs_as_hex: Vec, @@ -82,10 +79,8 @@ define_request_and_response! { SendRawTransaction, Request { tx_as_hex: String, - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - do_not_relay: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_true"))] - do_sanity_checks: bool = default_true(), + do_not_relay: bool = default_false(), "default_false", + do_sanity_checks: bool = default_true(), "default_true", }, AccessResponseBase { double_spend: bool, @@ -167,10 +162,8 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1369..=1417, GetPeerList, Request { - #[cfg_attr(feature = "serde", serde(default = "default_true"))] - public_only: bool = default_true(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - include_blocked: bool = default_false(), + public_only: bool = default_true(), "default_true", + include_blocked: bool = default_false(), "default_false", }, ResponseBase { white_list: Vec, @@ -208,8 +201,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1494..=1517, SetLogCategories, Request { - #[cfg_attr(feature = "serde", serde(default = "default_string"))] - categories: String = default_string(), + categories: String = default_string(), "default_string", }, ResponseBase { categories: String, @@ -300,8 +292,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1876..=1903, OutPeers, Request { - #[cfg_attr(feature = "serde", serde(default = "default_true"))] - set: bool = default_true(), + set: bool = default_true(), "default_true", out_peers: u32, }, ResponseBase { @@ -345,8 +336,7 @@ define_request_and_response! { Update, Request { command: String, - #[cfg_attr(feature = "serde", serde(default = "default_string"))] - path: String = default_string(), + path: String = default_string(), "default_string", }, ResponseBase { auto_uri: String, @@ -402,12 +392,9 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1419..=1448, GetPublicNodes, Request { - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - gray: bool = default_false(), - #[cfg_attr(feature = "serde", serde(default = "default_true"))] - white: bool = default_true(), - #[cfg_attr(feature = "serde", serde(default = "default_false"))] - include_blocked: bool = default_false(), + gray: bool = default_false(), "default_false", + white: bool = default_true(), "default_true", + include_blocked: bool = default_false(), "default_false", }, ResponseBase { gray: Vec, From 3b81df91298a128ce7560abe7eeba4b7d966b1ce Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 11 Jul 2024 17:06:42 -0400 Subject: [PATCH 07/93] readme: fixed bytes section --- rpc/types/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rpc/types/README.md b/rpc/types/README.md index 21905fab4..eb8da0134 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -64,6 +64,20 @@ These mixed types are: TODO: we need to figure out a type that (de)serializes correctly, `String` errors with `serde_json` +# Fixed byte containers +TODO + + + # Feature flags List of feature flags for `cuprate-rpc-types`. From f087481e3a9967cb5e96c03258fbe2e3e70b16ea Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 16:08:22 -0400 Subject: [PATCH 08/93] types: custom epee - `BlockCompleteEntry` --- rpc/types/src/misc/block_complete_entry.rs | 89 +++++++++++++++++++--- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/rpc/types/src/misc/block_complete_entry.rs b/rpc/types/src/misc/block_complete_entry.rs index ca791b0a0..ffb0cec4f 100644 --- a/rpc/types/src/misc/block_complete_entry.rs +++ b/rpc/types/src/misc/block_complete_entry.rs @@ -5,7 +5,11 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "epee")] -use cuprate_epee_encoding::epee_object; +use cuprate_epee_encoding::{ + epee_object, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, +}; use crate::misc::TxBlobEntry; @@ -25,13 +29,80 @@ pub struct BlockCompleteEntry { pub txs: Vec, } -// TODO: custom epee -// +//---------------------------------------------------------------------------------------------------- Serde #[cfg(feature = "epee")] -epee_object! { - BlockCompleteEntry, - pruned: bool, - block: String, - block_weight: u64, - txs: Vec, +/// [`EpeeObjectBuilder`] for [`BlockCompleteEntry`]. +/// +/// Not for public usage. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __BlockCompleteEntryEpeeBuilder { + pub pruned: Option, + pub block: Option, + pub block_weight: Option, + pub txs: Option>, +} + +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __BlockCompleteEntryEpeeBuilder { + fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { + match name { + "pruned" => self.pruned = Some(read_epee_value(r)?), + "block" => self.block = Some(read_epee_value(r)?), + "block_weight" => self.block_weight = Some(read_epee_value(r)?), + "txs" => self.txs = Some(read_epee_value(r)?), + _ => return Ok(false), + } + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + Ok(BlockCompleteEntry { + pruned: self.pruned.unwrap_or(false), + block: self.block.ok_or(ELSE)?, + block_weight: self.block_weight.unwrap_or(0), + txs: self.txs.ok_or(ELSE)?, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for BlockCompleteEntry { + type Builder = __BlockCompleteEntryEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + 4 + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + write_field(self.pruned, "pruned", w)?; + write_field(self.block, "block", w)?; + write_field(self.block_weight, "block_weight", w)?; + + // The following section is why custom epee (de)serialization exists. + // + // + + if self.pruned { + write_field(self.txs, "txs", w)?; + return Ok(()); + } + + let txs: Vec = if self.txs.should_write() { + self.txs.into_iter().map(|i| i.blob).collect() + } else { + Vec::new() + }; + + write_field(txs, "txs", w)?; + + // TODO: what is the purpose of these line? + // We take `self` so it gets destructed after this function, + // is there a need to do this swap? + // + // + + Ok(()) + } } From db4b30abe16f2bbdc33317006c29048c0df0b2b6 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 16:25:08 -0400 Subject: [PATCH 09/93] types: custom epee - `KeyImageSpentStatus` --- rpc/types/src/misc/key_image_spent_status.rs | 63 ++++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/rpc/types/src/misc/key_image_spent_status.rs b/rpc/types/src/misc/key_image_spent_status.rs index d075e64ed..774402509 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!() + /// 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 = ::MARKER; - fn epee_default_value() -> Option { - todo!() + 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) -> cuprate_epee_encoding::Result<()> { - todo!() + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) } } From b54b9bce81f84e29b26cebacc4e3acf9c3ea7f05 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 16:27:09 -0400 Subject: [PATCH 10/93] types: custom epee - `PoolInfoExtent` --- rpc/types/src/misc/pool_info_extent.rs | 62 ++++++++++++++++++++------ 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/rpc/types/src/misc/pool_info_extent.rs b/rpc/types/src/misc/pool_info_extent.rs index 09b6c96f6..4001979df 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, }; @@ -26,24 +27,59 @@ pub enum PoolInfoExtent { 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!() + /// 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 epee_default_value() -> Option { - todo!() + 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) -> cuprate_epee_encoding::Result<()> { - todo!() + fn write(self, w: &mut B) -> error::Result<()> { + let u = self.to_u8(); + u8::write(u, w)?; + Ok(()) } } From 46388712bae71cd7a04cc4a6fc161c822216734a Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 16:34:54 -0400 Subject: [PATCH 11/93] types: add `Status::Other(String)` variant --- rpc/types/src/base.rs | 2 +- rpc/types/src/bin.rs | 4 ++-- rpc/types/src/constants.rs | 3 --- rpc/types/src/json.rs | 9 ++------ rpc/types/src/lib.rs | 4 ++-- rpc/types/src/misc/misc.rs | 2 +- rpc/types/src/misc/status.rs | 41 ++++++++++++++++-------------------- rpc/types/src/other.rs | 1 - 8 files changed, 26 insertions(+), 40 deletions(-) diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index f13ac4039..4990cdd6a 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 3dcfb9674..207cb293f 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -27,13 +27,13 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 162..=262, GetBlocks, Request { - requested_info: u8 = default_zero(), "default_zero", + 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", + pool_info_since: u64 = default_zero::(), "default_zero", }, // TODO: this has custom epee (de)serialization. // diff --git a/rpc/types/src/constants.rs b/rpc/types/src/constants.rs index e58028362..8c6120ba6 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 2e7aa82ac..2bfb077fb 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -131,7 +131,6 @@ define_request_and_response! { // type alias to `()` instead of a `struct`. Request {}, - #[derive(Copy)] ResponseBase { count: u64, } @@ -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 { @@ -607,7 +603,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 45cca69c7..ae23abf24 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -121,6 +121,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/misc/misc.rs b/rpc/types/src/misc/misc.rs index 31719a345..1712bb530 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, diff --git a/rpc/types/src/misc/status.rs b/rpc/types/src/misc/status.rs index f2dff1a8c..79725cff8 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/other.rs b/rpc/types/src/other.rs index 5ad2caacd..a4d3e94bd 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -219,7 +219,6 @@ define_request_and_response! { password: String, proxy: String, }, - #[derive(Copy)] Response { status: Status, } From b924766206239750996173158f591f2ce3cddfb5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 16:56:24 -0400 Subject: [PATCH 12/93] types: custom epee - `TxEntry`, add `read_epee_field` macro --- rpc/types/src/misc/block_complete_entry.rs | 23 +++- rpc/types/src/misc/tx_entry.rs | 135 ++++++++++++++++++--- 2 files changed, 132 insertions(+), 26 deletions(-) diff --git a/rpc/types/src/misc/block_complete_entry.rs b/rpc/types/src/misc/block_complete_entry.rs index ffb0cec4f..47c66f784 100644 --- a/rpc/types/src/misc/block_complete_entry.rs +++ b/rpc/types/src/misc/block_complete_entry.rs @@ -46,13 +46,24 @@ pub struct __BlockCompleteEntryEpeeBuilder { #[cfg(feature = "epee")] impl EpeeObjectBuilder for __BlockCompleteEntryEpeeBuilder { fn add_field(&mut self, name: &str, r: &mut B) -> error::Result { - match name { - "pruned" => self.pruned = Some(read_epee_value(r)?), - "block" => self.block = Some(read_epee_value(r)?), - "block_weight" => self.block_weight = Some(read_epee_value(r)?), - "txs" => self.txs = Some(read_epee_value(r)?), - _ => return Ok(false), + macro_rules! read_epee_field { + ($($field:ident),*) => { + match name { + $( + stringify!($field) => { self.$field = Some(read_epee_value(r)?); }, + )* + _ => return Ok(false), + } + }; } + + read_epee_field! { + pruned, + block, + block_weight, + txs + } + Ok(true) } diff --git a/rpc/types/src/misc/tx_entry.rs b/rpc/types/src/misc/tx_entry.rs index 70fbdff3b..1cf819eaf 100644 --- a/rpc/types/src/misc/tx_entry.rs +++ b/rpc/types/src/misc/tx_entry.rs @@ -6,9 +6,9 @@ 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, }; //---------------------------------------------------------------------------------------------------- TxEntry @@ -37,23 +37,118 @@ pub struct TxEntry { pub tx_hash: String, } -// TODO: custom epee -// +//---------------------------------------------------------------------------------------------------- Serde #[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, +/// [`EpeeObjectBuilder`] for [`TxEntry`]. +/// +/// Not for public usage. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct __TxEntryEpeeBuilder { + pub as_hex: Option, + pub as_json: Option, + pub block_height: Option, + pub block_timestamp: Option, + pub confirmations: Option, + pub double_spend_seen: Option, + pub in_pool: Option, + pub output_indices: Option>, + pub prunable_as_hex: Option, + pub prunable_hash: Option, + pub pruned_as_hex: Option, + pub received_timestamp: Option, + pub relayed: Option, + pub tx_hash: Option, +} + +#[cfg(feature = "epee")] +impl EpeeObjectBuilder for __TxEntryEpeeBuilder { + 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! { + as_hex, + as_json, + block_height, + block_timestamp, + confirmations, + double_spend_seen, + in_pool, + output_indices, + prunable_as_hex, + prunable_hash, + pruned_as_hex, + received_timestamp, + relayed, + tx_hash + } + + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + Ok(TxEntry { + as_hex: self.as_hex.ok_or(ELSE)?, + as_json: self.as_json.ok_or(ELSE)?, + block_height: self.block_height.ok_or(ELSE)?, + block_timestamp: self.block_timestamp.ok_or(ELSE)?, + confirmations: self.confirmations.ok_or(ELSE)?, + double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, + in_pool: self.in_pool.ok_or(ELSE)?, + output_indices: self.output_indices.ok_or(ELSE)?, + prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, + prunable_hash: self.prunable_hash.ok_or(ELSE)?, + pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, + received_timestamp: self.received_timestamp.ok_or(ELSE)?, + relayed: self.relayed.ok_or(ELSE)?, + tx_hash: self.tx_hash.ok_or(ELSE)?, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for TxEntry { + type Builder = __TxEntryEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + // TODO: this is either 12 or 10 depending on `self.in_pool`. + 14 + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + write_field(self.as_hex, "as_hex", w)?; + write_field(self.as_json, "as_json", w)?; + write_field(self.double_spend_seen, "double_spend_seen", w)?; + write_field(self.in_pool, "in_pool", w)?; + write_field(self.prunable_as_hex, "prunable_as_hex", w)?; + write_field(self.prunable_hash, "prunable_hash", w)?; + write_field(self.pruned_as_hex, "pruned_as_hex", w)?; + write_field(self.tx_hash, "tx_hash", w)?; + + // The following section is why custom epee (de)serialization exists. + // + // + + if self.in_pool { + write_field(self.block_height, "block_height", w)?; + write_field(self.confirmations, "confirmations", w)?; + write_field(self.block_timestamp, "block_timestamp", w)?; + write_field(self.output_indices, "output_indices", w)?; + } else { + write_field(self.relayed, "relayed", w)?; + write_field(self.received_timestamp, "received_timestamp", w)?; + } + + Ok(()) + } } From 2adcbfa424c26dc2068b48865854815d09f4e276 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 12 Jul 2024 20:54:20 -0400 Subject: [PATCH 13/93] bin: custom epee - `GetBlocks` --- rpc/types/src/bin.rs | 200 +++++++++++++++++++++---- rpc/types/src/macros.rs | 44 +++--- rpc/types/src/misc/pool_info_extent.rs | 3 +- 3 files changed, 189 insertions(+), 58 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 207cb293f..0b9d2fb94 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -5,51 +5,30 @@ //---------------------------------------------------------------------------------------------------- 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 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, + 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]>, - } -} - define_request_and_response! { get_blocks_by_heightbin, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -134,6 +113,163 @@ define_request_and_response! { } } +//---------------------------------------------------------------------------------------------------- GetBlocks +define_request! { + #[doc = define_request_and_response_doc!( + "response" => GetBlocksResponse, + get_transaction_pool_hashesbin, + cc73fe71162d564ffda8e549b79a350bca53c454, + core_rpc_server_commands_defs, h, 1593, 1613, + )] + 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_transaction_pool_hashesbin, + cc73fe71162d564ffda8e549b79a350bca53c454, + core_rpc_server_commands_defs, h, 1593, 1613, +)] +#[allow(dead_code, missing_docs)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GetBlocksResponse { + 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 pool_info_extent: PoolInfoExtent, + pub added_pool_txs: Vec, + pub remaining_added_pool_txids: ByteArrayVec<32>, + pub removed_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!"); + Ok(GetBlocksResponse { + status: self.status.ok_or(ELSE)?, + untrusted: self.untrusted.ok_or(ELSE)?, + blocks: self.blocks.ok_or(ELSE)?, + start_height: self.start_height.ok_or(ELSE)?, + current_height: self.current_height.ok_or(ELSE)?, + output_indices: self.output_indices.ok_or(ELSE)?, + daemon_time: self.daemon_time.unwrap_or(0), + pool_info_extent: self.pool_info_extent.unwrap_or_default(), + 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)?, + }) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for GetBlocksResponse { + type Builder = __GetBlocksResponseEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + // TODO: fields written depends on branch + 11 + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + macro_rules! write_field { + ($($field:ident),*) => { + $( + write_field(self.$field, stringify!($field), w)?; + )* + }; + } + + write_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent + } + + // The following section is why custom epee (de)serialization exists. + // + // + + if self.pool_info_extent != PoolInfoExtent::None { + write_field!(added_pool_txs, remaining_added_pool_txids); + } + + if self.pool_info_extent != PoolInfoExtent::Incremental { + write_field!(removed_pool_txids); + } + + Ok(()) + } +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index e13013872..fa0d51882 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/pool_info_extent.rs b/rpc/types/src/misc/pool_info_extent.rs index 4001979df..6372cd654 100644 --- a/rpc/types/src/misc/pool_info_extent.rs +++ b/rpc/types/src/misc/pool_info_extent.rs @@ -18,10 +18,11 @@ 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, From 9ad90237129ec225d1c6e85d0cac63053bcde6af Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 17:03:05 -0400 Subject: [PATCH 14/93] types: add `serde.rs` --- rpc/types/src/lib.rs | 3 +++ rpc/types/src/serde.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 rpc/types/src/serde.rs diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index ae23abf24..d0d1e00da 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; diff --git a/rpc/types/src/serde.rs b/rpc/types/src/serde.rs new file mode 100644 index 000000000..1ae9ec93c --- /dev/null +++ b/rpc/types/src/serde.rs @@ -0,0 +1,30 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Lints +#![allow(clippy::trivially_copy_pass_by_ref)] // serde fn signature + +//---------------------------------------------------------------------------------------------------- Import +use serde::Serializer; + +//---------------------------------------------------------------------------------------------------- TODO +#[inline] +pub(crate) fn serde_true(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(true) +} + +#[inline] +pub(crate) fn serde_false(_: &bool, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(false) +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + use super::*; +} From dc07d9395f84d4cc21bb40ccd76df70d10acc8d4 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 17:03:25 -0400 Subject: [PATCH 15/93] misc: make `TxEntry` an `enum`, impl serde --- rpc/types/src/misc/tx_entry.rs | 249 +++++++++++++++++++++++++-------- 1 file changed, 193 insertions(+), 56 deletions(-) diff --git a/rpc/types/src/misc/tx_entry.rs b/rpc/types/src/misc/tx_entry.rs index 1cf819eaf..f87d45875 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, error, macros::bytes::{Buf, BufMut}, - read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, }; //---------------------------------------------------------------------------------------------------- TxEntry @@ -18,23 +20,96 @@ use cuprate_epee_encoding::{ 389..=428 )] /// Used in [`crate::other::GetTransactionsResponse`]. -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// +/// ```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, + }, +} + +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, + } + } } //---------------------------------------------------------------------------------------------------- Serde @@ -97,22 +172,40 @@ impl EpeeObjectBuilder for __TxEntryEpeeBuilder { fn finish(self) -> error::Result { const ELSE: error::Error = error::Error::Format("Required field was not found!"); - Ok(TxEntry { - as_hex: self.as_hex.ok_or(ELSE)?, - as_json: self.as_json.ok_or(ELSE)?, - block_height: self.block_height.ok_or(ELSE)?, - block_timestamp: self.block_timestamp.ok_or(ELSE)?, - confirmations: self.confirmations.ok_or(ELSE)?, - double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, - in_pool: self.in_pool.ok_or(ELSE)?, - output_indices: self.output_indices.ok_or(ELSE)?, - prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, - prunable_hash: self.prunable_hash.ok_or(ELSE)?, - pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, - received_timestamp: self.received_timestamp.ok_or(ELSE)?, - relayed: self.relayed.ok_or(ELSE)?, - tx_hash: self.tx_hash.ok_or(ELSE)?, - }) + + let in_pool = self.in_pool.ok_or(ELSE)?; + + let tx_entry = if in_pool { + TxEntry::InPool { + as_hex: self.as_hex.ok_or(ELSE)?, + as_json: self.as_json.ok_or(ELSE)?, + block_height: self.block_height.ok_or(ELSE)?, + block_timestamp: self.block_timestamp.ok_or(ELSE)?, + confirmations: self.confirmations.ok_or(ELSE)?, + double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, + in_pool: self.in_pool.ok_or(ELSE)?, + output_indices: self.output_indices.ok_or(ELSE)?, + prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, + prunable_hash: self.prunable_hash.ok_or(ELSE)?, + pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, + tx_hash: self.tx_hash.ok_or(ELSE)?, + } + } else { + TxEntry::NotInPool { + as_hex: self.as_hex.ok_or(ELSE)?, + as_json: self.as_json.ok_or(ELSE)?, + double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, + in_pool: self.in_pool.ok_or(ELSE)?, + prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, + prunable_hash: self.prunable_hash.ok_or(ELSE)?, + pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, + received_timestamp: self.received_timestamp.ok_or(ELSE)?, + relayed: self.relayed.ok_or(ELSE)?, + tx_hash: self.tx_hash.ok_or(ELSE)?, + } + }; + + Ok(tx_entry) } } @@ -121,32 +214,76 @@ impl EpeeObject for TxEntry { type Builder = __TxEntryEpeeBuilder; fn number_of_fields(&self) -> u64 { - // TODO: this is either 12 or 10 depending on `self.in_pool`. - 14 + match self { + Self::InPool { .. } => 12, + Self::NotInPool { .. } => 10, + } } fn write_fields(self, w: &mut B) -> error::Result<()> { - write_field(self.as_hex, "as_hex", w)?; - write_field(self.as_json, "as_json", w)?; - write_field(self.double_spend_seen, "double_spend_seen", w)?; - write_field(self.in_pool, "in_pool", w)?; - write_field(self.prunable_as_hex, "prunable_as_hex", w)?; - write_field(self.prunable_hash, "prunable_hash", w)?; - write_field(self.pruned_as_hex, "pruned_as_hex", w)?; - write_field(self.tx_hash, "tx_hash", w)?; - - // The following section is why custom epee (de)serialization exists. - // - // - - if self.in_pool { - write_field(self.block_height, "block_height", w)?; - write_field(self.confirmations, "confirmations", w)?; - write_field(self.block_timestamp, "block_timestamp", w)?; - write_field(self.output_indices, "output_indices", w)?; - } else { - write_field(self.relayed, "relayed", w)?; - write_field(self.received_timestamp, "received_timestamp", w)?; + macro_rules! write_fields { + ($($field:ident),*) => { + $( + write_field($field, stringify!($field), w)?; + )* + }; + } + + match self { + Self::InPool { + as_hex, + as_json, + block_height, + block_timestamp, + confirmations, + double_spend_seen, + output_indices, + prunable_as_hex, + prunable_hash, + pruned_as_hex, + tx_hash, + in_pool, + } => { + write_fields! { + as_hex, + as_json, + block_height, + block_timestamp, + confirmations, + double_spend_seen, + output_indices, + prunable_as_hex, + prunable_hash, + pruned_as_hex, + tx_hash, + in_pool + } + } + Self::NotInPool { + as_hex, + as_json, + double_spend_seen, + prunable_as_hex, + prunable_hash, + pruned_as_hex, + received_timestamp, + relayed, + tx_hash, + in_pool, + } => { + write_fields! { + as_hex, + as_json, + double_spend_seen, + prunable_as_hex, + prunable_hash, + pruned_as_hex, + received_timestamp, + relayed, + tx_hash, + in_pool + } + } } Ok(()) From e43e95f86328d700abdbd0781c13069112eedf25 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 17:08:05 -0400 Subject: [PATCH 16/93] misc: `unimplemented!()` for `TxEntry`'s epee --- rpc/types/src/misc/tx_entry.rs | 175 +++------------------------------ 1 file changed, 15 insertions(+), 160 deletions(-) diff --git a/rpc/types/src/misc/tx_entry.rs b/rpc/types/src/misc/tx_entry.rs index f87d45875..e643076de 100644 --- a/rpc/types/src/misc/tx_entry.rs +++ b/rpc/types/src/misc/tx_entry.rs @@ -21,6 +21,14 @@ use cuprate_epee_encoding::{ )] /// Used in [`crate::other::GetTransactionsResponse`]. /// +/// # 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}; @@ -112,180 +120,27 @@ impl Default for TxEntry { } } -//---------------------------------------------------------------------------------------------------- Serde -#[cfg(feature = "epee")] -/// [`EpeeObjectBuilder`] for [`TxEntry`]. -/// -/// Not for public usage. -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct __TxEntryEpeeBuilder { - pub as_hex: Option, - pub as_json: Option, - pub block_height: Option, - pub block_timestamp: Option, - pub confirmations: Option, - pub double_spend_seen: Option, - pub in_pool: Option, - pub output_indices: Option>, - pub prunable_as_hex: Option, - pub prunable_hash: Option, - pub pruned_as_hex: Option, - pub received_timestamp: Option, - pub relayed: Option, - pub tx_hash: Option, -} - +//---------------------------------------------------------------------------------------------------- Epee #[cfg(feature = "epee")] -impl EpeeObjectBuilder for __TxEntryEpeeBuilder { +impl EpeeObjectBuilder for () { 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! { - as_hex, - as_json, - block_height, - block_timestamp, - confirmations, - double_spend_seen, - in_pool, - output_indices, - prunable_as_hex, - prunable_hash, - pruned_as_hex, - received_timestamp, - relayed, - tx_hash - } - - Ok(true) + unreachable!() } fn finish(self) -> error::Result { - const ELSE: error::Error = error::Error::Format("Required field was not found!"); - - let in_pool = self.in_pool.ok_or(ELSE)?; - - let tx_entry = if in_pool { - TxEntry::InPool { - as_hex: self.as_hex.ok_or(ELSE)?, - as_json: self.as_json.ok_or(ELSE)?, - block_height: self.block_height.ok_or(ELSE)?, - block_timestamp: self.block_timestamp.ok_or(ELSE)?, - confirmations: self.confirmations.ok_or(ELSE)?, - double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, - in_pool: self.in_pool.ok_or(ELSE)?, - output_indices: self.output_indices.ok_or(ELSE)?, - prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, - prunable_hash: self.prunable_hash.ok_or(ELSE)?, - pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, - tx_hash: self.tx_hash.ok_or(ELSE)?, - } - } else { - TxEntry::NotInPool { - as_hex: self.as_hex.ok_or(ELSE)?, - as_json: self.as_json.ok_or(ELSE)?, - double_spend_seen: self.double_spend_seen.ok_or(ELSE)?, - in_pool: self.in_pool.ok_or(ELSE)?, - prunable_as_hex: self.prunable_as_hex.ok_or(ELSE)?, - prunable_hash: self.prunable_hash.ok_or(ELSE)?, - pruned_as_hex: self.pruned_as_hex.ok_or(ELSE)?, - received_timestamp: self.received_timestamp.ok_or(ELSE)?, - relayed: self.relayed.ok_or(ELSE)?, - tx_hash: self.tx_hash.ok_or(ELSE)?, - } - }; - - Ok(tx_entry) + unreachable!() } } #[cfg(feature = "epee")] impl EpeeObject for TxEntry { - type Builder = __TxEntryEpeeBuilder; + type Builder = (); fn number_of_fields(&self) -> u64 { - match self { - Self::InPool { .. } => 12, - Self::NotInPool { .. } => 10, - } + unreachable!() } fn write_fields(self, w: &mut B) -> error::Result<()> { - macro_rules! write_fields { - ($($field:ident),*) => { - $( - write_field($field, stringify!($field), w)?; - )* - }; - } - - match self { - Self::InPool { - as_hex, - as_json, - block_height, - block_timestamp, - confirmations, - double_spend_seen, - output_indices, - prunable_as_hex, - prunable_hash, - pruned_as_hex, - tx_hash, - in_pool, - } => { - write_fields! { - as_hex, - as_json, - block_height, - block_timestamp, - confirmations, - double_spend_seen, - output_indices, - prunable_as_hex, - prunable_hash, - pruned_as_hex, - tx_hash, - in_pool - } - } - Self::NotInPool { - as_hex, - as_json, - double_spend_seen, - prunable_as_hex, - prunable_hash, - pruned_as_hex, - received_timestamp, - relayed, - tx_hash, - in_pool, - } => { - write_fields! { - as_hex, - as_json, - double_spend_seen, - prunable_as_hex, - prunable_hash, - pruned_as_hex, - received_timestamp, - relayed, - tx_hash, - in_pool - } - } - } - - Ok(()) + unreachable!() } } From 705f72b266074fd3db9725d66568a0efc045126a Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 17:23:23 -0400 Subject: [PATCH 17/93] types: add `BlockCompleteEntry` --- Cargo.lock | 4 + types/Cargo.toml | 9 +- types/src/block_complete_entry.rs | 147 ++++++++++++++++++++++++++++++ types/src/lib.rs | 3 + 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 types/src/block_complete_entry.rs diff --git a/Cargo.lock b/Cargo.lock index 01d5329de..f36c1476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,8 +800,12 @@ version = "0.0.0" name = "cuprate-types" version = "0.0.0" dependencies = [ + "bytes", + "cuprate-epee-encoding", + "cuprate-fixed-bytes", "curve25519-dalek", "monero-serai", + "serde", ] [[package]] diff --git a/types/Cargo.toml b/types/Cargo.toml index 7f6b8f8d6..469aa5286 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -9,11 +9,18 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types" keywords = ["cuprate", "types"] [features] -default = ["blockchain"] +default = ["blockchain", "epee", "serde"] blockchain = [] +epee = ["dep:cuprate-epee-encoding"] +serde = ["dep:serde"] [dependencies] +cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true } +cuprate-fixed-bytes = { path = "../net/fixed-bytes" } + +bytes = { workspace = true } curve25519-dalek = { workspace = true } monero-serai = { workspace = true } +serde = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] \ No newline at end of file diff --git a/types/src/block_complete_entry.rs b/types/src/block_complete_entry.rs new file mode 100644 index 000000000..91ba75308 --- /dev/null +++ b/types/src/block_complete_entry.rs @@ -0,0 +1,147 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "epee")] +use bytes::Bytes; + +use cuprate_fixed_bytes::ByteArray; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, + macros::bytes::{Buf, BufMut}, + EpeeValue, InnerMarker, +}; + +//---------------------------------------------------------------------------------------------------- BlockCompleteEntry +/// A block that can contain transactions. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlockCompleteEntry { + /// `true` if transaction data is pruned. + pub pruned: bool, + /// The block. + pub block: Bytes, + /// The block weight/size. + pub block_weight: u64, + /// The block's transactions. + pub txs: TransactionBlobs, +} + +#[cfg(feature = "epee")] +epee_object!( + BlockCompleteEntry, + pruned: bool = false, + block: Bytes, + block_weight: u64 = 0_u64, + txs: TransactionBlobs = TransactionBlobs::None => + TransactionBlobs::tx_blob_read, + TransactionBlobs::tx_blob_write, + TransactionBlobs::should_write_tx_blobs, +); + +//---------------------------------------------------------------------------------------------------- TransactionBlobs +/// TODO +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TransactionBlobs { + /// TODO + Pruned(Vec), + /// TODO + Normal(Vec), + /// TODO + None, +} + +impl TransactionBlobs { + /// TODO + pub fn take_pruned(self) -> Option> { + match self { + Self::Normal(_) => None, + Self::Pruned(txs) => Some(txs), + Self::None => Some(vec![]), + } + } + + /// TODO + pub fn take_normal(self) -> Option> { + match self { + Self::Normal(txs) => Some(txs), + Self::Pruned(_) => None, + Self::None => Some(vec![]), + } + } + + /// TODO + pub fn len(&self) -> usize { + match self { + Self::Normal(txs) => txs.len(), + Self::Pruned(txs) => txs.len(), + Self::None => 0, + } + } + + /// TODO + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// TODO + #[cfg(feature = "epee")] + fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { + let marker = cuprate_epee_encoding::read_marker(b)?; + match marker.inner_marker { + InnerMarker::Object => Ok(Self::Pruned(Vec::read(b, &marker)?)), + InnerMarker::String => Ok(Self::Normal(Vec::read(b, &marker)?)), + _ => Err(cuprate_epee_encoding::Error::Value( + "Invalid marker for tx blobs".to_string(), + )), + } + } + + /// TODO + #[cfg(feature = "epee")] + fn tx_blob_write( + self, + field_name: &str, + w: &mut B, + ) -> cuprate_epee_encoding::Result<()> { + if self.should_write_tx_blobs() { + match self { + Self::Normal(bytes) => { + cuprate_epee_encoding::write_field(bytes, field_name, w)?; + } + Self::Pruned(obj) => { + cuprate_epee_encoding::write_field(obj, field_name, w)?; + } + Self::None => (), + } + } + Ok(()) + } + + /// TODO + #[cfg(feature = "epee")] + fn should_write_tx_blobs(&self) -> bool { + !self.is_empty() + } +} + +//---------------------------------------------------------------------------------------------------- PrunedTxBlobEntry +/// A pruned transaction with the hash of the missing prunable data +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PrunedTxBlobEntry { + /// The transaction. + pub tx: Bytes, + /// The prunable transaction hash. + pub prunable_hash: ByteArray<32>, +} + +#[cfg(feature = "epee")] +epee_object!( + PrunedTxBlobEntry, + tx: Bytes, + prunable_hash: ByteArray<32>, +); + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(test)] +mod tests {} diff --git a/types/src/lib.rs b/types/src/lib.rs index 2d161f7ec..16d12a6bc 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -86,7 +86,10 @@ // // Documentation for each module is located in the respective file. +mod block_complete_entry; mod types; + +pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; pub use types::{ ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation, VerifiedTransactionInformation, }; From ac5813e8bfa3190676d41a5d016dba5644285f84 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 19:41:50 -0400 Subject: [PATCH 18/93] rpc: replace `BlockCompleteEntry` with `cuprate-types` --- Cargo.lock | 1 + rpc/types/Cargo.toml | 1 + rpc/types/src/bin.rs | 8 +- rpc/types/src/misc/block_complete_entry.rs | 119 --------------------- rpc/types/src/misc/misc.rs | 13 --- rpc/types/src/misc/mod.rs | 4 +- types/Cargo.toml | 8 +- types/src/block_complete_entry.rs | 13 ++- 8 files changed, 22 insertions(+), 145 deletions(-) delete mode 100644 rpc/types/src/misc/block_complete_entry.rs diff --git a/Cargo.lock b/Cargo.lock index f36c1476c..887712a81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -763,6 +763,7 @@ version = "0.0.0" dependencies = [ "cuprate-epee-encoding", "cuprate-fixed-bytes", + "cuprate-types", "monero-serai", "paste", "serde", diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 1176526ad..768186839 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -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 } diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 0b9d2fb94..0eb683e09 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -16,15 +16,17 @@ use cuprate_epee_encoding::{ 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, define_request_and_response, define_request_and_response_doc}, misc::{ - AuxPow, BlockCompleteEntry, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, - GetBan, GetOutputsOut, HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, - Peer, PoolInfoExtent, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, + AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, + HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolInfoExtent, + PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, }; 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 47c66f784..000000000 --- a/rpc/types/src/misc/block_complete_entry.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Use -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "epee")] -use cuprate_epee_encoding::{ - epee_object, error, - macros::bytes::{Buf, BufMut}, - read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, -}; - -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, -} - -//---------------------------------------------------------------------------------------------------- Serde -#[cfg(feature = "epee")] -/// [`EpeeObjectBuilder`] for [`BlockCompleteEntry`]. -/// -/// Not for public usage. -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct __BlockCompleteEntryEpeeBuilder { - pub pruned: Option, - pub block: Option, - pub block_weight: Option, - pub txs: Option>, -} - -#[cfg(feature = "epee")] -impl EpeeObjectBuilder for __BlockCompleteEntryEpeeBuilder { - 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! { - pruned, - block, - block_weight, - txs - } - - Ok(true) - } - - fn finish(self) -> error::Result { - const ELSE: error::Error = error::Error::Format("Required field was not found!"); - Ok(BlockCompleteEntry { - pruned: self.pruned.unwrap_or(false), - block: self.block.ok_or(ELSE)?, - block_weight: self.block_weight.unwrap_or(0), - txs: self.txs.ok_or(ELSE)?, - }) - } -} - -#[cfg(feature = "epee")] -impl EpeeObject for BlockCompleteEntry { - type Builder = __BlockCompleteEntryEpeeBuilder; - - fn number_of_fields(&self) -> u64 { - 4 - } - - fn write_fields(self, w: &mut B) -> error::Result<()> { - write_field(self.pruned, "pruned", w)?; - write_field(self.block, "block", w)?; - write_field(self.block_weight, "block_weight", w)?; - - // The following section is why custom epee (de)serialization exists. - // - // - - if self.pruned { - write_field(self.txs, "txs", w)?; - return Ok(()); - } - - let txs: Vec = if self.txs.should_write() { - self.txs.into_iter().map(|i| i.blob).collect() - } else { - Vec::new() - }; - - write_field(txs, "txs", w)?; - - // TODO: what is the purpose of these line? - // We take `self` so it gets destructed after this function, - // is there a need to do this swap? - // - // - - Ok(()) - } -} diff --git a/rpc/types/src/misc/misc.rs b/rpc/types/src/misc/misc.rs index 1712bb530..4643ecc59 100644 --- a/rpc/types/src/misc/misc.rs +++ b/rpc/types/src/misc/misc.rs @@ -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 31dba3538..2a5564634 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -13,7 +13,6 @@ //---------------------------------------------------------------------------------------------------- Mod mod binary_string; -mod block_complete_entry; mod key_image_spent_status; mod misc; mod pool_info_extent; @@ -21,13 +20,12 @@ mod status; mod tx_entry; pub use binary_string::BinaryString; -pub use block_complete_entry::BlockCompleteEntry; 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/types/Cargo.toml b/types/Cargo.toml index 469aa5286..8f16eb486 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -9,14 +9,14 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types" keywords = ["cuprate", "types"] [features] -default = ["blockchain", "epee", "serde"] +default = ["blockchain", "epee", "serde"] blockchain = [] -epee = ["dep:cuprate-epee-encoding"] -serde = ["dep:serde"] +epee = ["dep:cuprate-epee-encoding"] +serde = ["dep:serde"] [dependencies] cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true } -cuprate-fixed-bytes = { path = "../net/fixed-bytes" } +cuprate-fixed-bytes = { path = "../net/fixed-bytes" } bytes = { workspace = true } curve25519-dalek = { workspace = true } diff --git a/types/src/block_complete_entry.rs b/types/src/block_complete_entry.rs index 91ba75308..ee6d9dd06 100644 --- a/types/src/block_complete_entry.rs +++ b/types/src/block_complete_entry.rs @@ -4,6 +4,9 @@ #[cfg(feature = "epee")] use bytes::Bytes; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use cuprate_fixed_bytes::ByteArray; #[cfg(feature = "epee")] @@ -15,7 +18,8 @@ use cuprate_epee_encoding::{ //---------------------------------------------------------------------------------------------------- BlockCompleteEntry /// A block that can contain transactions. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BlockCompleteEntry { /// `true` if transaction data is pruned. pub pruned: bool, @@ -41,12 +45,14 @@ epee_object!( //---------------------------------------------------------------------------------------------------- TransactionBlobs /// TODO -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransactionBlobs { /// TODO Pruned(Vec), /// TODO Normal(Vec), + #[default] /// TODO None, } @@ -127,7 +133,8 @@ impl TransactionBlobs { //---------------------------------------------------------------------------------------------------- PrunedTxBlobEntry /// A pruned transaction with the hash of the missing prunable data -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PrunedTxBlobEntry { /// The transaction. pub tx: Bytes, From 1b2ca7dc09e006a9a6befa64c1cc855edbc245a7 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 19:47:54 -0400 Subject: [PATCH 19/93] types: document `BlockCompleteEntry` --- types/README.md | 25 ++++++++----------------- types/src/block_complete_entry.rs | 24 ++++++++++++------------ types/src/lib.rs | 9 +-------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/types/README.md b/types/README.md index 6a2015afb..168b00342 100644 --- a/types/README.md +++ b/types/README.md @@ -1,20 +1,11 @@ # `cuprate-types` -Various data types shared by Cuprate. +Shared data types within Cuprate. -- [1. File Structure](#1-file-structure) - - [1.1 `src/`](#11-src) +This crate is a kitchen-sink for data types that are shared across Cuprate. ---- - -## 1. File Structure -A quick reference of the structure of the folders & files in `cuprate-types`. - -Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`. - -### 1.1 `src/` -The top-level `src/` files. - -| File | Purpose | -|---------------------|---------| -| `service.rs` | Types used in database requests; `enum {Request,Response}` -| `types.rs` | Various general types used by Cuprate \ No newline at end of file +# Features flags +| Feature flag | Does what | +|--------------|-----------| +| `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 diff --git a/types/src/block_complete_entry.rs b/types/src/block_complete_entry.rs index ee6d9dd06..ba5fc2b0d 100644 --- a/types/src/block_complete_entry.rs +++ b/types/src/block_complete_entry.rs @@ -1,4 +1,4 @@ -//! TODO +//! Contains [`BlockCompleteEntry`] and the related types. //---------------------------------------------------------------------------------------------------- Import #[cfg(feature = "epee")] @@ -44,21 +44,21 @@ epee_object!( ); //---------------------------------------------------------------------------------------------------- TransactionBlobs -/// TODO +/// Transaction blobs within [`BlockCompleteEntry`]. #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransactionBlobs { - /// TODO + /// Pruned transaction blobs. Pruned(Vec), - /// TODO + /// Normal transaction blobs. Normal(Vec), #[default] - /// TODO + /// No transactions. None, } impl TransactionBlobs { - /// TODO + /// Returns [`Some`] if `self` is [`Self::Pruned`]. pub fn take_pruned(self) -> Option> { match self { Self::Normal(_) => None, @@ -67,7 +67,7 @@ impl TransactionBlobs { } } - /// TODO + /// Returns [`Some`] if `self` is [`Self::Normal`]. pub fn take_normal(self) -> Option> { match self { Self::Normal(txs) => Some(txs), @@ -76,7 +76,7 @@ impl TransactionBlobs { } } - /// TODO + /// Returns the byte length of the blob. pub fn len(&self) -> usize { match self { Self::Normal(txs) => txs.len(), @@ -85,12 +85,12 @@ impl TransactionBlobs { } } - /// TODO + /// Returns `true` if the byte length of the blob is `0`. pub fn is_empty(&self) -> bool { self.len() == 0 } - /// TODO + /// Epee read function. #[cfg(feature = "epee")] fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { let marker = cuprate_epee_encoding::read_marker(b)?; @@ -103,7 +103,7 @@ impl TransactionBlobs { } } - /// TODO + /// Epee write function. #[cfg(feature = "epee")] fn tx_blob_write( self, @@ -124,7 +124,7 @@ impl TransactionBlobs { Ok(()) } - /// TODO + /// Epee should write function. #[cfg(feature = "epee")] fn should_write_tx_blobs(&self) -> bool { !self.is_empty() diff --git a/types/src/lib.rs b/types/src/lib.rs index 16d12a6bc..1cdb9d571 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1,11 +1,4 @@ -//! Cuprate shared data types. -//! -//! This crate is a kitchen-sink for data types that are shared across `Cuprate`. -//! -//! # Features flags -//! The [`blockchain`] module, containing the blockchain database request/response -//! types, must be enabled with the `blockchain` feature (on by default). - +#![doc = include_str!("../README.md")] //---------------------------------------------------------------------------------------------------- Lints // Forbid lints. // Our code, and code generated (e.g macros) cannot overrule these. From 74d35671f8d0229e8aa5b373ace1d128b046fc1f Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 20:09:58 -0400 Subject: [PATCH 20/93] bin: fix `number_of_fields` for `GetBlocksResponse` --- rpc/types/src/bin.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 0eb683e09..3b4e37fc5 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -232,15 +232,47 @@ impl EpeeObject for GetBlocksResponse { type Builder = __GetBlocksResponseEpeeBuilder; fn number_of_fields(&self) -> u64 { - // TODO: fields written depends on branch - 11 + let mut fields = 0; + + macro_rules! add_field { + ($($field:ident),*) => { + $( + if self.$field.should_write() { + fields += 1; + } + )* + }; + } + + add_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent + } + + if self.pool_info_extent != PoolInfoExtent::None { + add_field!(added_pool_txs, remaining_added_pool_txids); + } + + if self.pool_info_extent != PoolInfoExtent::Incremental { + add_field!(removed_pool_txids); + } + + fields } fn write_fields(self, w: &mut B) -> error::Result<()> { macro_rules! write_field { ($($field:ident),*) => { $( - write_field(self.$field, stringify!($field), w)?; + if self.$field.should_write() { + write_field(self.$field, stringify!($field), w)?; + } )* }; } From 7f3297c8a2e56d7f6d5c34da00c21410a52ae700 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 14 Jul 2024 20:52:38 -0400 Subject: [PATCH 21/93] misc: add `Distribution` --- rpc/types/src/bin.rs | 2 +- rpc/types/src/json.rs | 4 +- rpc/types/src/misc/distribution.rs | 246 +++++++++++++++++++++++++++++ rpc/types/src/misc/mod.rs | 2 + rpc/types/src/other.rs | 8 +- 5 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 rpc/types/src/misc/distribution.rs diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 3b4e37fc5..8934940b1 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -30,7 +30,7 @@ use crate::{ }, }; -//---------------------------------------------------------------------------------------------------- TODO +//---------------------------------------------------------------------------------------------------- Definitions define_request_and_response! { get_blocks_by_heightbin, cc73fe71162d564ffda8e549b79a350bca53c454 => diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 2bfb077fb..541acef37 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -14,7 +14,7 @@ use crate::{ }, }; -//---------------------------------------------------------------------------------------------------- Struct definitions +//---------------------------------------------------------------------------------------------------- Definitions // This generates 2 structs: // // - `GetBlockTemplateRequest` @@ -291,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, } diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs new file mode 100644 index 000000000..0a0c6750a --- /dev/null +++ b/rpc/types/src/misc/distribution.rs @@ -0,0 +1,246 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- 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, error, + macros::bytes::{Buf, BufMut}, + read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, +}; + +use super::OutputDistributionData; + +//---------------------------------------------------------------------------------------------------- Free +/// TODO +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 45..=55 +)] +pub fn compress_integer_array(array: Vec) -> Vec { + todo!(); +} + +/// TODO +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 57..=72 +)] +pub fn decompress_integer_array(array: Vec) -> Vec { + todo!(); +} + +//---------------------------------------------------------------------------------------------------- Distribution +#[doc = crate::macros::monero_definition_link!( + cc73fe71162d564ffda8e549b79a350bca53c454, + "rpc/core_rpc_server_commands_defs.h", + 2468..=2508 +)] +/// Used in [`crate::json::GetOutputDistributionResponse`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +pub enum Distribution { + /// TODO + Json { + data: OutputDistributionData, + amount: u64, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] + /// Will always be serialized as `false`. + binary: bool, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] + /// Will always be serialized as `false`. + compress: bool, + }, + /// TODO + Binary { + data: OutputDistributionData, + amount: u64, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] + /// Will always be serialized as `true`. + binary: bool, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] + /// Will always be serialized as `false`. + compress: bool, + }, + /// TODO + CompressedBinary { + data: OutputDistributionData, + amount: u64, + compressed_data: String, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] + /// Will always be serialized as `true`. + binary: bool, + #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] + /// Will always be serialized as `true`. + compress: bool, + }, +} + +impl Default for Distribution { + fn default() -> Self { + Self::Json { + data: OutputDistributionData::default(), + amount: u64::default(), + binary: false, + compress: false, + } + } +} + +//---------------------------------------------------------------------------------------------------- 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 data: 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! { + data, + amount, + compressed_data, + binary, + compress + } + + Ok(true) + } + + fn finish(self) -> error::Result { + const ELSE: error::Error = error::Error::Format("Required field was not found!"); + + let data = self.data.ok_or(ELSE)?; + let amount = self.amount.ok_or(ELSE)?; + let binary = self.binary.ok_or(ELSE)?; + let compress = self.compress.ok_or(ELSE)?; + + let distribution = if binary && compress { + Distribution::CompressedBinary { + compressed_data: self.compressed_data.ok_or(ELSE)?, + data, + amount, + binary, + compress, + } + } else if binary { + Distribution::Binary { + data, + amount, + binary, + compress, + } + } else { + Distribution::Json { + data, + amount, + binary, + compress, + } + }; + + Ok(distribution) + } +} + +#[cfg(feature = "epee")] +impl EpeeObject for Distribution { + type Builder = __DistributionEpeeBuilder; + + fn number_of_fields(&self) -> u64 { + match self { + Self::Json { .. } => 4, + Self::Binary { .. } => 4, + Self::CompressedBinary { .. } => 5, + } + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + match self { + Self::Json { + data, + amount, + binary, + compress, + } + | Self::Binary { + data, + amount, + binary, + compress, + } => { + if amount.should_write() { + write_field(amount, "amount", w)?; + } + if binary.should_write() { + write_field(binary, "binary", w)?; + } + if compress.should_write() { + write_field(compress, "compress", w)?; + } + data.write(w)?; + } + + Self::CompressedBinary { + data, + amount, + compressed_data, + binary, + compress, + } => { + if amount.should_write() { + write_field(amount, "amount", w)?; + } + if binary.should_write() { + write_field(binary, "binary", w)?; + } + if compress.should_write() { + write_field(compress, "compress", w)?; + } + if data.start_height.should_write() { + write_field(data.start_height, "start_height", w)?; + } + if data.base.should_write() { + write_field(data.base, "base", w)?; + } + + // + // TODO: cast `String` -> `Vec` + let compressed_data = compress_integer_array(compressed_data); + if compressed_data.should_write() { + compressed_data.write(w)?; + } + } + } + + Ok(()) + } +} diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs index 2a5564634..6705ac007 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -13,6 +13,7 @@ //---------------------------------------------------------------------------------------------------- Mod mod binary_string; +mod distribution; mod key_image_spent_status; mod misc; mod pool_info_extent; @@ -20,6 +21,7 @@ mod status; mod tx_entry; pub use binary_string::BinaryString; +pub use distribution::Distribution; pub use key_image_spent_status::KeyImageSpentStatus; pub use misc::{ AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, ConnectionInfo, GetBan, diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index a4d3e94bd..c944c87b0 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,7 @@ define_request_and_response! { key_images: Vec, }, AccessResponseBase { - spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. + spent_status: Vec, } } From a5dde87b8f69f9bd6e1a62c0b7df38acead7d9d9 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 16:51:38 -0400 Subject: [PATCH 22/93] distribution: add todo --- rpc/types/src/misc/distribution.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index 0a0c6750a..a9c6de087 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -1,8 +1,11 @@ //! TODO //---------------------------------------------------------------------------------------------------- Use +use std::mem::size_of; + #[cfg(feature = "serde")] use crate::serde::{serde_false, serde_true}; +use cuprate_epee_encoding::read_varint; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -10,30 +13,35 @@ use serde::{Deserialize, Serialize}; use cuprate_epee_encoding::{ epee_object, error, macros::bytes::{Buf, BufMut}, - read_epee_value, write_field, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, + read_epee_value, write_field, write_varint, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, }; use super::OutputDistributionData; //---------------------------------------------------------------------------------------------------- Free -/// TODO +/// Used for [`Distribution::CompressedBinary::compressed_data`]. +/// +/// TODO: for handler code. This should already be provided in the field. #[doc = crate::macros::monero_definition_link!( cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 45..=55 )] -pub fn compress_integer_array(array: Vec) -> Vec { - todo!(); +pub fn compress_integer_array(array: Vec) -> error::Result> { + // TODO: for handler. + todo!() } -/// TODO +/// Used for [`Distribution::CompressedBinary::compressed_data`]. +/// +/// TODO: for handler code. This should already be provided in the field. #[doc = crate::macros::monero_definition_link!( cc73fe71162d564ffda8e549b79a350bca53c454, "rpc/core_rpc_server_commands_defs.h", 57..=72 )] pub fn decompress_integer_array(array: Vec) -> Vec { - todo!(); + todo!() } //---------------------------------------------------------------------------------------------------- Distribution @@ -232,9 +240,6 @@ impl EpeeObject for Distribution { write_field(data.base, "base", w)?; } - // - // TODO: cast `String` -> `Vec` - let compressed_data = compress_integer_array(compressed_data); if compressed_data.should_write() { compressed_data.write(w)?; } From 093802ed32d93a5e732ce16496da95deb56e6978 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 17:02:40 -0400 Subject: [PATCH 23/93] misc fixes --- rpc/types/src/json.rs | 8 +++----- rpc/types/src/misc/distribution.rs | 16 ++++++++-------- rpc/types/src/misc/key_image_spent_status.rs | 2 +- rpc/types/src/other.rs | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 541acef37..f4bca993c 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -9,8 +9,8 @@ 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, }, }; @@ -570,10 +570,8 @@ define_request_and_response! { from_height: u64, to_height: u64, }, - /// TODO: this request has custom serde: - /// AccessResponseBase { - distributions: Vec, + distributions: Vec, } } diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index a9c6de087..9174accc0 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -27,8 +27,8 @@ use super::OutputDistributionData; "rpc/core_rpc_server_commands_defs.h", 45..=55 )] -pub fn compress_integer_array(array: Vec) -> error::Result> { - // TODO: for handler. +#[allow(clippy::needless_pass_by_value)] // TODO: remove after impl +fn compress_integer_array(array: Vec) -> error::Result> { todo!() } @@ -40,7 +40,8 @@ pub fn compress_integer_array(array: Vec) -> error::Result> { "rpc/core_rpc_server_commands_defs.h", 57..=72 )] -pub fn decompress_integer_array(array: Vec) -> Vec { +#[allow(clippy::needless_pass_by_value)] // TODO: remove after impl +fn decompress_integer_array(array: Vec) -> Vec { todo!() } @@ -55,7 +56,7 @@ pub fn decompress_integer_array(array: Vec) -> Vec { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] pub enum Distribution { - /// TODO + /// Distribution data will be (de)serialized as JSON. Json { data: OutputDistributionData, amount: u64, @@ -66,7 +67,7 @@ pub enum Distribution { /// Will always be serialized as `false`. compress: bool, }, - /// TODO + /// Distribution data will be (de)serialized as binary. Binary { data: OutputDistributionData, amount: u64, @@ -77,7 +78,7 @@ pub enum Distribution { /// Will always be serialized as `false`. compress: bool, }, - /// TODO + /// Distribution data will be (de)serialized as compressed binary. CompressedBinary { data: OutputDistributionData, amount: u64, @@ -185,8 +186,7 @@ impl EpeeObject for Distribution { fn number_of_fields(&self) -> u64 { match self { - Self::Json { .. } => 4, - Self::Binary { .. } => 4, + Self::Json { .. } | Self::Binary { .. } => 4, Self::CompressedBinary { .. } => 5, } } diff --git a/rpc/types/src/misc/key_image_spent_status.rs b/rpc/types/src/misc/key_image_spent_status.rs index 774402509..4b2eb535e 100644 --- a/rpc/types/src/misc/key_image_spent_status.rs +++ b/rpc/types/src/misc/key_image_spent_status.rs @@ -70,7 +70,7 @@ impl KeyImageSpentStatus { #[cfg(feature = "epee")] impl EpeeValue for KeyImageSpentStatus { - const MARKER: Marker = ::MARKER; + const MARKER: Marker = u8::MARKER; fn read(r: &mut B, marker: &Marker) -> error::Result { let u = u8::read(r, marker)?; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index c944c87b0..41530cba1 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -68,7 +68,8 @@ define_request_and_response! { key_images: Vec, }, AccessResponseBase { - spent_status: Vec, + /// FIXME: These are [`KeyImageSpentStatus`] in [`u8`] form. + spent_status: Vec, } } From 1621f7bc476254695c3331d27a4f7b6d8f947a9a Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 17:11:54 -0400 Subject: [PATCH 24/93] readme: add `(De)serialization invariants` --- rpc/types/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rpc/types/README.md b/rpc/types/README.md index eb8da0134..f71ba982f 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`. From 395baf5855ca41d06bd9d33d80aa99edf5ce502e Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 17:57:39 -0400 Subject: [PATCH 25/93] distribution: compress variants --- rpc/types/src/misc/distribution.rs | 183 ++++++++++++++++++----------- 1 file changed, 113 insertions(+), 70 deletions(-) diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index 9174accc0..e50ab43ac 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -56,23 +56,16 @@ fn decompress_integer_array(array: Vec) -> Vec { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] pub enum Distribution { - /// Distribution data will be (de)serialized as JSON. - Json { - data: OutputDistributionData, + /// Distribution data will be (de)serialized as either JSON or binary (uncompressed). + Uncompressed { + start_height: u64, + base: u64, + /// TODO: this is a binary JSON string if `binary == true`. + /// + /// Considering both the `binary` field and `/get_output_distribution.bin` + /// endpoint are undocumented in the first place, Cuprate could just drop support for this. + distribution: Vec, amount: u64, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] - /// Will always be serialized as `false`. - binary: bool, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] - /// Will always be serialized as `false`. - compress: bool, - }, - /// Distribution data will be (de)serialized as binary. - Binary { - data: OutputDistributionData, - amount: u64, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] - /// Will always be serialized as `true`. binary: bool, #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] /// Will always be serialized as `false`. @@ -80,9 +73,10 @@ pub enum Distribution { }, /// Distribution data will be (de)serialized as compressed binary. CompressedBinary { - data: OutputDistributionData, - amount: u64, + start_height: u64, + base: u64, compressed_data: String, + amount: u64, #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] /// Will always be serialized as `true`. binary: bool, @@ -94,8 +88,10 @@ pub enum Distribution { impl Default for Distribution { fn default() -> Self { - Self::Json { - data: OutputDistributionData::default(), + Self::Uncompressed { + start_height: u64::default(), + base: u64::default(), + distribution: Vec::::default(), amount: u64::default(), binary: false, compress: false, @@ -112,7 +108,9 @@ impl Default for Distribution { #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct __DistributionEpeeBuilder { - pub data: Option, + pub start_height: Option, + pub base: Option, + pub distribution: Option>, pub amount: Option, pub compressed_data: Option, pub binary: Option, @@ -134,7 +132,9 @@ impl EpeeObjectBuilder for __DistributionEpeeBuilder { } read_epee_field! { - data, + start_height, + base, + distribution, amount, compressed_data, binary, @@ -147,7 +147,8 @@ impl EpeeObjectBuilder for __DistributionEpeeBuilder { fn finish(self) -> error::Result { const ELSE: error::Error = error::Error::Format("Required field was not found!"); - let data = self.data.ok_or(ELSE)?; + let start_height = self.start_height.ok_or(ELSE)?; + let base = self.base.ok_or(ELSE)?; let amount = self.amount.ok_or(ELSE)?; let binary = self.binary.ok_or(ELSE)?; let compress = self.compress.ok_or(ELSE)?; @@ -155,21 +156,17 @@ impl EpeeObjectBuilder for __DistributionEpeeBuilder { let distribution = if binary && compress { Distribution::CompressedBinary { compressed_data: self.compressed_data.ok_or(ELSE)?, - data, - amount, - binary, - compress, - } - } else if binary { - Distribution::Binary { - data, + start_height, + base, amount, binary, compress, } } else { - Distribution::Json { - data, + Distribution::Uncompressed { + distribution: self.distribution.ok_or(ELSE)?, + start_height, + base, amount, binary, compress, @@ -185,63 +182,109 @@ impl EpeeObject for Distribution { type Builder = __DistributionEpeeBuilder; fn number_of_fields(&self) -> u64 { - match self { - Self::Json { .. } | Self::Binary { .. } => 4, - Self::CompressedBinary { .. } => 5, + let mut fields = 0; + + macro_rules! add_field { + ($($field:ident),*) => { + $( + if $field.should_write() { + fields += 1; + } + )* + }; } - } - fn write_fields(self, w: &mut B) -> error::Result<()> { match self { - Self::Json { - data, + Self::Uncompressed { + distribution, + start_height, + base, amount, binary, compress, + } => { + const COMPRESS: bool = false; + add_field! { + distribution, + start_height, + base, + amount, + binary, + COMPRESS + } } - | Self::Binary { - data, + Self::CompressedBinary { + start_height, + base, + compressed_data, amount, binary, compress, } => { - if amount.should_write() { - write_field(amount, "amount", w)?; - } - if binary.should_write() { - write_field(binary, "binary", w)?; + const BINARY: bool = true; + const COMPRESS: bool = true; + add_field! { + start_height, + base, + compressed_data, + amount, + BINARY, + COMPRESS } - if compress.should_write() { - write_field(compress, "compress", w)?; - } - data.write(w)?; } + } - Self::CompressedBinary { - data, + fields + } + + fn write_fields(self, w: &mut B) -> error::Result<()> { + macro_rules! write_field { + ($($field:ident),*) => { + $( + if $field.should_write() { + write_field($field, stringify!($field), w)?; + } + )* + }; + } + + match self { + Self::Uncompressed { + distribution, + start_height, + base, amount, - compressed_data, binary, compress, } => { - if amount.should_write() { - write_field(amount, "amount", w)?; - } - if binary.should_write() { - write_field(binary, "binary", w)?; - } - if compress.should_write() { - write_field(compress, "compress", w)?; - } - if data.start_height.should_write() { - write_field(data.start_height, "start_height", w)?; - } - if data.base.should_write() { - write_field(data.base, "base", w)?; + const COMPRESS: bool = false; + write_field! { + distribution, + start_height, + base, + amount, + binary, + COMPRESS } + } - if compressed_data.should_write() { - compressed_data.write(w)?; + Self::CompressedBinary { + start_height, + base, + compressed_data, + amount, + binary, + compress, + } => { + const BINARY: bool = true; + const COMPRESS: bool = true; + write_field! { + start_height, + base, + compressed_data, + amount, + BINARY, + COMPRESS } } } From 0b716267cdcc686932e58f768b0dd600986bc6d5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:14:02 -0400 Subject: [PATCH 26/93] types: add `block_complete_entry.rs` --- Cargo.lock | 4 + types/Cargo.toml | 9 +- types/README.md | 25 ++--- types/src/block_complete_entry.rs | 154 ++++++++++++++++++++++++++++++ types/src/lib.rs | 12 +-- 5 files changed, 178 insertions(+), 26 deletions(-) create mode 100644 types/src/block_complete_entry.rs diff --git a/Cargo.lock b/Cargo.lock index 01d5329de..f36c1476c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,8 +800,12 @@ version = "0.0.0" name = "cuprate-types" version = "0.0.0" dependencies = [ + "bytes", + "cuprate-epee-encoding", + "cuprate-fixed-bytes", "curve25519-dalek", "monero-serai", + "serde", ] [[package]] diff --git a/types/Cargo.toml b/types/Cargo.toml index 7f6b8f8d6..8f16eb486 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -9,11 +9,18 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types" keywords = ["cuprate", "types"] [features] -default = ["blockchain"] +default = ["blockchain", "epee", "serde"] blockchain = [] +epee = ["dep:cuprate-epee-encoding"] +serde = ["dep:serde"] [dependencies] +cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true } +cuprate-fixed-bytes = { path = "../net/fixed-bytes" } + +bytes = { workspace = true } curve25519-dalek = { workspace = true } monero-serai = { workspace = true } +serde = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] \ No newline at end of file diff --git a/types/README.md b/types/README.md index 6a2015afb..168b00342 100644 --- a/types/README.md +++ b/types/README.md @@ -1,20 +1,11 @@ # `cuprate-types` -Various data types shared by Cuprate. +Shared data types within Cuprate. -- [1. File Structure](#1-file-structure) - - [1.1 `src/`](#11-src) +This crate is a kitchen-sink for data types that are shared across Cuprate. ---- - -## 1. File Structure -A quick reference of the structure of the folders & files in `cuprate-types`. - -Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`. - -### 1.1 `src/` -The top-level `src/` files. - -| File | Purpose | -|---------------------|---------| -| `service.rs` | Types used in database requests; `enum {Request,Response}` -| `types.rs` | Various general types used by Cuprate \ No newline at end of file +# Features flags +| Feature flag | Does what | +|--------------|-----------| +| `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 diff --git a/types/src/block_complete_entry.rs b/types/src/block_complete_entry.rs new file mode 100644 index 000000000..ba5fc2b0d --- /dev/null +++ b/types/src/block_complete_entry.rs @@ -0,0 +1,154 @@ +//! Contains [`BlockCompleteEntry`] and the related types. + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "epee")] +use bytes::Bytes; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use cuprate_fixed_bytes::ByteArray; + +#[cfg(feature = "epee")] +use cuprate_epee_encoding::{ + epee_object, + macros::bytes::{Buf, BufMut}, + EpeeValue, InnerMarker, +}; + +//---------------------------------------------------------------------------------------------------- BlockCompleteEntry +/// A block that can contain transactions. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct BlockCompleteEntry { + /// `true` if transaction data is pruned. + pub pruned: bool, + /// The block. + pub block: Bytes, + /// The block weight/size. + pub block_weight: u64, + /// The block's transactions. + pub txs: TransactionBlobs, +} + +#[cfg(feature = "epee")] +epee_object!( + BlockCompleteEntry, + pruned: bool = false, + block: Bytes, + block_weight: u64 = 0_u64, + txs: TransactionBlobs = TransactionBlobs::None => + TransactionBlobs::tx_blob_read, + TransactionBlobs::tx_blob_write, + TransactionBlobs::should_write_tx_blobs, +); + +//---------------------------------------------------------------------------------------------------- TransactionBlobs +/// Transaction blobs within [`BlockCompleteEntry`]. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TransactionBlobs { + /// Pruned transaction blobs. + Pruned(Vec), + /// Normal transaction blobs. + Normal(Vec), + #[default] + /// No transactions. + None, +} + +impl TransactionBlobs { + /// Returns [`Some`] if `self` is [`Self::Pruned`]. + pub fn take_pruned(self) -> Option> { + match self { + Self::Normal(_) => None, + Self::Pruned(txs) => Some(txs), + Self::None => Some(vec![]), + } + } + + /// Returns [`Some`] if `self` is [`Self::Normal`]. + pub fn take_normal(self) -> Option> { + match self { + Self::Normal(txs) => Some(txs), + Self::Pruned(_) => None, + Self::None => Some(vec![]), + } + } + + /// Returns the byte length of the blob. + pub fn len(&self) -> usize { + match self { + Self::Normal(txs) => txs.len(), + Self::Pruned(txs) => txs.len(), + Self::None => 0, + } + } + + /// Returns `true` if the byte length of the blob is `0`. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Epee read function. + #[cfg(feature = "epee")] + fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { + let marker = cuprate_epee_encoding::read_marker(b)?; + match marker.inner_marker { + InnerMarker::Object => Ok(Self::Pruned(Vec::read(b, &marker)?)), + InnerMarker::String => Ok(Self::Normal(Vec::read(b, &marker)?)), + _ => Err(cuprate_epee_encoding::Error::Value( + "Invalid marker for tx blobs".to_string(), + )), + } + } + + /// Epee write function. + #[cfg(feature = "epee")] + fn tx_blob_write( + self, + field_name: &str, + w: &mut B, + ) -> cuprate_epee_encoding::Result<()> { + if self.should_write_tx_blobs() { + match self { + Self::Normal(bytes) => { + cuprate_epee_encoding::write_field(bytes, field_name, w)?; + } + Self::Pruned(obj) => { + cuprate_epee_encoding::write_field(obj, field_name, w)?; + } + Self::None => (), + } + } + Ok(()) + } + + /// Epee should write function. + #[cfg(feature = "epee")] + fn should_write_tx_blobs(&self) -> bool { + !self.is_empty() + } +} + +//---------------------------------------------------------------------------------------------------- PrunedTxBlobEntry +/// A pruned transaction with the hash of the missing prunable data +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PrunedTxBlobEntry { + /// The transaction. + pub tx: Bytes, + /// The prunable transaction hash. + pub prunable_hash: ByteArray<32>, +} + +#[cfg(feature = "epee")] +epee_object!( + PrunedTxBlobEntry, + tx: Bytes, + prunable_hash: ByteArray<32>, +); + +//---------------------------------------------------------------------------------------------------- Import +#[cfg(test)] +mod tests {} diff --git a/types/src/lib.rs b/types/src/lib.rs index 2d161f7ec..1cdb9d571 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1,11 +1,4 @@ -//! Cuprate shared data types. -//! -//! This crate is a kitchen-sink for data types that are shared across `Cuprate`. -//! -//! # Features flags -//! The [`blockchain`] module, containing the blockchain database request/response -//! types, must be enabled with the `blockchain` feature (on by default). - +#![doc = include_str!("../README.md")] //---------------------------------------------------------------------------------------------------- Lints // Forbid lints. // Our code, and code generated (e.g macros) cannot overrule these. @@ -86,7 +79,10 @@ // // Documentation for each module is located in the respective file. +mod block_complete_entry; mod types; + +pub use block_complete_entry::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; pub use types::{ ExtendedBlockHeader, OutputOnChain, VerifiedBlockInformation, VerifiedTransactionInformation, }; From 231aab8ae79911fea3c78062bb60b51b17abcd31 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:16:56 -0400 Subject: [PATCH 27/93] net: fix imports --- net/wire/Cargo.toml | 1 + net/wire/src/p2p/common.rs | 107 ----------------------------------- net/wire/src/p2p/protocol.rs | 5 +- 3 files changed, 3 insertions(+), 110 deletions(-) diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml index c71a77b8a..11900f7a6 100644 --- a/net/wire/Cargo.toml +++ b/net/wire/Cargo.toml @@ -14,6 +14,7 @@ tracing = ["cuprate-levin/tracing"] cuprate-levin = { path = "../levin" } cuprate-epee-encoding = { path = "../epee-encoding" } cuprate-fixed-bytes = { path = "../fixed-bytes" } +cuprate-types = { path = "../../types" } bitflags = { workspace = true, features = ["std"] } bytes = { workspace = true, features = ["std"] } diff --git a/net/wire/src/p2p/common.rs b/net/wire/src/p2p/common.rs index 91adb9085..818a3c18f 100644 --- a/net/wire/src/p2p/common.rs +++ b/net/wire/src/p2p/common.rs @@ -168,113 +168,6 @@ epee_object! { rpc_credits_per_hash: u32 = 0_u32, } -/// A pruned tx with the hash of the missing prunable data -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PrunedTxBlobEntry { - /// The Tx - pub tx: Bytes, - /// The Prunable Tx Hash - pub prunable_hash: ByteArray<32>, -} - -epee_object!( - PrunedTxBlobEntry, - tx: Bytes, - prunable_hash: ByteArray<32>, -); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TransactionBlobs { - Pruned(Vec), - Normal(Vec), - None, -} - -impl TransactionBlobs { - pub fn take_pruned(self) -> Option> { - match self { - TransactionBlobs::Normal(_) => None, - TransactionBlobs::Pruned(txs) => Some(txs), - TransactionBlobs::None => Some(vec![]), - } - } - - pub fn take_normal(self) -> Option> { - match self { - TransactionBlobs::Normal(txs) => Some(txs), - TransactionBlobs::Pruned(_) => None, - TransactionBlobs::None => Some(vec![]), - } - } - - pub fn len(&self) -> usize { - match self { - TransactionBlobs::Normal(txs) => txs.len(), - TransactionBlobs::Pruned(txs) => txs.len(), - TransactionBlobs::None => 0, - } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -/// A Block that can contain transactions -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BlockCompleteEntry { - /// True if tx data is pruned - pub pruned: bool, - /// The Block - pub block: Bytes, - /// The Block Weight/Size - pub block_weight: u64, - /// The blocks txs - pub txs: TransactionBlobs, -} - -epee_object!( - BlockCompleteEntry, - pruned: bool = false, - block: Bytes, - block_weight: u64 = 0_u64, - txs: TransactionBlobs = TransactionBlobs::None => tx_blob_read, tx_blob_write, should_write_tx_blobs, -); - -fn tx_blob_read(b: &mut B) -> cuprate_epee_encoding::Result { - let marker = cuprate_epee_encoding::read_marker(b)?; - match marker.inner_marker { - InnerMarker::Object => Ok(TransactionBlobs::Pruned(Vec::read(b, &marker)?)), - InnerMarker::String => Ok(TransactionBlobs::Normal(Vec::read(b, &marker)?)), - _ => Err(cuprate_epee_encoding::Error::Value( - "Invalid marker for tx blobs".to_string(), - )), - } -} - -fn tx_blob_write( - val: TransactionBlobs, - field_name: &str, - w: &mut B, -) -> cuprate_epee_encoding::Result<()> { - if should_write_tx_blobs(&val) { - match val { - TransactionBlobs::Normal(bytes) => { - cuprate_epee_encoding::write_field(bytes, field_name, w)? - } - TransactionBlobs::Pruned(obj) => { - cuprate_epee_encoding::write_field(obj, field_name, w)? - } - TransactionBlobs::None => (), - } - } - Ok(()) -} - -fn should_write_tx_blobs(val: &TransactionBlobs) -> bool { - !val.is_empty() -} - #[cfg(test)] mod tests { diff --git a/net/wire/src/p2p/protocol.rs b/net/wire/src/p2p/protocol.rs index a385099b3..2588e60b3 100644 --- a/net/wire/src/p2p/protocol.rs +++ b/net/wire/src/p2p/protocol.rs @@ -16,14 +16,13 @@ //! This module defines Monero protocol messages //! //! Protocol message requests don't have to be responded to in order unlike -//! admin messages. +//! admin messages. use bytes::Bytes; use cuprate_epee_encoding::{container_as_blob::ContainerAsBlob, epee_object}; use cuprate_fixed_bytes::{ByteArray, ByteArrayVec}; - -use super::common::BlockCompleteEntry; +use cuprate_types::BlockCompleteEntry; /// A block that SHOULD have transactions #[derive(Debug, Clone, PartialEq, Eq)] From 1d5e959d4459ebbfaa6ccf9c9536586df69ce720 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:16:59 -0400 Subject: [PATCH 28/93] p2p: fix imports --- Cargo.lock | 2 ++ p2p/p2p/Cargo.toml | 1 + p2p/p2p/src/block_downloader/tests.rs | 6 ++---- p2p/p2p/src/broadcast.rs | 6 ++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f36c1476c..965e2c6c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,6 +702,7 @@ dependencies = [ "cuprate-p2p-core", "cuprate-pruning", "cuprate-test-utils", + "cuprate-types", "cuprate-wire", "dashmap", "futures", @@ -817,6 +818,7 @@ dependencies = [ "cuprate-epee-encoding", "cuprate-fixed-bytes", "cuprate-levin", + "cuprate-types", "hex", "thiserror", ] diff --git a/p2p/p2p/Cargo.toml b/p2p/p2p/Cargo.toml index e9b03d2cf..4566bd81b 100644 --- a/p2p/p2p/Cargo.toml +++ b/p2p/p2p/Cargo.toml @@ -13,6 +13,7 @@ cuprate-address-book = { path = "../address-book" } cuprate-pruning = { path = "../../pruning" } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } cuprate-async-buffer = { path = "../async-buffer" } +cuprate-types = { path = "../../types" } monero-serai = { workspace = true, features = ["std"] } diff --git a/p2p/p2p/src/block_downloader/tests.rs b/p2p/p2p/src/block_downloader/tests.rs index 981c557f1..f6ddbfc67 100644 --- a/p2p/p2p/src/block_downloader/tests.rs +++ b/p2p/p2p/src/block_downloader/tests.rs @@ -26,10 +26,8 @@ use cuprate_p2p_core::{ ProtocolResponse, }; use cuprate_pruning::PruningSeed; -use cuprate_wire::{ - common::{BlockCompleteEntry, TransactionBlobs}, - protocol::{ChainResponse, GetObjectsResponse}, -}; +use cuprate_types::{BlockCompleteEntry, TransactionBlobs}; +use cuprate_wire::protocol::{ChainResponse, GetObjectsResponse}; use crate::{ block_downloader::{download_blocks, BlockDownloaderConfig, ChainSvcRequest, ChainSvcResponse}, diff --git a/p2p/p2p/src/broadcast.rs b/p2p/p2p/src/broadcast.rs index cfda28bc3..5d7d61e32 100644 --- a/p2p/p2p/src/broadcast.rs +++ b/p2p/p2p/src/broadcast.rs @@ -25,10 +25,8 @@ use tower::Service; use cuprate_p2p_core::{ client::InternalPeerID, BroadcastMessage, ConnectionDirection, NetworkZone, }; -use cuprate_wire::{ - common::{BlockCompleteEntry, TransactionBlobs}, - protocol::{NewFluffyBlock, NewTransactions}, -}; +use cuprate_types::{BlockCompleteEntry, TransactionBlobs}; +use cuprate_wire::protocol::{NewFluffyBlock, NewTransactions}; use crate::constants::{ DIFFUSION_FLUSH_AVERAGE_SECONDS_INBOUND, DIFFUSION_FLUSH_AVERAGE_SECONDS_OUTBOUND, From 4ef0cbbe75dd4d1e62c210a29b9604f81d43e918 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:19:17 -0400 Subject: [PATCH 29/93] turn off default-features --- net/wire/Cargo.toml | 2 +- p2p/p2p/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml index 11900f7a6..dac4e3ddc 100644 --- a/net/wire/Cargo.toml +++ b/net/wire/Cargo.toml @@ -14,7 +14,7 @@ tracing = ["cuprate-levin/tracing"] cuprate-levin = { path = "../levin" } cuprate-epee-encoding = { path = "../epee-encoding" } cuprate-fixed-bytes = { path = "../fixed-bytes" } -cuprate-types = { path = "../../types" } +cuprate-types = { path = "../../types", default-features = false } bitflags = { workspace = true, features = ["std"] } bytes = { workspace = true, features = ["std"] } diff --git a/p2p/p2p/Cargo.toml b/p2p/p2p/Cargo.toml index 4566bd81b..7cbbdcb1d 100644 --- a/p2p/p2p/Cargo.toml +++ b/p2p/p2p/Cargo.toml @@ -13,7 +13,7 @@ cuprate-address-book = { path = "../address-book" } cuprate-pruning = { path = "../../pruning" } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } cuprate-async-buffer = { path = "../async-buffer" } -cuprate-types = { path = "../../types" } +cuprate-types = { path = "../../types", default-features = false } monero-serai = { workspace = true, features = ["std"] } From 86c4fde1378154268e5d9152e3122c63803808bc Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:25:17 -0400 Subject: [PATCH 30/93] p2p: fix imports --- net/wire/src/p2p/common.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/wire/src/p2p/common.rs b/net/wire/src/p2p/common.rs index 818a3c18f..56caa90bb 100644 --- a/net/wire/src/p2p/common.rs +++ b/net/wire/src/p2p/common.rs @@ -16,10 +16,8 @@ //! Common types that are used across multiple messages. use bitflags::bitflags; -use bytes::{Buf, BufMut, Bytes}; -use cuprate_epee_encoding::{epee_object, EpeeValue, InnerMarker}; -use cuprate_fixed_bytes::ByteArray; +use cuprate_epee_encoding::epee_object; use crate::NetworkAddress; From a249a3dcf4ba67a56cd654b28ee9a16eb08918ad Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 15 Jul 2024 20:29:53 -0400 Subject: [PATCH 31/93] misc fixes --- rpc/types/README.md | 4 ++-- rpc/types/src/serde.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rpc/types/README.md b/rpc/types/README.md index f71ba982f..566cca7eb 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -86,8 +86,8 @@ 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 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 diff --git a/rpc/types/src/serde.rs b/rpc/types/src/serde.rs index 1ae9ec93c..cba52bc4f 100644 --- a/rpc/types/src/serde.rs +++ b/rpc/types/src/serde.rs @@ -1,4 +1,4 @@ -//! TODO +//! Custom (de)serialization functions for serde. //---------------------------------------------------------------------------------------------------- Lints #![allow(clippy::trivially_copy_pass_by_ref)] // serde fn signature @@ -6,7 +6,7 @@ //---------------------------------------------------------------------------------------------------- Import use serde::Serializer; -//---------------------------------------------------------------------------------------------------- TODO +//---------------------------------------------------------------------------------------------------- Free functions #[inline] pub(crate) fn serde_true(_: &bool, serializer: S) -> Result where From afabd39dcd1df8162147492d4f4440f34e23e137 Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Tue, 16 Jul 2024 17:22:51 -0400 Subject: [PATCH 32/93] Update net/wire/Cargo.toml Co-authored-by: Boog900 --- net/wire/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wire/Cargo.toml b/net/wire/Cargo.toml index dac4e3ddc..101daa39e 100644 --- a/net/wire/Cargo.toml +++ b/net/wire/Cargo.toml @@ -14,7 +14,7 @@ tracing = ["cuprate-levin/tracing"] cuprate-levin = { path = "../levin" } cuprate-epee-encoding = { path = "../epee-encoding" } cuprate-fixed-bytes = { path = "../fixed-bytes" } -cuprate-types = { path = "../../types", default-features = false } +cuprate-types = { path = "../../types", default-features = false, features = ["epee"] } bitflags = { workspace = true, features = ["std"] } bytes = { workspace = true, features = ["std"] } From ecc501c428aec5ae331ce7c62fce51729cc3d9ae Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 17:23:10 -0400 Subject: [PATCH 33/93] distribution: module doc --- rpc/types/src/misc/distribution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index e50ab43ac..36f0a7110 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -1,4 +1,4 @@ -//! TODO +//! Output distributions for [`crate::json::GetOutputDistributionResponse`]. //---------------------------------------------------------------------------------------------------- Use use std::mem::size_of; From 42e5905c1b6e5dcce13a8ebb7f77ea435d5cc155 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 17:30:44 -0400 Subject: [PATCH 34/93] wire: re-export types --- net/wire/src/p2p/common.rs | 1 + net/wire/src/p2p/protocol.rs | 3 ++- types/README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/net/wire/src/p2p/common.rs b/net/wire/src/p2p/common.rs index 56caa90bb..d585d0718 100644 --- a/net/wire/src/p2p/common.rs +++ b/net/wire/src/p2p/common.rs @@ -18,6 +18,7 @@ use bitflags::bitflags; use cuprate_epee_encoding::epee_object; +pub use cuprate_types::{BlockCompleteEntry, PrunedTxBlobEntry, TransactionBlobs}; use crate::NetworkAddress; diff --git a/net/wire/src/p2p/protocol.rs b/net/wire/src/p2p/protocol.rs index 2588e60b3..73694d575 100644 --- a/net/wire/src/p2p/protocol.rs +++ b/net/wire/src/p2p/protocol.rs @@ -22,7 +22,8 @@ use bytes::Bytes; use cuprate_epee_encoding::{container_as_blob::ContainerAsBlob, epee_object}; use cuprate_fixed_bytes::{ByteArray, ByteArrayVec}; -use cuprate_types::BlockCompleteEntry; + +use crate::p2p::common::BlockCompleteEntry; /// A block that SHOULD have transactions #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/types/README.md b/types/README.md index 168b00342..4023e9ff0 100644 --- a/types/README.md +++ b/types/README.md @@ -6,6 +6,6 @@ This crate is a kitchen-sink for data types that are shared across Cuprate. # Features flags | Feature flag | Does what | |--------------|-----------| -| `blockchain` | Enables the [`blockchain`] module, containing the blockchain database request/response types +| `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 From 054079f7733c0b3ff4c196e402a74dfa762687e1 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 19:53:00 -0400 Subject: [PATCH 35/93] test-utils: add `crate::rpc::types` module --- Cargo.lock | 1 + test-utils/Cargo.toml | 34 +- test-utils/README.md | 2 +- test-utils/src/data/free.rs | 2 +- test-utils/src/lib.rs | 1 + test-utils/src/rpc/{ => client}/client.rs | 2 +- test-utils/src/rpc/{ => client}/constants.rs | 0 test-utils/src/rpc/client/mod.rs | 25 + test-utils/src/rpc/mod.rs | 27 +- test-utils/src/rpc/types/bin.rs | 123 +++++ test-utils/src/rpc/types/json.rs | 501 +++++++++++++++++++ test-utils/src/rpc/types/macros.rs | 117 +++++ test-utils/src/rpc/types/mod.rs | 14 + test-utils/src/rpc/types/other.rs | 399 +++++++++++++++ 14 files changed, 1205 insertions(+), 43 deletions(-) rename test-utils/src/rpc/{ => client}/client.rs (99%) rename test-utils/src/rpc/{ => client}/constants.rs (100%) create mode 100644 test-utils/src/rpc/client/mod.rs create mode 100644 test-utils/src/rpc/types/bin.rs create mode 100644 test-utils/src/rpc/types/json.rs create mode 100644 test-utils/src/rpc/types/macros.rs create mode 100644 test-utils/src/rpc/types/mod.rs create mode 100644 test-utils/src/rpc/types/other.rs diff --git a/Cargo.lock b/Cargo.lock index 965e2c6c2..426ccc2fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -785,6 +785,7 @@ dependencies = [ "hex", "hex-literal", "monero-serai", + "paste", "pretty_assertions", "serde", "serde_json", diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index f9a5c6d99..dd24fd597 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -1,30 +1,30 @@ [package] -name = "cuprate-test-utils" +name = "cuprate-test-utils" version = "0.1.0" edition = "2021" license = "MIT" authors = ["Boog900", "hinto-janai"] [dependencies] -cuprate-types = { path = "../types" } -cuprate-helper = { path = "../helper", features = ["map"] } -cuprate-wire = { path = "../net/wire" } +cuprate-types = { path = "../types" } +cuprate-helper = { path = "../helper", features = ["map"] } +cuprate-wire = { path = "../net/wire" } cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] } -hex = { workspace = true } -hex-literal = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } monero-serai = { workspace = true, features = ["std", "http-rpc"] } -futures = { workspace = true, features = ["std"] } -async-trait = { workspace = true } -tokio = { workspace = true, features = ["full"] } -tokio-util = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -bytes = { workspace = true, features = ["std"] } -tempfile = { workspace = true } - -borsh = { workspace = true, features = ["derive"]} +futures = { workspace = true, features = ["std"] } +async-trait = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tokio-util = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +bytes = { workspace = true, features = ["std"] } +tempfile = { workspace = true } +paste = { workspace = true } +borsh = { workspace = true, features = ["derive"]} [dev-dependencies] -hex = { workspace = true } +hex = { workspace = true } pretty_assertions = { workspace = true } \ No newline at end of file diff --git a/test-utils/README.md b/test-utils/README.md index c210686ac..441f6f363 100644 --- a/test-utils/README.md +++ b/test-utils/README.md @@ -6,4 +6,4 @@ Cuprate crate, only in tests. It currently contains: - Code to spawn monerod instances and a testing network zone - Real raw and typed Monero data, e.g. `Block, Transaction` -- An RPC client to generate types from `cuprate_types` +- An RPC client to generate types from `cuprate_types` \ No newline at end of file diff --git a/test-utils/src/data/free.rs b/test-utils/src/data/free.rs index e80bdda0d..ee6f49a62 100644 --- a/test-utils/src/data/free.rs +++ b/test-utils/src/data/free.rs @@ -292,7 +292,7 @@ mod tests { use pretty_assertions::assert_eq; - use crate::rpc::HttpRpcClient; + use crate::rpc::client::HttpRpcClient; /// Assert the defined blocks are the same compared to ones received from a local RPC call. #[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 068f28ff8..f09ae292c 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +#![allow(clippy::module_inception)] pub mod data; pub mod monerod; diff --git a/test-utils/src/rpc/client.rs b/test-utils/src/rpc/client/client.rs similarity index 99% rename from test-utils/src/rpc/client.rs rename to test-utils/src/rpc/client/client.rs index 22ae11f52..45cf1f2d1 100644 --- a/test-utils/src/rpc/client.rs +++ b/test-utils/src/rpc/client/client.rs @@ -12,7 +12,7 @@ use monero_serai::{ use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; -use crate::rpc::constants::LOCALHOST_RPC_URL; +use crate::rpc::client::constants::LOCALHOST_RPC_URL; //---------------------------------------------------------------------------------------------------- HttpRpcClient /// An HTTP RPC client for Monero. diff --git a/test-utils/src/rpc/constants.rs b/test-utils/src/rpc/client/constants.rs similarity index 100% rename from test-utils/src/rpc/constants.rs rename to test-utils/src/rpc/client/constants.rs diff --git a/test-utils/src/rpc/client/mod.rs b/test-utils/src/rpc/client/mod.rs new file mode 100644 index 000000000..14c963ac4 --- /dev/null +++ b/test-utils/src/rpc/client/mod.rs @@ -0,0 +1,25 @@ +//! Monero RPC client. +//! +//! This module is a client for Monero RPC that maps the types +//! into the native types used by Cuprate found in `cuprate_types`. +//! +//! # Usage +//! ```rust,ignore +//! #[tokio::main] +//! async fn main() { +//! // Create RPC client. +//! let rpc = HttpRpcClient::new(None).await; +//! +//! // Collect 20 blocks. +//! let mut vec: Vec = vec![]; +//! for height in (3130269 - 20)..3130269 { +//! vec.push(rpc.get_verified_block_information(height).await); +//! } +//! } +//! ``` + +mod client; +pub use client::HttpRpcClient; + +mod constants; +pub use constants::LOCALHOST_RPC_URL; diff --git a/test-utils/src/rpc/mod.rs b/test-utils/src/rpc/mod.rs index 14c963ac4..028ca9298 100644 --- a/test-utils/src/rpc/mod.rs +++ b/test-utils/src/rpc/mod.rs @@ -1,25 +1,6 @@ -//! Monero RPC client. +//! Monero RPC data & client. //! -//! This module is a client for Monero RPC that maps the types -//! into the native types used by Cuprate found in `cuprate_types`. -//! -//! # Usage -//! ```rust,ignore -//! #[tokio::main] -//! async fn main() { -//! // Create RPC client. -//! let rpc = HttpRpcClient::new(None).await; -//! -//! // Collect 20 blocks. -//! let mut vec: Vec = vec![]; -//! for height in (3130269 - 20)..3130269 { -//! vec.push(rpc.get_verified_block_information(height).await); -//! } -//! } -//! ``` - -mod client; -pub use client::HttpRpcClient; +//! This module has a `monerod` RPC [`client`] and some real request/response [`types`]. -mod constants; -pub use constants::LOCALHOST_RPC_URL; +pub mod client; +pub mod types; diff --git a/test-utils/src/rpc/types/bin.rs b/test-utils/src/rpc/types/bin.rs new file mode 100644 index 000000000..323632746 --- /dev/null +++ b/test-utils/src/rpc/types/bin.rs @@ -0,0 +1,123 @@ +//! Binary types from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- 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]>, +// } +// } + +// define_request_and_response! { +// get_blocks_by_heightbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 264..=286, +// GetBlocksByHeight, +// Request { +// heights: Vec, +// }, +// AccessResponseBase { +// blocks: Vec, +// } +// } + +// define_request_and_response! { +// get_hashesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 309..=338, +// GetHashes, +// Request { +// block_ids: ByteArrayVec<32>, +// start_height: u64, +// }, +// AccessResponseBase { +// m_blocks_ids: ByteArrayVec<32>, +// start_height: u64, +// current_height: u64, +// } +// } + +// #[cfg(not(feature = "epee"))] +// define_request_and_response! { +// get_o_indexesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 487..=510, +// GetOutputIndexes, +// #[derive(Copy)] +// Request { +// txid: [u8; 32], +// }, +// AccessResponseBase { +// o_indexes: Vec, +// } +// } + +// #[cfg(feature = "epee")] +// define_request_and_response! { +// get_o_indexesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 487..=510, +// GetOutputIndexes, +// #[derive(Copy)] +// Request { +// txid: [u8; 32], +// }, +// AccessResponseBase { +// o_indexes: Vec as ContainerAsBlob, +// } +// } + +// define_request_and_response! { +// get_outsbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 512..=565, +// GetOuts, +// Request { +// outputs: Vec, +// get_txid: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// outs: Vec, +// } +// } + +// define_request_and_response! { +// get_transaction_pool_hashesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1593..=1613, +// GetTransactionPoolHashes, +// Request {}, +// AccessResponseBase { +// tx_hashes: ByteArrayVec<32>, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/types/json.rs b/test-utils/src/rpc/types/json.rs new file mode 100644 index 000000000..53d99cb93 --- /dev/null +++ b/test-utils/src/rpc/types/json.rs @@ -0,0 +1,501 @@ +//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. + +//---------------------------------------------------------------------------------------------------- Import +use crate::rpc::types::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- Struct definitions +// This generates 2 const strings: +// +// - `const GET_BLOCK_TEMPLATE_REQUEST: &str = "..."` +// - `const GET_BLOCK_TEMPLATE_RESPONSE: &str = "..."` +// +// with some interconnected documentation. +define_request_and_response! { + // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. + get_block_template, + + // The base const name: the type of the request/response. + GET_BLOCK_TEMPLATE: &str, + + // The request literal. + Request = ""; + + // The response literal. + Response = ""; +} + +// define_request_and_response! { +// get_block_count, +// GetBlockCount, +// Request {}, +// Response { +// } +// } + +// define_request_and_response! { +// on_get_block_hash, +// OnGetBlockHash, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashRequest { block_height: [3] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "[3]"); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// #[derive(Copy)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_height: [u64; 1], +// }, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "\"asdf\""); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// block_hash: String, +// } +// } + +// define_request_and_response! { +// submit_block, +// SubmitBlock, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, r#"["a"]"#); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_blob: [String; 1], +// }, +// ResponseBase { +// block_id: String, +// } +// } + +// define_request_and_response! { +// generateblocks, +// GenerateBlocks, +// Request { +// amount_of_blocks: u64, +// prev_block: String, +// starting_nonce: u32, +// wallet_address: String, +// }, +// ResponseBase { +// blocks: Vec, +// height: u64, +// } +// } + +// define_request_and_response! { +// get_last_block_header, +// GetLastBlockHeader, +// #[derive(Copy)] +// Request { +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_header_by_hash, +// GetBlockHeaderByHash, +// Request { +// hash: String, +// hashes: Vec, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// block_headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block_header_by_height, +// GetBlockHeaderByHeight, +// #[derive(Copy)] +// Request { +// height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_headers_range, +// GetBlockHeadersRange, +// #[derive(Copy)] +// Request { +// start_height: u64, +// end_height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block, +// GetBlock, +// Request { +// // `monerod` has both `hash` and `height` fields. +// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. +// height: u64 = default_height(), "default_height", +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// blob: String, +// block_header: BlockHeader, +// json: String, // TODO: this should be defined in a struct, it has many fields. +// miner_tx_hash: String, +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// get_connections, +// GetConnections, +// Request {}, +// ResponseBase { +// // FIXME: This is a `std::list` in `monerod` because...? +// connections: Vec, +// } +// } + +// define_request_and_response! { +// get_info, +// GetInfo, +// Request {}, +// AccessResponseBase { +// adjusted_time: u64, +// alt_blocks_count: u64, +// block_size_limit: u64, +// block_size_median: u64, +// block_weight_limit: u64, +// block_weight_median: u64, +// bootstrap_daemon_address: String, +// busy_syncing: bool, +// cumulative_difficulty_top64: u64, +// cumulative_difficulty: u64, +// database_size: u64, +// difficulty_top64: u64, +// difficulty: u64, +// free_space: u64, +// grey_peerlist_size: u64, +// height: u64, +// height_without_bootstrap: u64, +// incoming_connections_count: u64, +// mainnet: bool, +// nettype: String, +// offline: bool, +// outgoing_connections_count: u64, +// restricted: bool, +// rpc_connections_count: u64, +// stagenet: bool, +// start_time: u64, +// synchronized: bool, +// target_height: u64, +// target: u64, +// testnet: bool, +// top_block_hash: String, +// tx_count: u64, +// tx_pool_size: u64, +// update_available: bool, +// version: String, +// was_bootstrap_ever_used: bool, +// white_peerlist_size: u64, +// wide_cumulative_difficulty: String, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// hard_fork_info, +// HardForkInfo, +// Request {}, +// AccessResponseBase { +// earliest_height: u64, +// enabled: bool, +// state: u32, +// threshold: u32, +// version: u8, +// votes: u32, +// voting: u8, +// window: u32, +// } +// } + +// define_request_and_response! { +// set_bans, +// SetBans, +// Request { +// bans: Vec, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// get_bans, +// GetBans, +// Request {}, +// ResponseBase { +// bans: Vec, +// } +// } + +// define_request_and_response! { +// banned, +// Banned, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// address: String, +// }, +// #[derive(Copy)] +// Response { +// banned: bool, +// seconds: u32, +// status: Status, +// } +// } + +// define_request_and_response! { +// flush_txpool, +// FlushTransactionPool, +// Request { +// txids: Vec = default_vec::(), "default_vec", +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_output_histogram, +// GetOutputHistogram, +// Request { +// amounts: Vec, +// min_count: u64, +// max_count: u64, +// unlocked: bool, +// recent_cutoff: u64, +// }, +// AccessResponseBase { +// histogram: Vec, +// } +// } + +// define_request_and_response! { +// get_coinbase_tx_sum, +// GetCoinbaseTxSum, +// Request { +// height: u64, +// count: u64, +// }, +// AccessResponseBase { +// emission_amount: u64, +// emission_amount_top64: u64, +// fee_amount: u64, +// fee_amount_top64: u64, +// wide_emission_amount: String, +// wide_fee_amount: String, +// } +// } + +// define_request_and_response! { +// get_version, +// GetVersion, +// Request {}, +// ResponseBase { +// version: u32, +// release: bool, +// #[serde(skip_serializing_if = "is_zero")] +// current_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "is_zero")] +// target_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "Vec::is_empty")] +// hard_forks: Vec = default_vec(), "default_vec", +// } +// } + +// define_request_and_response! { +// get_fee_estimate, +// GetFeeEstimate, +// Request {}, +// AccessResponseBase { +// fee: u64, +// fees: Vec, +// #[serde(skip_serializing_if = "is_one")] +// quantization_mask: u64, +// } +// } + +// define_request_and_response! { +// get_alternate_chains, +// GetAlternateChains, +// Request {}, +// ResponseBase { +// chains: Vec, +// } +// } + +// define_request_and_response! { +// relay_tx, +// RelayTx, +// Request { +// txids: Vec, +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// sync_info, +// SyncInfo, +// Request {}, +// AccessResponseBase { +// height: u64, +// next_needed_pruning_seed: u32, +// overview: String, +// // FIXME: This is a `std::list` in `monerod` because...? +// peers: Vec, +// // FIXME: This is a `std::list` in `monerod` because...? +// spans: Vec, +// target_height: u64, +// } +// } + +// define_request_and_response! { +// get_txpool_backlog, +// GetTransactionPoolBacklog, +// Request {}, +// ResponseBase { +// // TODO: this is a [`BinaryString`]. +// backlog: Vec, +// } +// } + +// define_request_and_response! { +// get_output_distribution, +// /// This type is also used in the (undocumented) +// GetOutputDistribution, +// Request { +// amounts: Vec, +// binary: bool, +// compress: bool, +// cumulative: bool, +// from_height: u64, +// to_height: u64, +// }, +// /// TODO: this request has custom serde: +// distributions: Vec, +// } +// } + +// define_request_and_response! { +// get_miner_data, +// GetMinerData, +// Request {}, +// ResponseBase { +// major_version: u8, +// height: u64, +// prev_id: String, +// seed_hash: String, +// difficulty: String, +// median_weight: u64, +// already_generated_coins: u64, +// } +// } + +// define_request_and_response! { +// prune_blockchain, +// PruneBlockchain, +// #[derive(Copy)] +// Request { +// check: bool = default_false(), "default_false", +// }, +// #[derive(Copy)] +// ResponseBase { +// pruned: bool, +// pruning_seed: u32, +// } +// } + +// define_request_and_response! { +// calc_pow, +// CalcPow, +// Request { +// major_version: u8, +// height: u64, +// block_blob: String, +// seed_hash: String, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// pow_hash: String, +// } +// } + +// define_request_and_response! { +// flush_cache, +// FlushCache, +// #[derive(Copy)] +// Request { +// bad_txs: bool = default_false(), "default_false", +// bad_blocks: bool = default_false(), "default_false", +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// add_aux_pow, +// AddAuxPow, +// Request { +// blocktemplate_blob: String, +// aux_pow: Vec, +// }, +// ResponseBase { +// blocktemplate_blob: String, +// blockhashing_blob: String, +// merkle_root: String, +// merkle_tree_depth: u64, +// aux_pow: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/types/macros.rs b/test-utils/src/rpc/types/macros.rs new file mode 100644 index 000000000..0a63e7f94 --- /dev/null +++ b/test-utils/src/rpc/types/macros.rs @@ -0,0 +1,117 @@ +//! Macros. + +//---------------------------------------------------------------------------------------------------- define_request_and_response +/// A template for generating the RPC request and response `const` data. +/// +/// See the [`crate::json`] module for example usage. +/// +/// # Macro internals +/// This macro uses: +/// - [`__define_request_and_response_doc`] +/// - [`__define_request_and_response_test`] +macro_rules! define_request_and_response { + ( + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. + $monero_daemon_rpc_doc_link:ident, + + // The base `struct` name. + // Attributes added here will apply to _both_ + // request and response types. + $( #[$attr:meta] )* + $name:ident: $type:ty, + + // The request type (and any doc comments, derives, etc). + $( #[$request_attr:meta] )* + Request = $request:literal; + + // The response type (and any doc comments, derives, etc). + $( #[$response_attr:meta] )* + Response = $response:literal; + ) => { paste::paste! { + #[doc = $crate::rpc::types::macros::__define_request_and_response_doc!( + "response" => [<$name:upper _RESPONSE>], + $monero_daemon_rpc_doc_link, + )] + /// + $( #[$attr] )* + /// + $( #[$request_attr] )* + /// + #[doc = $crate::rpc::types::macros::__define_request_and_response_test!([<$name:upper _REQUEST>])] + pub const [<$name:upper _REQUEST>]: $type = $request; + + #[doc = $crate::rpc::types::macros::__define_request_and_response_doc!( + "request" => [<$name:upper _REQUEST>], + $monero_daemon_rpc_doc_link, + )] + /// + $( #[$attr] )* + /// + $( #[$response_attr] )* + /// + #[doc = $crate::rpc::types::macros::__define_request_and_response_test!([<$name:upper _RESPONSE>])] + pub const [<$name:upper _RESPONSE>]: $type = $response; + }}; +} +pub(super) use define_request_and_response; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_doc +/// Generate documentation for the types generated +/// by the [`__define_request_and_response`] macro. +/// +/// See it for more info on inputs. +macro_rules! __define_request_and_response_doc { + ( + // This labels the last `[request]` or `[response]` + // hyperlink in documentation. Input is either: + // - "request" + // - "response" + // + // Remember this is linking to the _other_ type, + // so if defining a `Request` type, input should + // be "response". + $request_or_response:literal => $request_or_response_type:ident, + $monero_daemon_rpc_doc_link:ident, + ) => { + concat!( + "", + "[Documentation](", + "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", + "#", + stringify!($monero_daemon_rpc_doc_link), + "), [", + $request_or_response, + "](", + stringify!($request_or_response_type), + ")." + ) + }; +} +pub(super) use __define_request_and_response_doc; + +//---------------------------------------------------------------------------------------------------- __define_request_and_response_test +/// Generate documentation for the types generated +/// by the [`__define_request_and_response`] macro. +/// +/// See it for more info on inputs. +macro_rules! __define_request_and_response_test { + ( + $name:ident // TODO + ) => { + concat!( + "```rust", + "use cuprate_test_utils::rpc::types::{json::*,bin::*,other::*};", + "use serde_json::to_value;", + "", + "let value = serde_json::to_value(&", + stringify!($name), + ").unwrap();", + "let string = serde_json::to_string_pretty(&value).unwrap();", + "assert_eq!(string, ", + stringify!($name), + ");", + "```", + ) + }; +} +pub(super) use __define_request_and_response_test; diff --git a/test-utils/src/rpc/types/mod.rs b/test-utils/src/rpc/types/mod.rs new file mode 100644 index 000000000..4b75f888c --- /dev/null +++ b/test-utils/src/rpc/types/mod.rs @@ -0,0 +1,14 @@ +//! Monero RPC types. +//! +//! This module contains real `monerod` RPC +//! requests/responses as `const` strings. +//! +//! These strings include the JSON-RPC 2.0 portions of the JSON. +//! +//! Tests exist within Cuprate's `rpc/` crates that +//! ensure these strings are valid. + +pub mod bin; +pub mod json; +mod macros; +pub mod other; diff --git a/test-utils/src/rpc/types/other.rs b/test-utils/src/rpc/types/other.rs new file mode 100644 index 000000000..fa2bec676 --- /dev/null +++ b/test-utils/src/rpc/types/other.rs @@ -0,0 +1,399 @@ +//! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. + +//---------------------------------------------------------------------------------------------------- Import +// use crate::rpc::types::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- TODO +// define_request_and_response! { +// get_height, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 138..=160, +// GetHeight, +// Request {}, +// ResponseBase { +// hash: String, +// height: u64, +// } +// } + +// define_request_and_response! { +// get_transactions, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 370..=451, +// GetTransactions, +// Request { +// txs_hashes: Vec, +// // FIXME: this is documented as optional but it isn't serialized as an optional +// // but it is set _somewhere_ to false in `monerod` +// // +// decode_as_json: bool = default_false(), "default_false", +// prune: bool = default_false(), "default_false", +// split: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// txs_as_hex: Vec, +// txs_as_json: Vec, +// missed_tx: Vec, +// txs: Vec, +// } +// } + +// define_request_and_response! { +// get_alt_blocks_hashes, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 288..=308, +// GetAltBlocksHashes, +// Request {}, +// AccessResponseBase { +// blks_hashes: Vec, +// } +// } + +// define_request_and_response! { +// is_key_image_spent, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 454..=484, +// IsKeyImageSpent, +// Request { +// key_images: Vec, +// }, +// AccessResponseBase { +// spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. +// } +// } + +// define_request_and_response! { +// send_raw_transaction, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 370..=451, +// SendRawTransaction, +// Request { +// tx_as_hex: String, +// do_not_relay: bool = default_false(), "default_false", +// do_sanity_checks: bool = default_true(), "default_true", +// }, +// AccessResponseBase { +// double_spend: bool, +// fee_too_low: bool, +// invalid_input: bool, +// invalid_output: bool, +// low_mixin: bool, +// nonzero_unlock_time: bool, +// not_relayed: bool, +// overspend: bool, +// reason: String, +// sanity_check_failed: bool, +// too_big: bool, +// too_few_outputs: bool, +// tx_extra_too_big: bool, +// } +// } + +// define_request_and_response! { +// start_mining, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 665..=691, +// StartMining, +// Request { +// miner_address: String, +// threads_count: u64, +// do_background_mining: bool, +// ignore_battery: bool, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// stop_mining, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 825..=843, +// StopMining, +// Request {}, +// ResponseBase {} +// } + +// define_request_and_response! { +// mining_status, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 846..=895, +// MiningStatus, +// Request {}, +// ResponseBase { +// active: bool, +// address: String, +// bg_idle_threshold: u8, +// bg_ignore_battery: bool, +// bg_min_idle_seconds: u8, +// bg_target: u8, +// block_reward: u64, +// block_target: u32, +// difficulty: u64, +// difficulty_top64: u64, +// is_background_mining_enabled: bool, +// pow_algorithm: String, +// speed: u64, +// threads_count: u32, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// save_bc, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 898..=916, +// SaveBc, +// Request {}, +// ResponseBase {} +// } + +// define_request_and_response! { +// get_peer_list, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1369..=1417, +// GetPeerList, +// Request { +// public_only: bool = default_true(), "default_true", +// include_blocked: bool = default_false(), "default_false", +// }, +// ResponseBase { +// white_list: Vec, +// gray_list: Vec, +// } +// } + +// define_request_and_response! { +// set_log_hash_rate, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1450..=1470, +// SetLogHashRate, +// #[derive(Copy)] +// Request { +// visible: bool, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// set_log_level, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1450..=1470, +// SetLogLevel, +// #[derive(Copy)] +// Request { +// level: u8, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// set_log_categories, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1494..=1517, +// SetLogCategories, +// Request { +// categories: String = default_string(), "default_string", +// }, +// ResponseBase { +// categories: String, +// } +// } + +// define_request_and_response! { +// set_bootstrap_daemon, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1785..=1812, +// SetBootstrapDaemon, +// Request { +// address: String, +// username: String, +// password: String, +// proxy: String, +// }, +// #[derive(Copy)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_transaction_pool, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1569..=1591, +// GetTransactionPool, +// Request {}, +// AccessResponseBase { +// transactions: Vec, +// spent_key_images: Vec, +// } +// } + +// define_request_and_response! { +// get_transaction_pool_stats, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1712..=1732, +// GetTransactionPoolStats, +// Request {}, +// AccessResponseBase { +// pool_stats: TxpoolStats, +// } +// } + +// define_request_and_response! { +// stop_daemon, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1814..=1831, +// StopDaemon, +// Request {}, +// ResponseBase { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_limit, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1852..=1874, +// GetLimit, +// Request {}, +// ResponseBase { +// limit_down: u64, +// limit_up: u64, +// } +// } + +// define_request_and_response! { +// set_limit, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1876..=1903, +// SetLimit, +// Request { +// limit_down: i64, +// limit_up: i64, +// }, +// ResponseBase { +// limit_down: i64, +// limit_up: i64, +// } +// } + +// define_request_and_response! { +// out_peers, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1876..=1903, +// OutPeers, +// Request { +// set: bool = default_true(), "default_true", +// out_peers: u32, +// }, +// ResponseBase { +// out_peers: u32, +// } +// } + +// define_request_and_response! { +// get_net_stats, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 793..=822, +// GetNetStats, +// Request {}, +// ResponseBase { +// start_time: u64, +// total_packets_in: u64, +// total_bytes_in: u64, +// total_packets_out: u64, +// total_bytes_out: u64, +// } +// } + +// define_request_and_response! { +// get_outs, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 567..=609, +// GetOuts, +// Request { +// outputs: Vec, +// get_txid: bool, +// }, +// ResponseBase { +// outs: Vec, +// } +// } + +// define_request_and_response! { +// update, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2324..=2359, +// Update, +// Request { +// command: String, +// path: String = default_string(), "default_string", +// }, +// ResponseBase { +// auto_uri: String, +// hash: String, +// path: String, +// update: bool, +// user_uri: String, +// version: String, +// } +// } + +// define_request_and_response! { +// pop_blocks, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2722..=2745, +// PopBlocks, +// Request { +// nblocks: u64, +// }, +// ResponseBase { +// height: u64, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2798..=2823, +// GetTxIdsLoose, +// Request { +// txid_template: String, +// num_matching_bits: u32, +// }, +// ResponseBase { +// txids: Vec, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1615..=1635, +// GetTransactionPoolHashes, +// Request {}, +// ResponseBase { +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1419..=1448, +// GetPublicNodes, +// Request { +// gray: bool = default_false(), "default_false", +// white: bool = default_true(), "default_true", +// include_blocked: bool = default_false(), "default_false", +// }, +// ResponseBase { +// gray: Vec, +// white: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} From 5e2c0aa2be28cba082e6b77bbadb148ed01120f3 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 20:10:51 -0400 Subject: [PATCH 36/93] test-utils: conditional json doc-tests --- test-utils/README.md | 3 ++- test-utils/src/rpc/types/json.rs | 5 +++- test-utils/src/rpc/types/macros.rs | 37 +++++++++++++++++++----------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/test-utils/README.md b/test-utils/README.md index 441f6f363..3c71c0a3e 100644 --- a/test-utils/README.md +++ b/test-utils/README.md @@ -6,4 +6,5 @@ Cuprate crate, only in tests. It currently contains: - Code to spawn monerod instances and a testing network zone - Real raw and typed Monero data, e.g. `Block, Transaction` -- An RPC client to generate types from `cuprate_types` \ No newline at end of file +- An RPC client to generate types from `cuprate_types` +- Raw RPC request/response strings and binary data \ No newline at end of file diff --git a/test-utils/src/rpc/types/json.rs b/test-utils/src/rpc/types/json.rs index 53d99cb93..db835c9b1 100644 --- a/test-utils/src/rpc/types/json.rs +++ b/test-utils/src/rpc/types/json.rs @@ -12,7 +12,10 @@ use crate::rpc::types::macros::define_request_and_response; // with some interconnected documentation. define_request_and_response! { // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. - get_block_template, + // + // Adding `(json)` after this will trigger the macro to automatically + // add a `serde_json` test for the request/response data. + get_block_template (json), // The base const name: the type of the request/response. GET_BLOCK_TEMPLATE: &str, diff --git a/test-utils/src/rpc/types/macros.rs b/test-utils/src/rpc/types/macros.rs index 0a63e7f94..aed0ed88b 100644 --- a/test-utils/src/rpc/types/macros.rs +++ b/test-utils/src/rpc/types/macros.rs @@ -7,12 +7,15 @@ /// /// # Macro internals /// This macro uses: -/// - [`__define_request_and_response_doc`] -/// - [`__define_request_and_response_test`] +/// - [`define_request_and_response_doc`] +/// - [`define_request_and_response_test`] macro_rules! define_request_and_response { ( // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. - $monero_daemon_rpc_doc_link:ident, + // + // Adding `(json)` after this will trigger the macro to automatically + // add a `serde_json` test for the request/response data. + $monero_daemon_rpc_doc_link:ident $(($json:ident))?, // The base `struct` name. // Attributes added here will apply to _both_ @@ -28,7 +31,7 @@ macro_rules! define_request_and_response { $( #[$response_attr:meta] )* Response = $response:literal; ) => { paste::paste! { - #[doc = $crate::rpc::types::macros::__define_request_and_response_doc!( + #[doc = $crate::rpc::types::macros::define_request_and_response_doc!( "response" => [<$name:upper _RESPONSE>], $monero_daemon_rpc_doc_link, )] @@ -37,10 +40,13 @@ macro_rules! define_request_and_response { /// $( #[$request_attr] )* /// - #[doc = $crate::rpc::types::macros::__define_request_and_response_test!([<$name:upper _REQUEST>])] + $( + const _: &str = stringify!($json); + #[doc = $crate::rpc::types::macros::json_test!([<$name:upper _REQUEST>])] + )? pub const [<$name:upper _REQUEST>]: $type = $request; - #[doc = $crate::rpc::types::macros::__define_request_and_response_doc!( + #[doc = $crate::rpc::types::macros::define_request_and_response_doc!( "request" => [<$name:upper _REQUEST>], $monero_daemon_rpc_doc_link, )] @@ -49,7 +55,10 @@ macro_rules! define_request_and_response { /// $( #[$response_attr] )* /// - #[doc = $crate::rpc::types::macros::__define_request_and_response_test!([<$name:upper _RESPONSE>])] + $( + const _: &str = stringify!($json); + #[doc = $crate::rpc::types::macros::json_test!([<$name:upper _RESPONSE>])] + )? pub const [<$name:upper _RESPONSE>]: $type = $response; }}; } @@ -57,10 +66,10 @@ pub(super) use define_request_and_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. -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: @@ -87,14 +96,14 @@ macro_rules! __define_request_and_response_doc { ) }; } -pub(super) use __define_request_and_response_doc; +pub(super) use define_request_and_response_doc; -//---------------------------------------------------------------------------------------------------- __define_request_and_response_test +//---------------------------------------------------------------------------------------------------- define_request_and_response_test /// 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. -macro_rules! __define_request_and_response_test { +macro_rules! json_test { ( $name:ident // TODO ) => { @@ -114,4 +123,4 @@ macro_rules! __define_request_and_response_test { ) }; } -pub(super) use __define_request_and_response_test; +pub(super) use json_test; From 58c64ca3ef79352fa3a40e53d66e1298293cd0e8 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 21:00:14 -0400 Subject: [PATCH 37/93] bin: use enum for `GetBlocksResponse` --- rpc/types/src/bin.rs | 343 +++++++++++++++++++++++------ rpc/types/src/misc/distribution.rs | 2 + rpc/types/src/serde.rs | 2 + 3 files changed, 276 insertions(+), 71 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 8934940b1..625c60408 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -119,9 +119,9 @@ define_request_and_response! { define_request! { #[doc = define_request_and_response_doc!( "response" => GetBlocksResponse, - get_transaction_pool_hashesbin, + get_blocksbin, cc73fe71162d564ffda8e549b79a350bca53c454, - core_rpc_server_commands_defs, h, 1593, 1613, + core_rpc_server_commands_defs, h, 162, 262, )] GetBlocksRequest { requested_info: u8 = default_zero::(), "default_zero", @@ -136,25 +136,69 @@ define_request! { #[doc = define_request_and_response_doc!( "request" => GetBlocksRequest, - get_transaction_pool_hashesbin, + get_blocksbin, cc73fe71162d564ffda8e549b79a350bca53c454, - core_rpc_server_commands_defs, h, 1593, 1613, + 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, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct GetBlocksResponse { - 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 pool_info_extent: PoolInfoExtent, - pub added_pool_txs: Vec, - pub remaining_added_pool_txids: ByteArrayVec<32>, - pub removed_pool_txids: ByteArrayVec<32>, +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum GetBlocksResponse { + PoolInfoNone { + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + /// Will always serialize as [`PoolInfoExtent::None`]. + pool_info_extent: PoolInfoExtent, + }, + PoolInfoIncremental { + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + /// Will always serialize as [`PoolInfoExtent::Incremental`]. + pool_info_extent: PoolInfoExtent, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, + removed_pool_txids: ByteArrayVec<32>, + }, + PoolInfoFull { + status: Status, + untrusted: bool, + blocks: Vec, + start_height: u64, + current_height: u64, + output_indices: Vec, + daemon_time: u64, + /// Will always serialize as [`PoolInfoExtent::Full`]. + pool_info_extent: PoolInfoExtent, + added_pool_txs: Vec, + remaining_added_pool_txids: ByteArrayVec<32>, + }, +} + +impl Default for GetBlocksResponse { + fn default() -> Self { + Self::PoolInfoNone { + status: Status::default(), + untrusted: bool::default(), + blocks: Vec::::default(), + start_height: u64::default(), + current_height: u64::default(), + output_indices: Vec::::default(), + daemon_time: u64::default(), + pool_info_extent: PoolInfoExtent::default(), + } + } } #[cfg(feature = "epee")] @@ -211,23 +255,60 @@ impl EpeeObjectBuilder for __GetBlocksResponseEpeeBuilder { fn finish(self) -> error::Result { const ELSE: error::Error = error::Error::Format("Required field was not found!"); - Ok(GetBlocksResponse { - status: self.status.ok_or(ELSE)?, - untrusted: self.untrusted.ok_or(ELSE)?, - blocks: self.blocks.ok_or(ELSE)?, - start_height: self.start_height.ok_or(ELSE)?, - current_height: self.current_height.ok_or(ELSE)?, - output_indices: self.output_indices.ok_or(ELSE)?, - daemon_time: self.daemon_time.unwrap_or(0), - pool_info_extent: self.pool_info_extent.unwrap_or_default(), - 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)?, - }) + + 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 { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + }, + PoolInfoExtent::Incremental => GetBlocksResponse::PoolInfoIncremental { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + 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 { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + 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; @@ -237,30 +318,91 @@ impl EpeeObject for GetBlocksResponse { macro_rules! add_field { ($($field:ident),*) => { $( - if self.$field.should_write() { + if $field.should_write() { fields += 1; } )* }; } - add_field! { - status, - untrusted, - blocks, - start_height, - current_height, - output_indices, - daemon_time, - pool_info_extent - } - - if self.pool_info_extent != PoolInfoExtent::None { - add_field!(added_pool_txs, remaining_added_pool_txids); - } - - if self.pool_info_extent != PoolInfoExtent::Incremental { - add_field!(removed_pool_txids); + match self { + Self::PoolInfoNone { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + } => { + const POOL_INFO_EXTENT: u8 = PoolInfoExtent::None.to_u8(); + add_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + POOL_INFO_EXTENT + } + } + + Self::PoolInfoIncremental { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + removed_pool_txids, + } => { + const POOL_INFO_EXTENT: u8 = PoolInfoExtent::Incremental.to_u8(); + add_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 + } + } + Self::PoolInfoFull { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + } => { + const POOL_INFO_EXTENT: u8 = PoolInfoExtent::Full.to_u8(); + add_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + POOL_INFO_EXTENT, + added_pool_txs, + remaining_added_pool_txids + } + } } fields @@ -270,34 +412,93 @@ impl EpeeObject for GetBlocksResponse { macro_rules! write_field { ($($field:ident),*) => { $( - if self.$field.should_write() { - write_field(self.$field, stringify!($field), w)?; + if $field.should_write() { + write_field($field, stringify!($field), w)?; } )* }; } - write_field! { - status, - untrusted, - blocks, - start_height, - current_height, - output_indices, - daemon_time, - pool_info_extent - } - - // The following section is why custom epee (de)serialization exists. - // - // - - if self.pool_info_extent != PoolInfoExtent::None { - write_field!(added_pool_txs, remaining_added_pool_txids); - } - - if self.pool_info_extent != PoolInfoExtent::Incremental { - write_field!(removed_pool_txids); + match self { + Self::PoolInfoNone { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + } => { + // This is on purpose `lower_case` instead of + // `CONST_UPPER` due to `stringify!`. + let pool_info_extent = PoolInfoExtent::None.to_u8(); + write_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent + } + } + + Self::PoolInfoIncremental { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + removed_pool_txids, + } => { + let pool_info_extent = PoolInfoExtent::None.to_u8(); + write_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 + } + } + Self::PoolInfoFull { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids, + } => { + let pool_info_extent = PoolInfoExtent::None.to_u8(); + write_field! { + status, + untrusted, + blocks, + start_height, + current_height, + output_indices, + daemon_time, + pool_info_extent, + added_pool_txs, + remaining_added_pool_txids + } + } } Ok(()) diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index 36f0a7110..becb8d169 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -52,6 +52,8 @@ fn decompress_integer_array(array: Vec) -> Vec { 2468..=2508 )] /// Used in [`crate::json::GetOutputDistributionResponse`]. +/// +/// This enum's variant depends upon the `binary` and `compress` fields. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] diff --git a/rpc/types/src/serde.rs b/rpc/types/src/serde.rs index cba52bc4f..70885e09e 100644 --- a/rpc/types/src/serde.rs +++ b/rpc/types/src/serde.rs @@ -7,6 +7,7 @@ use serde::Serializer; //---------------------------------------------------------------------------------------------------- Free functions +/// Always serializes `true`. #[inline] pub(crate) fn serde_true(_: &bool, serializer: S) -> Result where @@ -15,6 +16,7 @@ where serializer.serialize_bool(true) } +/// Always serializes `false`. #[inline] pub(crate) fn serde_false(_: &bool, serializer: S) -> Result where From d5c502b1949849353baf976450f4ec8c0475f4a9 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 16 Jul 2024 21:01:29 -0400 Subject: [PATCH 38/93] misc: use lowercase for stringify --- rpc/types/src/misc/distribution.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index becb8d169..8de833e21 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -259,14 +259,16 @@ impl EpeeObject for Distribution { binary, compress, } => { - const COMPRESS: bool = false; + // This is on purpose `lower_case` instead of + // `CONST_UPPER` due to `stringify!`. + let compress = false; write_field! { distribution, start_height, base, amount, binary, - COMPRESS + compress } } @@ -278,15 +280,15 @@ impl EpeeObject for Distribution { binary, compress, } => { - const BINARY: bool = true; - const COMPRESS: bool = true; + let binary = true; + let compress = true; write_field! { start_height, base, compressed_data, amount, - BINARY, - COMPRESS + binary, + compress } } } From 28aafe9dd1ab4911b29b7fc7181b497823feace3 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 16:34:27 -0400 Subject: [PATCH 39/93] json: add test data, fix macro doc tests --- test-utils/src/rpc/{types => data}/bin.rs | 2 +- test-utils/src/rpc/data/json.rs | 879 +++++++++++++++++++ test-utils/src/rpc/{types => data}/macros.rs | 51 +- test-utils/src/rpc/{types => data}/mod.rs | 7 +- test-utils/src/rpc/{types => data}/other.rs | 2 +- test-utils/src/rpc/mod.rs | 4 +- test-utils/src/rpc/types/json.rs | 504 ----------- typos.toml | 1 + 8 files changed, 923 insertions(+), 527 deletions(-) rename test-utils/src/rpc/{types => data}/bin.rs (96%) create mode 100644 test-utils/src/rpc/data/json.rs rename test-utils/src/rpc/{types => data}/macros.rs (73%) rename test-utils/src/rpc/{types => data}/mod.rs (57%) rename test-utils/src/rpc/{types => data}/other.rs (98%) delete mode 100644 test-utils/src/rpc/types/json.rs diff --git a/test-utils/src/rpc/types/bin.rs b/test-utils/src/rpc/data/bin.rs similarity index 96% rename from test-utils/src/rpc/types/bin.rs rename to test-utils/src/rpc/data/bin.rs index 323632746..3b1b36422 100644 --- a/test-utils/src/rpc/types/bin.rs +++ b/test-utils/src/rpc/data/bin.rs @@ -1,4 +1,4 @@ -//! Binary types from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). +//! Binary data from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). //---------------------------------------------------------------------------------------------------- Import diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs new file mode 100644 index 000000000..f0d54600d --- /dev/null +++ b/test-utils/src/rpc/data/json.rs @@ -0,0 +1,879 @@ +//! JSON data from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. + +//---------------------------------------------------------------------------------------------------- Import +use crate::rpc::data::macros::define_request_and_response; + +//---------------------------------------------------------------------------------------------------- Struct definitions +// This generates 2 const strings: +// +// - `const GET_BLOCK_TEMPLATE_REQUEST: &str = "..."` +// - `const GET_BLOCK_TEMPLATE_RESPONSE: &str = "..."` +// +// with some interconnected documentation. +define_request_and_response! { + // The markdown tag for Monero RPC documentation. Not necessarily the endpoint (json). + // + // Adding `(json)` after this will trigger the macro to automatically + // add a `serde_json` test for the request/response data. + get_block_template (json), + + // The base const name: the type of the request/response. + GET_BLOCK_TEMPLATE: &str, + + // The request literal. + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_template", + "params": { + "wallet_address": "44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns", + "reserve_size": 60 + } +}"#; + + // The response literal. + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blockhashing_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401", + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": 283305047039, + "difficulty_top64": 0, + "expected_reward": 600000000000, + "height": 3195018, + "next_seed_hash": "", + "prev_hash": "9d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a", + "reserved_offset": 131, + "seed_hash": "e2aa0b7b55042cd48b02e395d78fa66a29815ccc1584e38db2d1f0e8485cd44f", + "seed_height": 3194880, + "status": "OK", + "untrusted": false, + "wide_difficulty": "0x41f64bf3ff" + } +}"#; +} + +define_request_and_response! { + get_block_count (json), + GET_BLOCK_COUNT: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_count" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "count": 3195019, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + on_get_block_hash (json), + ON_GET_BLOCK_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "on_get_block_hash", + "params": [912345] +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6" +}"#; +} + +define_request_and_response! { + submit_block (json), + SUBMIT_BLOCK: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "submit_block", + "params": ["0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000"] +}"#; + Response = +r#"{ + "error": { + "code": -7, + "message": "Block not accepted" + }, + "id": "0", + "jsonrpc": "2.0" +}"#; +} + +define_request_and_response! { + generateblocks (json), + GENERATE_BLOCKS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "generateblocks", + "params": { + "amount_of_blocks": 1, + "wallet_address": "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A", + "starting_nonce": 0 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blocks": ["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4"], + "height": 9783, + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_last_block_header (json), + GET_LAST_BLOCK_HEADER: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_last_block_header" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 200419, + "block_weight": 200419, + "cumulative_difficulty": 366125734645190820, + "cumulative_difficulty_top64": 0, + "depth": 0, + "difficulty": 282052561854, + "difficulty_top64": 0, + "hash": "57238217820195ac4c08637a144a885491da167899cf1d20e8e7ce0ae0a3434e", + "height": 3195020, + "long_term_weight": 200419, + "major_version": 16, + "miner_tx_hash": "7a42667237d4f79891bb407c49c712a9299fb87fce799833a7b633a3a9377dbd", + "minor_version": 16, + "nonce": 1885649739, + "num_txes": 37, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "22c72248ae9c5a2863c94735d710a3525c499f70707d1c2f395169bc5c8a0da3", + "reward": 615702960000, + "timestamp": 1721245548, + "wide_cumulative_difficulty": "0x514bd6a74a7d0a4", + "wide_difficulty": "0x41aba48bbe" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_header_by_hash (json), + GET_BLOCK_HEADER_BY_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_header_by_hash", + "params": { + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 210, + "block_weight": 210, + "cumulative_difficulty": 754734824984346, + "cumulative_difficulty_top64": 0, + "depth": 2282676, + "difficulty": 815625611, + "difficulty_top64": 0, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "long_term_weight": 210, + "major_version": 1, + "miner_tx_hash": "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30", + "minor_version": 2, + "nonce": 1646, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716, + "wide_cumulative_difficulty": "0x2ae6d65248f1a", + "wide_difficulty": "0x309d758b" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_header_by_height (json), + GET_BLOCK_HEADER_BY_HEIGHT: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_header_by_height", + "params": { + "height": 912345 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "block_header": { + "block_size": 210, + "block_weight": 210, + "cumulative_difficulty": 754734824984346, + "cumulative_difficulty_top64": 0, + "depth": 2282677, + "difficulty": 815625611, + "difficulty_top64": 0, + "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", + "height": 912345, + "long_term_weight": 210, + "major_version": 1, + "miner_tx_hash": "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30", + "minor_version": 2, + "nonce": 1646, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", + "reward": 7388968946286, + "timestamp": 1452793716, + "wide_cumulative_difficulty": "0x2ae6d65248f1a", + "wide_difficulty": "0x309d758b" + }, + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block_headers_range (json), + GET_BLOCK_HEADERS_RANGE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block_headers_range", + "params": { + "start_height": 1545999, + "end_height": 1546000 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "headers": [{ + "block_size": 301413, + "block_weight": 301413, + "cumulative_difficulty": 13185267971483472, + "cumulative_difficulty_top64": 0, + "depth": 1649024, + "difficulty": 134636057921, + "difficulty_top64": 0, + "hash": "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a", + "height": 1545999, + "long_term_weight": 301413, + "major_version": 6, + "miner_tx_hash": "9909c6f8a5267f043c3b2b079fb4eacc49ef9c1dee1c028eeb1a259b95e6e1d9", + "minor_version": 6, + "nonce": 3246403956, + "num_txes": 20, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "0ef6e948f77b8f8806621003f5de24b1bcbea150bc0e376835aea099674a5db5", + "reward": 5025593029981, + "timestamp": 1523002893, + "wide_cumulative_difficulty": "0x2ed7ee6db56750", + "wide_difficulty": "0x1f58ef3541" + },{ + "block_size": 13322, + "block_weight": 13322, + "cumulative_difficulty": 13185402687569710, + "cumulative_difficulty_top64": 0, + "depth": 1649023, + "difficulty": 134716086238, + "difficulty_top64": 0, + "hash": "b408bf4cfcd7de13e7e370c84b8314c85b24f0ba4093ca1d6eeb30b35e34e91a", + "height": 1546000, + "long_term_weight": 13322, + "major_version": 7, + "miner_tx_hash": "7f749c7c64acb35ef427c7454c45e6688781fbead9bbf222cb12ad1a96a4e8f6", + "minor_version": 7, + "nonce": 3737164176, + "num_txes": 1, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a", + "reward": 4851952181070, + "timestamp": 1523002931, + "wide_cumulative_difficulty": "0x2ed80dcb69bf2e", + "wide_difficulty": "0x1f5db457de" + }], + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block (json), + GET_BLOCK: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block", + "params": { + "height": 2751506 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blob": "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000", + "block_header": { + "block_size": 106, + "block_weight": 106, + "cumulative_difficulty": 236046001376524168, + "cumulative_difficulty_top64": 0, + "depth": 443517, + "difficulty": 313732272488, + "difficulty_top64": 0, + "hash": "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428", + "height": 2751506, + "long_term_weight": 176470, + "major_version": 16, + "miner_tx_hash": "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd", + "minor_version": 16, + "nonce": 4110909056, + "num_txes": 0, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7", + "reward": 600000000000, + "timestamp": 1667941829, + "wide_cumulative_difficulty": "0x3469a966eb2f788", + "wide_difficulty": "0x490be69168" + }, + "credits": 0, + "json": "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667941829, \n \"prev_id\": \"b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7\", \n \"nonce\": 4110909056, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751566, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751506\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600000000000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85\", \n \"view_tag\": \"d0\"\n }\n }\n }\n ], \n \"extra\": [ 1, 159, 98, 157, 139, 54, 189, 22, 162, 191, 206, 62, 168, 12, 49, 220, 77, 135, 98, 198, 113, 101, 174, 194, 24, 69, 73, 78, 50, 183, 88, 47, 224, 2, 17, 0, 0, 0, 41, 122, 120, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ ]\n}", + "miner_tx_hash": "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd", + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_block (json), + /// This is the same as [`GET_BLOCK_REQUEST`] and + /// [`GET_BLOCK_RESPONSE`] but it uses the `hash` parameter. + GET_BLOCK_BY_HASH: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_block", + "params": { + "hash": "86d421322b700166dde2d7eba1cc8600925ef640abf6c0a2cc8ce0d6dd90abfd" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "blob": "1010d8faa89b06f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb5328010b02a6f6a70101ffeaf5a70101a08bc8b3bb11036d6713f5aa552a1aaf33baed7591f795b86daf339e51029a9062dfe09f0f909b312b0124d6023d591c4d434000e5e31c6db718a1e96e865939930e90a7042a1cd4cbd202083786a78452fdfc000002a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf90c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299", + "block_header": { + "block_size": 3166, + "block_weight": 3166, + "cumulative_difficulty": 235954020187853162, + "cumulative_difficulty_top64": 0, + "depth": 443814, + "difficulty": 312527777859, + "difficulty_top64": 0, + "hash": "86d421322b700166dde2d7eba1cc8600925ef640abf6c0a2cc8ce0d6dd90abfd", + "height": 2751210, + "long_term_weight": 176470, + "major_version": 16, + "miner_tx_hash": "dabe07900d3123ed895612f4a151adb3e39681b145f0f85bfee23ea1fe47acf2", + "minor_version": 16, + "nonce": 184625235, + "num_txes": 2, + "orphan_status": false, + "pow_hash": "", + "prev_hash": "f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb", + "reward": 600061380000, + "timestamp": 1667906904, + "wide_cumulative_difficulty": "0x34646ee649f516a", + "wide_difficulty": "0x48c41b7043" + }, + "credits": 0, + "json": "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667906904, \n \"prev_id\": \"f8a36d0dbe4d27d2f52160000563896048d71067c31e99a3869bf9b7142227bb\", \n \"nonce\": 184625235, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751270, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751210\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600061380000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"6d6713f5aa552a1aaf33baed7591f795b86daf339e51029a9062dfe09f0f909b\", \n \"view_tag\": \"31\"\n }\n }\n }\n ], \n \"extra\": [ 1, 36, 214, 2, 61, 89, 28, 77, 67, 64, 0, 229, 227, 28, 109, 183, 24, 161, 233, 110, 134, 89, 57, 147, 14, 144, 167, 4, 42, 28, 212, 203, 210, 2, 8, 55, 134, 167, 132, 82, 253, 252, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ \"a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf9\", \"0c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299\"\n ]\n}", + "miner_tx_hash": "dabe07900d3123ed895612f4a151adb3e39681b145f0f85bfee23ea1fe47acf2", + "status": "OK", + "top_hash": "", + "tx_hashes": ["a89e380a44d8dfc64b551baa171447a0f9c9262255be6e8f8ef10896e36e2bf9","0c4d343e416e394ad9cc10b7d2df7b2f39370a554730f75dfcb04944bd62c299"], + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_connections (json), + GET_CONNECTIONS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_connections" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "connections": [{ + "address": "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "22ef856d0f1d44cc95e84fecfd065fe2", + "current_download": 0, + "current_upload": 0, + "height": 3195026, + "host": "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion", + "incoming": false, + "ip": "", + "live_time": 76651, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 0, + "recv_count": 240328, + "recv_idle_time": 34, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 3406572, + "send_idle_time": 30, + "state": "normal", + "support_flags": 0 + },{ + "address": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "c7734e15936f485a86d2b0534f87e499", + "current_download": 0, + "current_upload": 0, + "height": 3195024, + "host": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion", + "incoming": false, + "ip": "", + "live_time": 76755, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 389, + "recv_count": 237657, + "recv_idle_time": 120, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 3370566, + "send_idle_time": 120, + "state": "normal", + "support_flags": 0 + }], + "status": "OK", + "untrusted": false + } +}"#; +} + +define_request_and_response! { + get_info (json), + GET_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_info" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "adjusted_time": 1721245289, + "alt_blocks_count": 16, + "block_size_limit": 600000, + "block_size_median": 300000, + "block_weight_limit": 600000, + "block_weight_median": 300000, + "bootstrap_daemon_address": "", + "busy_syncing": false, + "credits": 0, + "cumulative_difficulty": 366127702242611947, + "cumulative_difficulty_top64": 0, + "database_size": 235169075200, + "difficulty": 280716748706, + "difficulty_top64": 0, + "free_space": 30521749504, + "grey_peerlist_size": 4996, + "height": 3195028, + "height_without_bootstrap": 3195028, + "incoming_connections_count": 62, + "mainnet": true, + "nettype": "mainnet", + "offline": false, + "outgoing_connections_count": 1143, + "restricted": false, + "rpc_connections_count": 1, + "stagenet": false, + "start_time": 1720462427, + "status": "OK", + "synchronized": true, + "target": 120, + "target_height": 0, + "testnet": false, + "top_block_hash": "bdf06d18ed1931a8ee62654e9b6478cc459bc7072628b8e36f4524d339552946", + "top_hash": "", + "tx_count": 43205750, + "tx_pool_size": 12, + "untrusted": false, + "update_available": false, + "version": "0.18.3.3-release", + "was_bootstrap_ever_used": false, + "white_peerlist_size": 1000, + "wide_cumulative_difficulty": "0x514bf349299d2eb", + "wide_difficulty": "0x415c05a7a2" + } +}"#; +} + +// define_request_and_response! { +// hard_fork_info (json), +// HardForkInfo, +// Request = r#""#; +// Response = r#""#; +// earliest_height: u64, +// enabled: bool, +// state: u32, +// threshold: u32, +// version: u8, +// votes: u32, +// voting: u8, +// window: u32, +// } +// } + +// define_request_and_response! { +// set_bans (json), +// SetBans, +// Request = r#""#; +// bans: Vec, +// }, +// Response = +// r#""#; +// } + +// define_request_and_response! { +// get_bans (json), +// GetBans, +// Request = r#""#; +// Response = +// r#""#; +// bans: Vec, +// } +// } + +// define_request_and_response! { +// banned (json), +// Banned, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request = r#""#; +// address: String, +// }, +// #[derive(Copy)] +// Response { +// banned: bool, +// seconds: u32, +// status: Status, +// } +// } + +// define_request_and_response! { +// flush_txpool (json), +// FlushTransactionPool, +// Request = r#""#; +// txids: Vec = default_vec::(), "default_vec", +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_output_histogram (json), +// GetOutputHistogram, +// Request = r#""#; +// amounts: Vec, +// min_count: u64, +// max_count: u64, +// unlocked: bool, +// recent_cutoff: u64, +// }, +// Response = r#""#; +// histogram: Vec, +// } +// } + +// define_request_and_response! { +// get_coinbase_tx_sum (json), +// GetCoinbaseTxSum, +// Request = r#""#; +// height: u64, +// count: u64, +// }, +// Response = r#""#; +// emission_amount: u64, +// emission_amount_top64: u64, +// fee_amount: u64, +// fee_amount_top64: u64, +// wide_emission_amount: String, +// wide_fee_amount: String, +// } +// } + +// define_request_and_response! { +// get_version (json), +// GetVersion, +// Request = r#""#; +// Response = +// r#""#; +// version: u32, +// release: bool, +// #[serde(skip_serializing_if = "is_zero")] +// current_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "is_zero")] +// target_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "Vec::is_empty")] +// hard_forks: Vec = default_vec(), "default_vec", +// } +// } + +// define_request_and_response! { +// get_fee_estimate (json), +// GetFeeEstimate, +// Request = r#""#; +// Response = r#""#; +// fee: u64, +// fees: Vec, +// #[serde(skip_serializing_if = "is_one")] +// quantization_mask: u64, +// } +// } + +// define_request_and_response! { +// get_alternate_chains (json), +// GetAlternateChains, +// Request = r#""#; +// Response = +// r#""#; +// chains: Vec, +// } +// } + +// define_request_and_response! { +// relay_tx (json), +// RelayTx, +// Request = r#""#; +// txids: Vec, +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// sync_info (json), +// SyncInfo, +// Request = r#""#; +// Response = r#""#; +// height: u64, +// next_needed_pruning_seed: u32, +// overview: String, +// // FIXME: This is a `std::list` in `monerod` because...? +// peers: Vec, +// // FIXME: This is a `std::list` in `monerod` because...? +// spans: Vec, +// target_height: u64, +// } +// } + +// define_request_and_response! { +// get_txpool_backlog (json), +// GetTransactionPoolBacklog, +// Request = r#""#; +// Response = +// r#""#; +// // TODO: this is a [`BinaryString`]. +// backlog: Vec, +// } +// } + +// define_request_and_response! { +// get_output_distribution (json), +// /// This type is also used in the (undocumented) +// GetOutputDistribution, +// Request = r#""#; +// amounts: Vec, +// binary: bool, +// compress: bool, +// cumulative: bool, +// from_height: u64, +// to_height: u64, +// }, +// /// TODO: this request has custom serde: +// distributions: Vec, +// } +// } + +// define_request_and_response! { +// get_miner_data (json), +// GetMinerData, +// Request = r#""#; +// Response = +// r#""#; +// major_version: u8, +// height: u64, +// prev_id: String, +// seed_hash: String, +// difficulty: String, +// median_weight: u64, +// already_generated_coins: u64, +// } +// } + +// define_request_and_response! { +// prune_blockchain (json), +// PruneBlockchain, +// #[derive(Copy)] +// Request = r#""#; +// check: bool = default_false(), "default_false", +// }, +// #[derive(Copy)] +// Response = +// r#""#; +// pruned: bool, +// pruning_seed: u32, +// } +// } + +// define_request_and_response! { +// calc_pow (json), +// CalcPow, +// Request = r#""#; +// major_version: u8, +// height: u64, +// block_blob: String, +// seed_hash: String, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// pow_hash: String, +// } +// } + +// define_request_and_response! { +// flush_cache (json), +// FlushCache, +// #[derive(Copy)] +// Request = r#""#; +// bad_txs: bool = default_false(), "default_false", +// bad_blocks: bool = default_false(), "default_false", +// }, +// Response = +// r#""#; +// } + +// define_request_and_response! { +// add_aux_pow (json), +// AddAuxPow, +// Request = r#""#; +// blocktemplate_blob: String, +// aux_pow: Vec, +// }, +// Response = +// r#""#; +// blocktemplate_blob: String, +// blockhashing_blob: String, +// merkle_root: String, +// merkle_tree_depth: u64, +// aux_pow: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/test-utils/src/rpc/types/macros.rs b/test-utils/src/rpc/data/macros.rs similarity index 73% rename from test-utils/src/rpc/types/macros.rs rename to test-utils/src/rpc/data/macros.rs index aed0ed88b..893659660 100644 --- a/test-utils/src/rpc/types/macros.rs +++ b/test-utils/src/rpc/data/macros.rs @@ -31,7 +31,7 @@ macro_rules! define_request_and_response { $( #[$response_attr:meta] )* Response = $response:literal; ) => { paste::paste! { - #[doc = $crate::rpc::types::macros::define_request_and_response_doc!( + #[doc = $crate::rpc::data::macros::define_request_and_response_doc!( "response" => [<$name:upper _RESPONSE>], $monero_daemon_rpc_doc_link, )] @@ -41,12 +41,11 @@ macro_rules! define_request_and_response { $( #[$request_attr] )* /// $( - const _: &str = stringify!($json); - #[doc = $crate::rpc::types::macros::json_test!([<$name:upper _REQUEST>])] + #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _REQUEST>], $json)] )? pub const [<$name:upper _REQUEST>]: $type = $request; - #[doc = $crate::rpc::types::macros::define_request_and_response_doc!( + #[doc = $crate::rpc::data::macros::define_request_and_response_doc!( "request" => [<$name:upper _REQUEST>], $monero_daemon_rpc_doc_link, )] @@ -56,8 +55,7 @@ macro_rules! define_request_and_response { $( #[$response_attr] )* /// $( - const _: &str = stringify!($json); - #[doc = $crate::rpc::types::macros::json_test!([<$name:upper _RESPONSE>])] + #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _RESPONSE>], $json)] )? pub const [<$name:upper _RESPONSE>]: $type = $response; }}; @@ -105,21 +103,38 @@ pub(super) use define_request_and_response_doc; /// See it for more info on inputs. macro_rules! json_test { ( - $name:ident // TODO + $name:ident, // TODO + $json:ident ) => { concat!( - "```rust", - "use cuprate_test_utils::rpc::types::{json::*,bin::*,other::*};", - "use serde_json::to_value;", - "", - "let value = serde_json::to_value(&", - stringify!($name), - ").unwrap();", - "let string = serde_json::to_string_pretty(&value).unwrap();", - "assert_eq!(string, ", + "```rust\n", + "use cuprate_test_utils::rpc::data::{json::*, bin::*, other::*};\n", + "use serde_json::{to_value, Value};\n", + "\n", + "let value = serde_json::from_str::(&", stringify!($name), - ");", - "```", + ").unwrap();\n", + "let Value::Object(map) = value else {\n", + " panic!();\n", + "};\n", + "\n", + r#"assert_eq!(map.get("jsonrpc").unwrap(), "2.0");"#, + "\n", + r#"map.get("id").unwrap();"#, + "\n\n", + r#"if map.get("method").is_some() {"#, + "\n", + r#" return;"#, + "\n", + "}\n", + "\n", + r#"if map.get("result").is_none() {"#, + "\n", + r#" map.get("error").unwrap();"#, + "\n", + "}\n", + "\n", + "```\n", ) }; } diff --git a/test-utils/src/rpc/types/mod.rs b/test-utils/src/rpc/data/mod.rs similarity index 57% rename from test-utils/src/rpc/types/mod.rs rename to test-utils/src/rpc/data/mod.rs index 4b75f888c..662508185 100644 --- a/test-utils/src/rpc/types/mod.rs +++ b/test-utils/src/rpc/data/mod.rs @@ -1,4 +1,4 @@ -//! Monero RPC types. +//! Monero RPC data. //! //! This module contains real `monerod` RPC //! requests/responses as `const` strings. @@ -7,6 +7,11 @@ //! //! Tests exist within Cuprate's `rpc/` crates that //! ensure these strings are valid. +//! +//! # Determinism +//! Note that although both request/response data is defined, +//! they aren't necessarily tied to each other, i.e. the request +//! will not deterministically lead to the response. pub mod bin; pub mod json; diff --git a/test-utils/src/rpc/types/other.rs b/test-utils/src/rpc/data/other.rs similarity index 98% rename from test-utils/src/rpc/types/other.rs rename to test-utils/src/rpc/data/other.rs index fa2bec676..b739471cd 100644 --- a/test-utils/src/rpc/types/other.rs +++ b/test-utils/src/rpc/data/other.rs @@ -1,4 +1,4 @@ -//! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. +//! JSON data from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. //---------------------------------------------------------------------------------------------------- Import // use crate::rpc::types::macros::define_request_and_response; diff --git a/test-utils/src/rpc/mod.rs b/test-utils/src/rpc/mod.rs index 028ca9298..0da6b48f2 100644 --- a/test-utils/src/rpc/mod.rs +++ b/test-utils/src/rpc/mod.rs @@ -1,6 +1,6 @@ //! Monero RPC data & client. //! -//! This module has a `monerod` RPC [`client`] and some real request/response [`types`]. +//! This module has a `monerod` RPC [`client`] and some real request/response [`data`]. pub mod client; -pub mod types; +pub mod data; diff --git a/test-utils/src/rpc/types/json.rs b/test-utils/src/rpc/types/json.rs deleted file mode 100644 index db835c9b1..000000000 --- a/test-utils/src/rpc/types/json.rs +++ /dev/null @@ -1,504 +0,0 @@ -//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. - -//---------------------------------------------------------------------------------------------------- Import -use crate::rpc::types::macros::define_request_and_response; - -//---------------------------------------------------------------------------------------------------- Struct definitions -// This generates 2 const strings: -// -// - `const GET_BLOCK_TEMPLATE_REQUEST: &str = "..."` -// - `const GET_BLOCK_TEMPLATE_RESPONSE: &str = "..."` -// -// with some interconnected documentation. -define_request_and_response! { - // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. - // - // Adding `(json)` after this will trigger the macro to automatically - // add a `serde_json` test for the request/response data. - get_block_template (json), - - // The base const name: the type of the request/response. - GET_BLOCK_TEMPLATE: &str, - - // The request literal. - Request = ""; - - // The response literal. - Response = ""; -} - -// define_request_and_response! { -// get_block_count, -// GetBlockCount, -// Request {}, -// Response { -// } -// } - -// define_request_and_response! { -// on_get_block_hash, -// OnGetBlockHash, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashRequest { block_height: [3] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "[3]"); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// #[derive(Copy)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_height: [u64; 1], -// }, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "\"asdf\""); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// block_hash: String, -// } -// } - -// define_request_and_response! { -// submit_block, -// SubmitBlock, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, r#"["a"]"#); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_blob: [String; 1], -// }, -// ResponseBase { -// block_id: String, -// } -// } - -// define_request_and_response! { -// generateblocks, -// GenerateBlocks, -// Request { -// amount_of_blocks: u64, -// prev_block: String, -// starting_nonce: u32, -// wallet_address: String, -// }, -// ResponseBase { -// blocks: Vec, -// height: u64, -// } -// } - -// define_request_and_response! { -// get_last_block_header, -// GetLastBlockHeader, -// #[derive(Copy)] -// Request { -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } - -// define_request_and_response! { -// get_block_header_by_hash, -// GetBlockHeaderByHash, -// Request { -// hash: String, -// hashes: Vec, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// block_headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block_header_by_height, -// GetBlockHeaderByHeight, -// #[derive(Copy)] -// Request { -// height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } - -// define_request_and_response! { -// get_block_headers_range, -// GetBlockHeadersRange, -// #[derive(Copy)] -// Request { -// start_height: u64, -// end_height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block, -// GetBlock, -// Request { -// // `monerod` has both `hash` and `height` fields. -// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. -// height: u64 = default_height(), "default_height", -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// blob: String, -// block_header: BlockHeader, -// json: String, // TODO: this should be defined in a struct, it has many fields. -// miner_tx_hash: String, -// tx_hashes: Vec, -// } -// } - -// define_request_and_response! { -// get_connections, -// GetConnections, -// Request {}, -// ResponseBase { -// // FIXME: This is a `std::list` in `monerod` because...? -// connections: Vec, -// } -// } - -// define_request_and_response! { -// get_info, -// GetInfo, -// Request {}, -// AccessResponseBase { -// adjusted_time: u64, -// alt_blocks_count: u64, -// block_size_limit: u64, -// block_size_median: u64, -// block_weight_limit: u64, -// block_weight_median: u64, -// bootstrap_daemon_address: String, -// busy_syncing: bool, -// cumulative_difficulty_top64: u64, -// cumulative_difficulty: u64, -// database_size: u64, -// difficulty_top64: u64, -// difficulty: u64, -// free_space: u64, -// grey_peerlist_size: u64, -// height: u64, -// height_without_bootstrap: u64, -// incoming_connections_count: u64, -// mainnet: bool, -// nettype: String, -// offline: bool, -// outgoing_connections_count: u64, -// restricted: bool, -// rpc_connections_count: u64, -// stagenet: bool, -// start_time: u64, -// synchronized: bool, -// target_height: u64, -// target: u64, -// testnet: bool, -// top_block_hash: String, -// tx_count: u64, -// tx_pool_size: u64, -// update_available: bool, -// version: String, -// was_bootstrap_ever_used: bool, -// white_peerlist_size: u64, -// wide_cumulative_difficulty: String, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// hard_fork_info, -// HardForkInfo, -// Request {}, -// AccessResponseBase { -// earliest_height: u64, -// enabled: bool, -// state: u32, -// threshold: u32, -// version: u8, -// votes: u32, -// voting: u8, -// window: u32, -// } -// } - -// define_request_and_response! { -// set_bans, -// SetBans, -// Request { -// bans: Vec, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// get_bans, -// GetBans, -// Request {}, -// ResponseBase { -// bans: Vec, -// } -// } - -// define_request_and_response! { -// banned, -// Banned, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// address: String, -// }, -// #[derive(Copy)] -// Response { -// banned: bool, -// seconds: u32, -// status: Status, -// } -// } - -// define_request_and_response! { -// flush_txpool, -// FlushTransactionPool, -// Request { -// txids: Vec = default_vec::(), "default_vec", -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_output_histogram, -// GetOutputHistogram, -// Request { -// amounts: Vec, -// min_count: u64, -// max_count: u64, -// unlocked: bool, -// recent_cutoff: u64, -// }, -// AccessResponseBase { -// histogram: Vec, -// } -// } - -// define_request_and_response! { -// get_coinbase_tx_sum, -// GetCoinbaseTxSum, -// Request { -// height: u64, -// count: u64, -// }, -// AccessResponseBase { -// emission_amount: u64, -// emission_amount_top64: u64, -// fee_amount: u64, -// fee_amount_top64: u64, -// wide_emission_amount: String, -// wide_fee_amount: String, -// } -// } - -// define_request_and_response! { -// get_version, -// GetVersion, -// Request {}, -// ResponseBase { -// version: u32, -// release: bool, -// #[serde(skip_serializing_if = "is_zero")] -// current_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "is_zero")] -// target_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "Vec::is_empty")] -// hard_forks: Vec = default_vec(), "default_vec", -// } -// } - -// define_request_and_response! { -// get_fee_estimate, -// GetFeeEstimate, -// Request {}, -// AccessResponseBase { -// fee: u64, -// fees: Vec, -// #[serde(skip_serializing_if = "is_one")] -// quantization_mask: u64, -// } -// } - -// define_request_and_response! { -// get_alternate_chains, -// GetAlternateChains, -// Request {}, -// ResponseBase { -// chains: Vec, -// } -// } - -// define_request_and_response! { -// relay_tx, -// RelayTx, -// Request { -// txids: Vec, -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// sync_info, -// SyncInfo, -// Request {}, -// AccessResponseBase { -// height: u64, -// next_needed_pruning_seed: u32, -// overview: String, -// // FIXME: This is a `std::list` in `monerod` because...? -// peers: Vec, -// // FIXME: This is a `std::list` in `monerod` because...? -// spans: Vec, -// target_height: u64, -// } -// } - -// define_request_and_response! { -// get_txpool_backlog, -// GetTransactionPoolBacklog, -// Request {}, -// ResponseBase { -// // TODO: this is a [`BinaryString`]. -// backlog: Vec, -// } -// } - -// define_request_and_response! { -// get_output_distribution, -// /// This type is also used in the (undocumented) -// GetOutputDistribution, -// Request { -// amounts: Vec, -// binary: bool, -// compress: bool, -// cumulative: bool, -// from_height: u64, -// to_height: u64, -// }, -// /// TODO: this request has custom serde: -// distributions: Vec, -// } -// } - -// define_request_and_response! { -// get_miner_data, -// GetMinerData, -// Request {}, -// ResponseBase { -// major_version: u8, -// height: u64, -// prev_id: String, -// seed_hash: String, -// difficulty: String, -// median_weight: u64, -// already_generated_coins: u64, -// } -// } - -// define_request_and_response! { -// prune_blockchain, -// PruneBlockchain, -// #[derive(Copy)] -// Request { -// check: bool = default_false(), "default_false", -// }, -// #[derive(Copy)] -// ResponseBase { -// pruned: bool, -// pruning_seed: u32, -// } -// } - -// define_request_and_response! { -// calc_pow, -// CalcPow, -// Request { -// major_version: u8, -// height: u64, -// block_blob: String, -// seed_hash: String, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// pow_hash: String, -// } -// } - -// define_request_and_response! { -// flush_cache, -// FlushCache, -// #[derive(Copy)] -// Request { -// bad_txs: bool = default_false(), "default_false", -// bad_blocks: bool = default_false(), "default_false", -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// add_aux_pow, -// AddAuxPow, -// Request { -// blocktemplate_blob: String, -// aux_pow: Vec, -// }, -// ResponseBase { -// blocktemplate_blob: String, -// blockhashing_blob: String, -// merkle_root: String, -// merkle_tree_depth: u64, -// aux_pow: Vec, -// } -// } - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/typos.toml b/typos.toml index abab1903b..0317c404a 100644 --- a/typos.toml +++ b/typos.toml @@ -17,4 +17,5 @@ extend-ignore-identifiers-re = [ extend-exclude = [ "/misc/gpg_keys/", "cryptonight/", + "/test-utils/src/rpc/data/json.rs", ] From 8ed314af7f7ed31162342985dabfc80f137b384f Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 19:47:22 -0400 Subject: [PATCH 40/93] json: add all data --- test-utils/src/rpc/data/json.rs | 887 +++++++++++++++++++++++--------- 1 file changed, 638 insertions(+), 249 deletions(-) diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index f0d54600d..b027be3d6 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -416,7 +416,7 @@ define_request_and_response! { get_block (json), /// This is the same as [`GET_BLOCK_REQUEST`] and /// [`GET_BLOCK_RESPONSE`] but it uses the `hash` parameter. - GET_BLOCK_BY_HASH: &str, + GET_BLOCK_HASH: &str, Request = r#"{ "jsonrpc": "2.0", @@ -601,276 +601,665 @@ r#"{ }"#; } -// define_request_and_response! { -// hard_fork_info (json), -// HardForkInfo, -// Request = r#""#; -// Response = r#""#; -// earliest_height: u64, -// enabled: bool, -// state: u32, -// threshold: u32, -// version: u8, -// votes: u32, -// voting: u8, -// window: u32, -// } -// } +define_request_and_response! { + hard_fork_info (json), + HARD_FORK_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "hard_fork_info" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "earliest_height": 2689608, + "enabled": true, + "state": 0, + "status": "OK", + "threshold": 0, + "top_hash": "", + "untrusted": false, + "version": 16, + "votes": 10080, + "voting": 16, + "window": 10080 + } +}"#; +} -// define_request_and_response! { -// set_bans (json), -// SetBans, -// Request = r#""#; -// bans: Vec, -// }, -// Response = -// r#""#; -// } +define_request_and_response! { + set_bans (json), + SET_BANS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "set_bans", + "params": { + "bans": [{ + "host": "192.168.1.51", + "ban": true, + "seconds": 30 + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// get_bans (json), -// GetBans, -// Request = r#""#; -// Response = -// r#""#; -// bans: Vec, -// } -// } +define_request_and_response! { + set_bans (json), + /// This is the same as [`SET_BANS_REQUEST`] and + /// [`SET_BANS_RESPONSE`] but it uses the `ip` parameter. + SET_BANS_IP: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "set_bans", + "params": { + "bans": [{ + "ip": 838969536, + "ban": true, + "seconds": 30 + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// banned (json), -// Banned, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request = r#""#; -// address: String, -// }, -// #[derive(Copy)] -// Response { -// banned: bool, -// seconds: u32, -// status: Status, -// } -// } +define_request_and_response! { + get_bans (json), + GET_BANS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_bans" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "bans": [{ + "host": "104.248.206.131", + "ip": 2211379304, + "seconds": 689754 + },{ + "host": "209.222.252.0\/24", + "ip": 0, + "seconds": 689754 + }], + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// flush_txpool (json), -// FlushTransactionPool, -// Request = r#""#; -// txids: Vec = default_vec::(), "default_vec", -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } +define_request_and_response! { + banned (json), + BANNED: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "banned", + "params": { + "address": "95.216.203.255" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "banned": true, + "seconds": 689655, + "status": "OK" + } +}"#; +} -// define_request_and_response! { -// get_output_histogram (json), -// GetOutputHistogram, -// Request = r#""#; -// amounts: Vec, -// min_count: u64, -// max_count: u64, -// unlocked: bool, -// recent_cutoff: u64, -// }, -// Response = r#""#; -// histogram: Vec, -// } -// } +define_request_and_response! { + flush_txpool (json), + FLUSH_TRANSACTION_POOL: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "flush_txpool", + "params": { + "txids": ["dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308"] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK" + } +}"#; +} -// define_request_and_response! { -// get_coinbase_tx_sum (json), -// GetCoinbaseTxSum, -// Request = r#""#; -// height: u64, -// count: u64, -// }, -// Response = r#""#; -// emission_amount: u64, -// emission_amount_top64: u64, -// fee_amount: u64, -// fee_amount_top64: u64, -// wide_emission_amount: String, -// wide_fee_amount: String, -// } -// } +define_request_and_response! { + get_output_histogram (json), + GET_OUTPUT_HISTOGRAM: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_output_histogram", + "params": { + "amounts": ["20000000000"] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "histogram": [{ + "amount": 20000000000, + "recent_instances": 0, + "total_instances": 381490, + "unlocked_instances": 0 + }], + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// get_version (json), -// GetVersion, -// Request = r#""#; -// Response = -// r#""#; -// version: u32, -// release: bool, -// #[serde(skip_serializing_if = "is_zero")] -// current_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "is_zero")] -// target_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "Vec::is_empty")] -// hard_forks: Vec = default_vec(), "default_vec", -// } -// } +define_request_and_response! { + get_coinbase_tx_sum (json), + GET_COINBASE_TX_SUM: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_coinbase_tx_sum", + "params": { + "height": 1563078, + "count": 2 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "emission_amount": 9387854817320, + "emission_amount_top64": 0, + "fee_amount": 83981380000, + "fee_amount_top64": 0, + "status": "OK", + "top_hash": "", + "untrusted": false, + "wide_emission_amount": "0x889c7c06828", + "wide_fee_amount": "0x138dae29a0" + } +}"#; +} -// define_request_and_response! { -// get_fee_estimate (json), -// GetFeeEstimate, -// Request = r#""#; -// Response = r#""#; -// fee: u64, -// fees: Vec, -// #[serde(skip_serializing_if = "is_one")] -// quantization_mask: u64, -// } -// } +define_request_and_response! { + get_version (json), + GET_VERSION: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_version" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "current_height": 3195051, + "hard_forks": [{ + "height": 1, + "hf_version": 1 + },{ + "height": 1009827, + "hf_version": 2 + },{ + "height": 1141317, + "hf_version": 3 + },{ + "height": 1220516, + "hf_version": 4 + },{ + "height": 1288616, + "hf_version": 5 + },{ + "height": 1400000, + "hf_version": 6 + },{ + "height": 1546000, + "hf_version": 7 + },{ + "height": 1685555, + "hf_version": 8 + },{ + "height": 1686275, + "hf_version": 9 + },{ + "height": 1788000, + "hf_version": 10 + },{ + "height": 1788720, + "hf_version": 11 + },{ + "height": 1978433, + "hf_version": 12 + },{ + "height": 2210000, + "hf_version": 13 + },{ + "height": 2210720, + "hf_version": 14 + },{ + "height": 2688888, + "hf_version": 15 + },{ + "height": 2689608, + "hf_version": 16 + }], + "release": true, + "status": "OK", + "untrusted": false, + "version": 196621 + } +}"#; +} -// define_request_and_response! { -// get_alternate_chains (json), -// GetAlternateChains, -// Request = r#""#; -// Response = -// r#""#; -// chains: Vec, -// } -// } +define_request_and_response! { + get_fee_estimate (json), + GET_FEE_ESTIMATE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_fee_estimate" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "fee": 20000, + "fees": [20000,80000,320000,4000000], + "quantization_mask": 10000, + "status": "OK", + "top_hash": "", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// relay_tx (json), -// RelayTx, -// Request = r#""#; -// txids: Vec, -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } +define_request_and_response! { + get_alternate_chains (json), + GET_ALTERNATE_CHAINS: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_alternate_chains" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "chains": [{ + "block_hash": "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce", + "block_hashes": ["4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce"], + "difficulty": 357404825113208373, + "difficulty_top64": 0, + "height": 3167471, + "length": 1, + "main_chain_parent_block": "69b5075ea627d6ba06b1c30b7e023884eeaef5282cf58ec847dab838ddbcdd86", + "wide_difficulty": "0x4f5c1cb79e22635" + },{ + "block_hash": "33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401", + "block_hashes": ["33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401"], + "difficulty": 354736121711617293, + "difficulty_top64": 0, + "height": 3157465, + "length": 1, + "main_chain_parent_block": "fd522fcc4cefe5c8c0e5c5600981b3151772c285df3a4e38e5c4011cf466d2cb", + "wide_difficulty": "0x4ec469f8b9ee50d" + }], + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// sync_info (json), -// SyncInfo, -// Request = r#""#; -// Response = r#""#; -// height: u64, -// next_needed_pruning_seed: u32, -// overview: String, -// // FIXME: This is a `std::list` in `monerod` because...? -// peers: Vec, -// // FIXME: This is a `std::list` in `monerod` because...? -// spans: Vec, -// target_height: u64, -// } -// } +define_request_and_response! { + relay_tx (json), + RELAY_TX: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "relay_tx", + "params": { + "txids": ["9fd75c429cbe52da9a52f2ffc5fbd107fe7fd2099c0d8de274dc8a67e0c98613"] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK" + } +}"#; +} + +define_request_and_response! { + sync_info (json), + SYNC_INFO: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "sync_info" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "credits": 0, + "height": 3195157, + "next_needed_pruning_seed": 0, + "overview": "[]", + "peers": [{ + "info": { + "address": "142.93.128.65:44986", + "address_type": 1, + "avg_download": 1, + "avg_upload": 1, + "connection_id": "a5803c4c2dac49e7b201dccdef54c862", + "current_download": 2, + "current_upload": 1, + "height": 3195157, + "host": "142.93.128.65", + "incoming": true, + "ip": "142.93.128.65", + "live_time": 18, + "local_ip": false, + "localhost": false, + "peer_id": "6830e9764d3e5687", + "port": "44986", + "pruning_seed": 0, + "recv_count": 20340, + "recv_idle_time": 0, + "rpc_credits_per_hash": 0, + "rpc_port": 18089, + "send_count": 32235, + "send_idle_time": 6, + "state": "normal", + "support_flags": 1 + } + },{ + "info": { + "address": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083", + "address_type": 4, + "avg_download": 0, + "avg_upload": 0, + "connection_id": "277f7c821bc546878c8bd29977e780f5", + "current_download": 0, + "current_upload": 0, + "height": 3195157, + "host": "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion", + "incoming": false, + "ip": "", + "live_time": 2246, + "local_ip": false, + "localhost": false, + "peer_id": "0000000000000001", + "port": "", + "pruning_seed": 389, + "recv_count": 65164, + "recv_idle_time": 15, + "rpc_credits_per_hash": 0, + "rpc_port": 0, + "send_count": 99120, + "send_idle_time": 15, + "state": "normal", + "support_flags": 0 + } + }], + "status": "OK", + "target_height": 0, + "top_hash": "", + "untrusted": false + } +}"#; +} +// TODO: binary string. // define_request_and_response! { // get_txpool_backlog (json), -// GetTransactionPoolBacklog, -// Request = r#""#; +// GET_TRANSACTION_POOL_BACKLOG: &str, +// Request = +// r#"{ +// "jsonrpc": "2.0", +// "id": "0", +// "method": "get_txpool_backlog" +// }"#; // Response = -// r#""#; -// // TODO: this is a [`BinaryString`]. -// backlog: Vec, -// } +// r#"{ +// "id": "0", +// "jsonrpc": "2.0", +// "result": { +// "backlog": "...Binary...", +// "status": "OK", +// "untrusted": false +// } +// }"#; // } -// define_request_and_response! { -// get_output_distribution (json), -// /// This type is also used in the (undocumented) -// GetOutputDistribution, -// Request = r#""#; -// amounts: Vec, -// binary: bool, -// compress: bool, -// cumulative: bool, -// from_height: u64, -// to_height: u64, -// }, -// /// TODO: this request has custom serde: -// distributions: Vec, -// } -// } +define_request_and_response! { + get_output_distribution (json), + GET_OUTPUT_DISTRIBUTION: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_output_distribution", + "params": { + "amounts": [628780000], + "from_height": 1462078 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "distributions": [{ + "amount": 2628780000, + "base": 0, + "distribution": "", + "start_height": 1462078 + }], + "status": "OK" + } +}"#; +} -// define_request_and_response! { -// get_miner_data (json), -// GetMinerData, -// Request = r#""#; -// Response = -// r#""#; -// major_version: u8, -// height: u64, -// prev_id: String, -// seed_hash: String, -// difficulty: String, -// median_weight: u64, -// already_generated_coins: u64, -// } -// } +define_request_and_response! { + get_miner_data (json), + GET_MINER_DATA: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_miner_data" +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "already_generated_coins": 18186022843595960691, + "difficulty": "0x48afae42de", + "height": 2731375, + "major_version": 16, + "median_weight": 300000, + "prev_id": "78d50c5894d187c4946d54410990ca59a75017628174a9e8c7055fa4ca5c7c6d", + "seed_hash": "a6b869d50eca3a43ec26fe4c369859cf36ae37ce6ecb76457d31ffeb8a6ca8a6", + "status": "OK", + "tx_backlog": [{ + "fee": 30700000, + "id": "9868490d6bb9207fdd9cf17ca1f6c791b92ca97de0365855ea5c089f67c22208", + "weight": 1535 + },{ + "fee": 44280000, + "id": "b6000b02bbec71e18ad704bcae09fb6e5ae86d897ced14a718753e76e86c0a0a", + "weight": 2214 + }], + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// prune_blockchain (json), -// PruneBlockchain, -// #[derive(Copy)] -// Request = r#""#; -// check: bool = default_false(), "default_false", -// }, -// #[derive(Copy)] -// Response = -// r#""#; -// pruned: bool, -// pruning_seed: u32, -// } -// } +define_request_and_response! { + prune_blockchain (json), + PRUNE_BLOCKCHAIN: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "prune_blockchain", + "params": { + "check": true + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "pruned": true, + "pruning_seed": 387, + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// calc_pow (json), -// CalcPow, -// Request = r#""#; -// major_version: u8, -// height: u64, -// block_blob: String, -// seed_hash: String, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// pow_hash: String, -// } -// } +define_request_and_response! { + calc_pow (json), + CALC_POW: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "calc_pow", + "params": { + "major_version": 14, + "height": 2286447, + "block_blob": "0e0ed286da8006ecdc1aab3033cf1716c52f13f9d8ae0051615a2453643de94643b550d543becd0000000002abc78b0101ffefc68b0101fcfcf0d4b422025014bb4a1eade6622fd781cb1063381cad396efa69719b41aa28b4fce8c7ad4b5f019ce1dc670456b24a5e03c2d9058a2df10fec779e2579753b1847b74ee644f16b023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051399a1bc46a846474f5b33db24eae173a26393b976054ee14f9feefe99925233802867097564c9db7a36af5bb5ed33ab46e63092bd8d32cef121608c3258edd55562812e21cc7e3ac73045745a72f7d74581d9a0849d6f30e8b2923171253e864f4e9ddea3acb5bc755f1c4a878130a70c26297540bc0b7a57affb6b35c1f03d8dbd54ece8457531f8cba15bb74516779c01193e212050423020e45aa2c15dcb", + "seed_hash": "d432f499205150873b2572b5f033c9c6e4b7c6f3394bd2dd93822cd7085e7307" + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": "d0402d6834e26fb94a9ce38c6424d27d2069896a9b8b1ce685d79936bca6e0a8" +}"#; +} -// define_request_and_response! { -// flush_cache (json), -// FlushCache, -// #[derive(Copy)] -// Request = r#""#; -// bad_txs: bool = default_false(), "default_false", -// bad_blocks: bool = default_false(), "default_false", -// }, -// Response = -// r#""#; -// } +define_request_and_response! { + flush_cache (json), + FLUSH_CACHE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "flush_cache", + "params": { + "bad_txs": true, + "bad_blocks": true + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "status": "OK", + "untrusted": false + } +}"#; +} -// define_request_and_response! { -// add_aux_pow (json), -// AddAuxPow, -// Request = r#""#; -// blocktemplate_blob: String, -// aux_pow: Vec, -// }, -// Response = -// r#""#; -// blocktemplate_blob: String, -// blockhashing_blob: String, -// merkle_root: String, -// merkle_tree_depth: u64, -// aux_pow: Vec, -// } -// } +define_request_and_response! { + add_aux_pow (json), + ADD_AUX_POW: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "add_aux_pow", + "params": { + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "aux_pow": [{ + "id": "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8", + "hash": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a" + }] + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "aux_pow": [{ + "hash": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a", + "id": "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8" + }], + "blockhashing_blob": "1010ee97e2a106e9f8ebe8887e5b609949ac8ea6143e560ed13552b110cb009b21f0cfca1eaccf00000000b2685c1283a646bc9020c758daa443be145b7370ce5a6efacb3e614117032e2c22", + "blocktemplate_blob": "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "merkle_root": "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a", + "merkle_tree_depth": 0, + "status": "OK", + "untrusted": false + } +} +"#; +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] From f71acce20fca477aa1f950e6805ff725e7915783 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 20:28:47 -0400 Subject: [PATCH 41/93] other: add all data --- test-utils/src/rpc/data/json.rs | 68 +- test-utils/src/rpc/data/macros.rs | 29 +- test-utils/src/rpc/data/other.rs | 1170 ++++++++++++++++++++--------- 3 files changed, 869 insertions(+), 398 deletions(-) diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index b027be3d6..101d3998c 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -13,9 +13,9 @@ use crate::rpc::data::macros::define_request_and_response; define_request_and_response! { // The markdown tag for Monero RPC documentation. Not necessarily the endpoint (json). // - // Adding `(json)` after this will trigger the macro to automatically + // Adding `(json_rpc)` after this will trigger the macro to automatically // add a `serde_json` test for the request/response data. - get_block_template (json), + get_block_template (json_rpc), // The base const name: the type of the request/response. GET_BLOCK_TEMPLATE: &str, @@ -57,7 +57,7 @@ r#"{ } define_request_and_response! { - get_block_count (json), + get_block_count (json_rpc), GET_BLOCK_COUNT: &str, Request = r#"{ @@ -78,7 +78,7 @@ r#"{ } define_request_and_response! { - on_get_block_hash (json), + on_get_block_hash (json_rpc), ON_GET_BLOCK_HASH: &str, Request = r#"{ @@ -96,7 +96,7 @@ r#"{ } define_request_and_response! { - submit_block (json), + submit_block (json_rpc), SUBMIT_BLOCK: &str, Request = r#"{ @@ -117,7 +117,7 @@ r#"{ } define_request_and_response! { - generateblocks (json), + generateblocks (json_rpc), GENERATE_BLOCKS: &str, Request = r#"{ @@ -144,7 +144,7 @@ r#"{ } define_request_and_response! { - get_last_block_header (json), + get_last_block_header (json_rpc), GET_LAST_BLOCK_HEADER: &str, Request = r#"{ @@ -190,7 +190,7 @@ r#"{ } define_request_and_response! { - get_block_header_by_hash (json), + get_block_header_by_hash (json_rpc), GET_BLOCK_HEADER_BY_HASH: &str, Request = r#"{ @@ -239,7 +239,7 @@ r#"{ } define_request_and_response! { - get_block_header_by_height (json), + get_block_header_by_height (json_rpc), GET_BLOCK_HEADER_BY_HEIGHT: &str, Request = r#"{ @@ -288,7 +288,7 @@ r#"{ } define_request_and_response! { - get_block_headers_range (json), + get_block_headers_range (json_rpc), GET_BLOCK_HEADERS_RANGE: &str, Request = r#"{ @@ -361,7 +361,7 @@ r#"{ } define_request_and_response! { - get_block (json), + get_block (json_rpc), GET_BLOCK: &str, Request = r#"{ @@ -413,7 +413,7 @@ r#"{ } define_request_and_response! { - get_block (json), + get_block (json_rpc), /// This is the same as [`GET_BLOCK_REQUEST`] and /// [`GET_BLOCK_RESPONSE`] but it uses the `hash` parameter. GET_BLOCK_HASH: &str, @@ -468,7 +468,7 @@ r#"{ } define_request_and_response! { - get_connections (json), + get_connections (json_rpc), GET_CONNECTIONS: &str, Request = r#"{ @@ -541,7 +541,7 @@ r#"{ } define_request_and_response! { - get_info (json), + get_info (json_rpc), GET_INFO: &str, Request = r#"{ @@ -602,7 +602,7 @@ r#"{ } define_request_and_response! { - hard_fork_info (json), + hard_fork_info (json_rpc), HARD_FORK_INFO: &str, Request = r#"{ @@ -632,7 +632,7 @@ r#"{ } define_request_and_response! { - set_bans (json), + set_bans (json_rpc), SET_BANS: &str, Request = r#"{ @@ -659,7 +659,7 @@ r#"{ } define_request_and_response! { - set_bans (json), + set_bans (json_rpc), /// This is the same as [`SET_BANS_REQUEST`] and /// [`SET_BANS_RESPONSE`] but it uses the `ip` parameter. SET_BANS_IP: &str, @@ -688,7 +688,7 @@ r#"{ } define_request_and_response! { - get_bans (json), + get_bans (json_rpc), GET_BANS: &str, Request = r#"{ @@ -717,7 +717,7 @@ r#"{ } define_request_and_response! { - banned (json), + banned (json_rpc), BANNED: &str, Request = r#"{ @@ -741,7 +741,7 @@ r#"{ } define_request_and_response! { - flush_txpool (json), + flush_txpool (json_rpc), FLUSH_TRANSACTION_POOL: &str, Request = r#"{ @@ -763,7 +763,7 @@ r#"{ } define_request_and_response! { - get_output_histogram (json), + get_output_histogram (json_rpc), GET_OUTPUT_HISTOGRAM: &str, Request = r#"{ @@ -794,7 +794,7 @@ r#"{ } define_request_and_response! { - get_coinbase_tx_sum (json), + get_coinbase_tx_sum (json_rpc), GET_COINBASE_TX_SUM: &str, Request = r#"{ @@ -826,7 +826,7 @@ r#"{ } define_request_and_response! { - get_version (json), + get_version (json_rpc), GET_VERSION: &str, Request = r#"{ @@ -898,7 +898,7 @@ r#"{ } define_request_and_response! { - get_fee_estimate (json), + get_fee_estimate (json_rpc), GET_FEE_ESTIMATE: &str, Request = r#"{ @@ -923,7 +923,7 @@ r#"{ } define_request_and_response! { - get_alternate_chains (json), + get_alternate_chains (json_rpc), GET_ALTERNATE_CHAINS: &str, Request = r#"{ @@ -962,7 +962,7 @@ r#"{ } define_request_and_response! { - relay_tx (json), + relay_tx (json_rpc), RELAY_TX: &str, Request = r#"{ @@ -984,7 +984,7 @@ r#"{ } define_request_and_response! { - sync_info (json), + sync_info (json_rpc), SYNC_INFO: &str, Request = r#"{ @@ -1068,7 +1068,7 @@ r#"{ // TODO: binary string. // define_request_and_response! { -// get_txpool_backlog (json), +// get_txpool_backlog (json_rpc), // GET_TRANSACTION_POOL_BACKLOG: &str, // Request = // r#"{ @@ -1089,7 +1089,7 @@ r#"{ // } define_request_and_response! { - get_output_distribution (json), + get_output_distribution (json_rpc), GET_OUTPUT_DISTRIBUTION: &str, Request = r#"{ @@ -1118,7 +1118,7 @@ r#"{ } define_request_and_response! { - get_miner_data (json), + get_miner_data (json_rpc), GET_MINER_DATA: &str, Request = r#"{ @@ -1154,7 +1154,7 @@ r#"{ } define_request_and_response! { - prune_blockchain (json), + prune_blockchain (json_rpc), PRUNE_BLOCKCHAIN: &str, Request = r#"{ @@ -1179,7 +1179,7 @@ r#"{ } define_request_and_response! { - calc_pow (json), + calc_pow (json_rpc), CALC_POW: &str, Request = r#"{ @@ -1202,7 +1202,7 @@ r#"{ } define_request_and_response! { - flush_cache (json), + flush_cache (json_rpc), FLUSH_CACHE: &str, Request = r#"{ @@ -1226,7 +1226,7 @@ r#"{ } define_request_and_response! { - add_aux_pow (json), + add_aux_pow (json_rpc), ADD_AUX_POW: &str, Request = r#"{ diff --git a/test-utils/src/rpc/data/macros.rs b/test-utils/src/rpc/data/macros.rs index 893659660..25f793bc8 100644 --- a/test-utils/src/rpc/data/macros.rs +++ b/test-utils/src/rpc/data/macros.rs @@ -15,7 +15,7 @@ macro_rules! define_request_and_response { // // Adding `(json)` after this will trigger the macro to automatically // add a `serde_json` test for the request/response data. - $monero_daemon_rpc_doc_link:ident $(($json:ident))?, + $monero_daemon_rpc_doc_link:ident $(($test:ident))?, // The base `struct` name. // Attributes added here will apply to _both_ @@ -41,7 +41,7 @@ macro_rules! define_request_and_response { $( #[$request_attr] )* /// $( - #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _REQUEST>], $json)] + #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _REQUEST>], $test)] )? pub const [<$name:upper _REQUEST>]: $type = $request; @@ -55,7 +55,7 @@ macro_rules! define_request_and_response { $( #[$response_attr] )* /// $( - #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _RESPONSE>], $json)] + #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _RESPONSE>], $test)] )? pub const [<$name:upper _RESPONSE>]: $type = $response; }}; @@ -104,7 +104,7 @@ pub(super) use define_request_and_response_doc; macro_rules! json_test { ( $name:ident, // TODO - $json:ident + json_rpc ) => { concat!( "```rust\n", @@ -137,5 +137,26 @@ macro_rules! json_test { "```\n", ) }; + ( + $name:ident, + other + ) => { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::{json::*, bin::*, other::*};\n", + "use serde_json::{to_value, Value};\n", + "\n", + "let value = serde_json::from_str::(&", + stringify!($name), + ");\n", + "```\n", + ) + }; + ( + $name:ident, + $test:ident, + ) => { + "" + }; } pub(super) use json_test; diff --git a/test-utils/src/rpc/data/other.rs b/test-utils/src/rpc/data/other.rs index b739471cd..081e2a4a4 100644 --- a/test-utils/src/rpc/data/other.rs +++ b/test-utils/src/rpc/data/other.rs @@ -1,396 +1,846 @@ //! JSON data from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. //---------------------------------------------------------------------------------------------------- Import -// use crate::rpc::types::macros::define_request_and_response; +use crate::rpc::data::macros::define_request_and_response; //---------------------------------------------------------------------------------------------------- TODO -// define_request_and_response! { -// get_height, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 138..=160, -// GetHeight, -// Request {}, -// ResponseBase { -// hash: String, -// height: u64, -// } -// } +define_request_and_response! { + // `(other)` adds a JSON sanity-check test. + get_height (other), + GET_HEIGHT: &str, + Request = +r#"{}"#; + Response = +r#"{ + "hash": "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f", + "height": 3195160, + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_transactions, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 370..=451, -// GetTransactions, -// Request { -// txs_hashes: Vec, -// // FIXME: this is documented as optional but it isn't serialized as an optional -// // but it is set _somewhere_ to false in `monerod` -// // -// decode_as_json: bool = default_false(), "default_false", -// prune: bool = default_false(), "default_false", -// split: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// txs_as_hex: Vec, -// txs_as_json: Vec, -// missed_tx: Vec, -// txs: Vec, -// } -// } +define_request_and_response! { + get_transactions (other), + GET_TRANSACTIONS: &str, + Request = +r#"{ + "txs_hashes": ["d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408"] +}"#; + Response = +r#"{ + "credits": 0, + "status": "OK", + "top_hash": "", + "txs": [{ + "as_hex": "0100940102ffc7afa02501b3056ebee1b651a8da723462b4891d471b990ddc226049a0866d3029b8e2f75b70120280a0b6cef785020190dd0a200bd02b70ee707441a8863c5279b4e4d9f376dc97a140b1e5bc7d72bc5080690280c0caf384a30201d0b12b751e8f6e2e31316110fa6631bf2eb02e88ac8d778ec70d42b24ef54843fd75d90280d0dbc3f40201c498358287895f16b62a000a3f2fd8fb2e70d8e376858fb9ba7d9937d3a076e36311bb0280f092cbdd0801e5a230c6250d5835877b735c71d41587082309bf593d06a78def1b4ec57355a37838b5028080bfb59dd20d01c36c6dd3a9826658642ba4d1d586366f2782c0768c7e9fb93f32e8fdfab18c0228ed0280d0b8e1981a01bfb0158a530682f78754ab5b1b81b15891b2c7a22d4d7a929a5b51c066ffd73ac360230280f092cbdd0801f9a330a1217984cc5d31bf0e76ed4f8e3d4115f470824bc214fa84929fcede137173a60280e0bcefa75701f3910e3a8b3c031e15573f7a69db9f8dda3b3f960253099d8f844169212f2de772f6ff0280d0b8e1981a01adc1157920f2c72d6140afd4b858da3f41d07fc1655f2ebe593d32f96d5335d11711ee0280d0dbc3f40201ca8635a1373fa829f58e8f46d72c8e52aa1ce53fa1d798284ed08b44849e2e9ad79b620280a094a58d1d01faf729e5ab208fa809dd2efc6f0b74d3e7eff2a66c689a3b5c31c33c8a14e2359ac484028080e983b1de1601eced0182c8d37d77ce439824ddb3c8ff7bd60642181e183c409545c9d6f9c36683908f028080d194b57401ead50b3eefebb5303e14a5087de37ad1799a4592cf0e897eafb46d9b57257b5732949e0280a094a58d1d01d3882a1e949b2d1b6fc1fd5e44df95bae9068b090677d76b6c307188da44dd4e343cef028090cad2c60e0196c73a74a60fc4ce3a7b14d1abdf7a0c70a9efb490a9de6ec6208a846f8282d878132b028080bb8b939b4401c03dbcbfd9fb02e181d99c0093e53aceecf42bf6ccc0ec611a5093fe6f2b2738a07f0280f092cbdd0801b98d30c27f297ae4cb89fb7bb29ed11adff17db9b71d39edf736172892784897488c5d0280d0dbc3f40201da9a353d39555c27a2d620bf69136e4c665aaa19557d6fc0255cbb67ec69bf2403b63e0280b09dc2df0101f8820caab7a5e736f5445b5624837de46e9ef906cb538f7c860f688a7f7d155e19e0ac0280808d93f5d77101b544e62708eb27ff140b58c521e4a90acab5eca36f1ce9516a6318306f7d48beddbc0280a0b6cef7850201abdd0a5453712326722427f66b865e67f8cdb7188001aaacb70f1a018403d3289fcb130280c0caf384a30201a2b32b2cfb06b7022668b2cd5b8263a162511c03154b259ce91c6c97270e4c19efe4710280c0caf384a302018db32bda81bfbe5f9cdf94b20047d12a7fb5f097a83099fafdfedc03397826fb4d18d50280c0fc82aa0201b2e60b825e8c0360b4b44f4fe0a30f4d2f18c80d5bbb7bfc5ddf671f27b6867461c51d028080e983b1de1601b2eb0156dd7ab6dcb0970d4a5dbcb4e04281c1db350198e31893cec9b9d77863fedaf60280e08d84ddcb0101e0960fe3cafedb154111449c5112fc1d9e065222ed0243de3207c3e6f974941a66f177028080df9ad7949f01019815c8c5032f2c28e7e6c9f9c70f6fccdece659d8df53e54ad99a0f7fa5d831cf762028090dfc04a01b4fb123d97504b9d832f7041c4d1db1cda3b7a6d307194aff104ec6b711cced2b005e2028080dd9da41701bef1179c9a459e75a0c4cf4aff1a81f31f477bd682e28a155231da1a1aa7a25ef219910280d88ee16f01facd0f043485225a1e708aa89d71f951bc092724b53942a67a35b2315bfeac4e8af0eb0280d0dbc3f40201c4e634d6e1f3a1b232ef130d4a5417379c4fcc9d078f71899f0617cec8b1e72a1844b60280f092cbdd0801f6b9300c8c94a337fefc1c19f12dee0f2551a09ee0aaa954d1762c93fec8dadae2146c0280c0f9decfae0101ce8d09f26c90144257b5462791487fd1b017eb283268b1c86c859c4194bf1a987c62bf0280c0caf384a30201cead2bbb01653d0d7ff8a42958040814c3cbf228ebb772e03956b367bace3b684b9b7f0280a0e5b9c2910101c1b40c1796904ac003f7a6dd72b4845625e99ba12bdd003e65b2dd2760a4e460821178028080e983b1de160186e9013f55160cd9166756ea8e2c9af065dcdfb16a684e9376c909d18b65fd5306f9690280a0e5b9c2910101aeb70c4433f95ff4cdc4aa54a1ede9ae725cec06350db5d3056815486e761e381ae4d00280c0a8ca9a3a01ebe2139bd558b63ebb9f4d12aca270159ccf565e9cffaadd717ce200db779f202b106f0280d0dbc3f40201b9963568acf599958be4e72f71c3446332a39c815876c185198fa2dcf13877eba3627b0280c0f4c198af0b01bccc01408cbb5a210ad152bd6138639673a6161efd2f85be310b477ae14891870985f90280a0b6cef7850201cadd0a82e7950f5d9e62d14d0f7c6af84002ea9822cdeefabbb866b7a5776c6436636b028080d287e2bc2d01d888013a7146b96a7abc5ce5249b7981fb54250eef751964ff00530915084479b5d6ba028080d287e2bc2d018c8901d8cc1933366dceb49416b2f48fd2ce297cfd8da8baadc7f63856c46130368fca0280a0b787e90501b6d242f93a6570ad920332a354b14ad93b37c0f3815bb5fa2dcc7ca5e334256bd165320280a0e5b9c2910101a3ac0c4ed5ebf0c11285c351ddfd0bb52bd225ee0f8a319025dc416a5e2ba8e84186680280c0f9decfae0101f18709daddc52dccb6e1527ac83da15e19c2272206ee0b2ac37ac478b4dd3e6bcac5dc0280f8cce2840201b7c80bc872a1e1323d61342a2a7ac480b4b061163811497e08698057a8774493a1abe50280e0bcefa75701f38b0e532d34791916b1f56c3f008b2231de5cc62bd1ec898c62d19fb1ec716d467ae20280c0fc82aa0201d6da0b7de4dc001430473852620e13e5931960c55ab6ebeff574fbddea995cbc9d7c010280c0f4c198af0b01edca017ec6af4ece2622edaf9914cfb1cc6663639285256912d7d9d70e905120a6961c028090cad2c60e01cad43abcc63a192d53fe8269ecaf2d9ca3171c2172d85956fe44fcc1ac01efe4c610dd0280809aa6eaafe30101a92fccd2bcadfa42dcbd28d483d32abde14b377b72c4e6ef31a1f1e0ff6c2c9f452f0280a0b787e9050197d9420ce413f5321a785cd5bea4952e6c32acd0b733240a2ea2835747bb916a032da7028080d287e2bc2d01c48a01a3c4afcbbdac70524b27585c68ed1f8ea3858c1c392aeb7ada3432f3eed7cab10280f092cbdd0801abb130d362484a00ea63b2a250a8ae7cf8b904522638838a460653a3c37db1b76ff3de0280e08d84ddcb0101cc980fce5c2d8b34b3039e612adeb707f9ab397c75f76c1f0da8af92c64cd021976ff3028080dd9da4170198c217e4a7b63fd645bd27aa5afc6fae7db1e66896cece0d4b84ac76428268b5c213c30280a0b6cef7850201e3e20acab086a758c56372cf461e5339c09745ee510a785b540d68c7f62c35b8b6c5c10280a094a58d1d01ab842ac0ba57e87a3f204a56cbecca405419e4c896c817c5ced715d903a104a09735660280e0a596bb1101ade921f1ef2fe56c74320ceb1f6c52506d0b835808474b928ad6309398b42434d27f3d0280d0b8e1981a01dab515d684a324cff4b00753a9ef0868f308b8121cbc79077ede84d70cf6015ec989be0280c0ee8ed20b01f99c227da8d616ce3669517282902cdf1ef75e75a427385270d1a94197b93cf6620c15028080dd9da41701d2d0175c8494f151e585f01e80c303c84eea460b69874b773ba01d20f28a05916111a0028090dfc04a01a4f312c12a9f52a99f69e43979354446fd4e2ba5e2d5fb8aaa17cd25cdf591543149da0280d0dbc3f40201969c3510fbca0efa6d5b0c45dca32a5a91b10608594a58e5154d6a453493d4a0f10cf70280f8cce2840201ddcb0b76ca6a2df4544ea2d9634223becf72b6d6a176eae609d8a496ee7c0a45bec8240280e0bcefa7570180920e95d8d04f9f7a4b678497ded16da4caca0934fc019f582d8e1db1239920914d35028090cad2c60e01c2d43a30bbb2dcbb2b6c361dc49649a6cf733b29df5f6e7504b03a55ee707ed3db2c4e028080d287e2bc2d01c38801941b46cb00712de68cebc99945dc7b472b352c9a2e582a9217ea6d0b8c3f07590280e0a596bb1101b6eb219463e6e8aa6395eefc4e7d2d7d6484b5f684e7018fe56d3d6ddca82f4b89c5840280d0dbc3f40201db9535db1fb02f4a45c21eae26f5c40c01ab1bca304deac2fb08d2b3d9ac4f65fd10c60280a094a58d1d01948c2a413da2503eb92880f02f764c2133ed6f2951ae86e8c8c17d1e9e024ca4dc72320280c0ee8ed20b01869f22d3e106082527d6f0b052106a4850801fcd59d0b6ce61b237c2321111ed8bdf47028080d194b57401acd20b9c0e61b23698c4b4d47965a597284d409f71d7f16f4997bc04ba042d3cbe044d028090cad2c60e0194b83ac3b448f0bd45f069df6a80e49778c289edeb93b9f213039e53a828e685c270f90280a094a58d1d01bdfb2984b37167dce720d3972eaa50ba42ae1c73ce8e8bc15b5b420e55c9ae96e5ca8c028090dfc04a01abf3120595fbef2082079af5448c6d0d6491aa758576881c1839f4934fa5f6276b33810280e0a596bb1101f9ea2170a571f721540ec01ae22501138fa808045bb8d86b22b1be686b258b2cc999c5028088aca3cf02019cb60d1ffda55346c6612364a9f426a8b9942d9269bef1360f20b8f3ccf57e9996b5f70280e8eda1ba0101aff90c87588ff1bb510a30907357afbf6c3292892c2d9ff41e363889af32e70891cb9b028080d49ca7981201d65ee875df2a98544318a5f4e9aa70a799374b40cff820c132a388736b86ff6c7b7d0280c0caf384a30201dab52bbf532aa44298858b0a313d0f29953ea90efd3ac3421c674dbda79530e4a6b0060280f092cbdd0801c3ab30b0fc9f93dddc6c3e4d976e9c5e4cfee5bfd58415c96a3e7ec05a3172c29f223f0280a094a58d1d01a2812a3e0ec75af0330302c35c582d9a14c8e5f00a0bf84da22eec672c4926ca6fccb10280a094a58d1d01ca842a2b03a22e56f164bae94e43d1c353217c1a1048375731c0c47bb63216e1ef6c480280e08d84ddcb0101d68b0fb2d29505b3f25a8e36f17a2fde13bce41752ecec8c2042a7e1a7d65a0fd35cdf028090cad2c60e0199ce3afa0b62692f1b87324fd4904bf9ffd45ed169d1f5096634a3ba8602919681e5660280c0f9decfae010193ed081977c266b88f1c3cb7027c0216adb506f0e929ce650cd178b81645458c3af4c6028090cad2c60e01eec13a9cce0e6750904e5649907b0bdc6c6963f62fef41ef3932765418d933fc1cd97a0280c0ee8ed20b019ea8228d467474d1073d5c906acdec6ee799b71e16141930c9d66b7f181dbd7a6e924a028080bb8b939b4401c23d3cb4e840ad6766bb0fd6d2b81462f1e4828d2eae341ce3bd8d7ce38b036ac6fb028080e983b1de1601b9e601e3cf485fa441836d83d1f1be6d8611599eccc29f3af832b922e45ab1cd7f31d00280f092cbdd0801fc9e30fff408d7b0c5d88f662dddb5d06ff382baa06191102278e54a0030f7e3246e7c0280d88ee16f01bfcd0f96a24f27ac225278701c6b54df41c6aa511dd86ce682516fb1824ff104c572fb0280f092cbdd0801cbb430bd5f343e45c62efcd6e0f62e77ceb3c95ef945b0cff7002872ea350b5dfffef10280c0caf384a30201bfb22b14dccbba4582da488aef91b530395979f73fa83511e3b3bcb311221c6832b18d0280a0b6cef7850201c4dc0a31fb268316ab21229072833191b7a77b9832afea700a1b93f2e86fb31be9480f028090cad2c60e01cab63a313af96a15c06fcf1f1cf10b69eae50e2c1d005357df457768d384b7a35fa0bb0280d0dbc3f40201fe9835fffd89020879ec3ca02db3eadbb81b0c34a6d77f9d1031438d55fd9c33827db00280d0dbc3f40201d19b354a25ddf2641fc7e000f9306df1c6bf731bddfe9ab148713781bbfab4814ed87e0280e08d84ddcb0101ba960fec80e1dcda9fae2d63e14500354f191f287811f5503e269c9ec1ae56cef4cd110280a0b787e90501acde42b0bdfd00ab0518c8bd6938f0a6efab1b1762704e86c71a154f6d6378dd63ce840280e0bcefa75701b5900eedc2ce12618978351788e46fefe8b9b776510ec32add7423f649c613b9db853a028080e983b1de1601edeb01d68b226cd6b71903a812aa6a6f0799381cf6f70870810df560042cd732b26526028080f696a6b6880101ca18a91fd53d6370a95ca2e0700aabc3784e355afcafb25656c70d780de90e30be31028090cad2c60e0184c03adc307ee3753a20f8f687344aae487639ab12276b604b1f74789d47f1371cac6b0280c0fc82aa0201a2dc0b000aa40a7e3e44d0181aaa5cc64df6307cf119798797fbf82421e3b78a0aa2760280e8eda1ba0101daf20caa8a7c80b400f4dd189e4a00ef1074e26fcc186fed46f0d97814c464aa7561e20280c0f9decfae0101a18b092ee7d48a9fb88cefb22874e5a1ed7a1bf99cc06e93e55c7f75ca4bf38ad185a60280a094a58d1d01dff92904ef53b1415cdb435a1589c072a7e6bd8e69a31bf31153c3beb07ebf585aa838028080bfb59dd20d01916c9d21b580aed847f256b4f507f562858396a9d392adc92b7ed3585d78acf9b38b028080a2a9eae80101fab80b153098181e5fabf1056b4e88db7ce5ed875132e3b7d78ed3b6fc528edda921050280d88ee16f019fcf0fd5f4d68c9afe2e543c125963043024fe557e817c279dbd0602b158fe96ec4b6f0280e0bcefa75701d1910e44b59722c588c30a65b920fc72e0e58c5acc1535b4cad4fc889a89fccfa271510280d0dbc3f40201b78b358b066d485145ada1c39153caacf843fcd9c2f4681d226d210a9a9942109314d90280e0bcefa75701a88b0e5f100858379f9edbbfe92d8f3de825990af354e38edc3b0d246d8a62f01ab3220280d0dbc3f40201959135c6a904269f0bf29fbf8cef1f705dde8c7364ba4618ad9ee378b69a3d590af5680280e0a596bb1101edeb2140e07858aa36619a74b0944c52663b7d3818ab6bf9e66ee792cda1b6dd39842e0280c0a8ca9a3a01aae213a90a6708e741f688a32fb5f1b800800e64cfd341c0f82f8e1ca822336d70c78e0280c0fc82aa02018ddf0b5e03adc078c32952c9077fee655a65a933558c610f23245cd7416669da12611e0280f092cbdd0801aca0305b7157269b35d5068d64f8d43386e8463f2893695bc94f07b4a14f9f5c85e8c50280e0bcefa75701b18f0efd26a0ad840829429252c7e6db2ff0eb7980a8f4c4e400b3a68475f6831cc5f50280b09dc2df0101a6830c2b7555fd29e82d1f0cf6a00f2c671c94c3c683254853c045519d1c5d5dc314fb028080bb8b939b4401be3d76fcfea2c6216513382a75aedaba8932f339ed56f4aad33fb04565429c7f7fa50280c0ee8ed20b01b4a322218a5fd3a13ed0847e8165a28861fb3edc0e2c1603e95e042e2cbb0040e49ab50280c0caf384a30201ecb42b7c10020495d95b3c1ea45ce8709ff4a181771fc053911e5ec51d237d585f19610280f092cbdd0801eba3309533ea103add0540f9624cb24c5cecadf4b93ceb39aa2e541668a0dd23bf3f2f028090dfc04a01a6f3121520ad99387cf4dd779410542b3f5ed9991f0fadbb40f292be0057f4a1dfbf10028090cad2c60e019ac83a125492706ba043a4e3b927ab451c8dccb4b798f83312320dcf4d306bc45c3016028080a2a9eae80101b4ba0bd413da8f7f0aad9cd41d728b4fef20e31fbc61fc397a585c6755134406680b14028080d49ca798120192600ef342c8cf4c0e9ebf52986429add3de7f7757c3d5f7c951810b2fb5352aec620280a0b787e90501afe442797256544eb3515e6fa45b1785d65816dd179bd7f0372a561709f87fae7f95f10280a094a58d1d01dc882aacbd3e13a0b97c2a08b6b6deec5e9685b94409d30c774c85a373b252169d588f028090dfc04a0184f81225e7ded2e83d4f9f0ee64f60c9c8bce2dcb110fd2f3d66c17aafdff53fbf6bbe028080d287e2bc2d01d18901e2fd0eeb4fe9223b4610e05022fcc194240e8afe5472fceda8346cb5b66a0a5902808095e789c60401cf88036cf7317af6dc47cd0ce319a51aaaf936854287c07a24afad791a1431cbd2df5c0280c0f9decfae0101999909d038b9c30a2de009813e56ba2ba17964a24d5f195aaa5f7f2f5fefacd69893e80280a0e5b9c291010199b10cf336c49e2864d07ad3c7a0b9a19e0c17aaf0e72f9fcc980180272000fe5ba1260280a0b6cef7850201a2e20a7a870af412e8fff7eba50b2f8f3be318736996a347fa1222019be9971b6f9b81028090dfc04a01bae5127889a54246328815e9819a05eea4c93bdfffaa2a2cc9747c5d8e74a9a4a8bfe10280f8cce284020191da0b24ee29cd3f554bb618f336dd2841ba23168bf123ee88ebdb48bcbb033a67a02f0280f8cce2840201e6c30b2756e87b0b6ff35103c20c1ddb3b0502f712977fd7909a0b552f1c7dfc3e0c3c028080e983b1de16018fed01a3c245ee280ff115f7e92b16dc2c25831a2da6af5321ad76a1fbbcdd6afc780c0280e0bcefa7570183920ef957193122bb2624d28c0a3cbd4370a1cfff4e1c2e0c8bb22d4c4b47e7f0a5a60280f092cbdd0801ccab30f5440aceabe0c8c408dddf755f789fae2afbf21a64bc183f2d4218a8a792f2870280e08d84ddcb0101f8870f8e26eacca06623c8291d2b29d26ca7f476f09e89c21302d0b85e144267b2712a028080aace938c0901b0b1014c9b9fab49660c2067f4b60151427cf415aa0887447da450652f83a8027524170580b09dc2df01028c792dea94dab48160e067fb681edd6247ba375281fbcfedc03cb970f3b98e2d80b081daaf14021ab33e69737e157d23e33274c42793be06a8711670e73fa10ecebc604f87cc7180a0b6cef78502020752a6308df9466f0838c978216926cb69e113761e84446d5c8453863f06a05c808095e789c60402edc8db59ee3f13d361537cb65c6c89b218e5580a8fbaf9734e9dc71c26a996d780809ce5fd9ed40a024d3ae5019faae01f3e7ae5e978ae0f6a4344976c414b617146f7e76d9c6020c52101038c6d9ccd2f949909115d5321a26e98018b4679138a0a2c06378cf27c8fdbacfd82214a59c99d9251fa00126d353f9cf502a80d8993a6c223e3c802a40ab405555637f495903d3ba558312881e586d452e6e95826d8e128345f6c0a8f9f350e8c04ef50cf34afa3a9ec19c457143496f8cf7045ed869b581f9efa2f1d65e30f1cec5272b00e9c61a34bdd3c78cf82ae8ef4df3132f70861391069b9c255cd0875496ee376e033ee44f8a2d5605a19c88c07f354483a4144f1116143bb212f02fafb5ef7190ce928d2ab32601de56eb944b76935138ca496a345b89b54526a0265614d7932ac0b99289b75b2e18eb4f2918de8cb419bf19d916569b8f90e450bb5bc0da806b7081ecf36da21737ec52379a143632ef483633059741002ee520807713c344b38f710b904c806cf93d3f604111e6565de5f4a64e9d7ea5c24140965684e03cefcb9064ecefb46b82bb5589a6b9baac1800bed502bbc6636ad92026f57fdf2839f36726b0d69615a03b35bb182ec1ef1dcd790a259127a65208e08ea0dd55c8f8cd993c32458562638cf1fb09f77aa7f40e3fecc432f16b2396d0cb7239f7e9f5600bdface5d5f5c0285a9dca1096bd033c4ccf9ceebe063c01e0ec6e2d551189a3d70ae6214a22cd79322de7710ac834c98955d93a5aeed21f900792a98210a1a4a44a17901de0d93e20863a04903e2e77eb119b31c9971653f070ddec02bd08a577bf132323ccf763d6bfc615f1a35802877c6703b70ab7216089a3d5f9b9eacb55ba430484155cb195f736d6c094528b29d3e01032fe61c2c07da6618cf5edad594056db4f6db44adb47721616c4c70e770661634d436e6e90cbcdfdb44603948338401a6ba60c64ca6b51bbf493ecd99ccddd92e6cad20160b0b983744f90cdc4260f60b0776af7c9e664eeb5394ee1182fb6881026271db0a9aad0764782ba106074a0576239681ecae941a9ef56b7b6dda7dbf08ecafac08ab8302d52ee495e4403f2c8b9b18d53ac3863e22d4181688f2bda37943afbf04a436302498f2298b50761eb6e1f43f6354bdc79671b9e97fa239f77924683904e0cf6b1351d4535393a9352d27b007dfda7a8ae8b767e2b5241313d7b5daf20523a80dd6cc9c049da66a5d23f76c132a85d772c45b4c10f2032f58b90c862f09f625cbd18c91a37bb3fc3a413a2e081618da845910cf5b2e6bffea555e883b0bb9c5f9063380a1c33ebdb764d9ffefe9e3169de40b18eeb9bfca48296457bb0b4e29d7b2b5bc4e0021ba0a1d389f77a8e253d6db149d400f675a9330f3bcfd09c7169224a947b6b4e0745ae08cd7adea4151277a94f51f85292ba082cf28300cca233ff4966b093c9cb6abcef476026040fec2b435021aff717b8bb90a40950e010f70bb416a618dc3c5c03f590c5b7ec8e0c05b85ba94078de4817918f783022364b8aa228b5df43b38fba3060c30616f265022584ab6034ddbc832450f90047d0cf41a4af8a20fb1aa66406133a17c2e905ee28d8acd186c872859c196db0474dfaaaded2d63768143cf6b5e2e34662f7bae573a08cb15069ef881892e5a0c08b5c6c7b2e6376cd2080fb29e8d3d5aa5b853662b4f1784ba7f072130e4dc00cba3cc9278fc4213f2ce2fc82bd1ea9fa91bb17b4f7c36962c78d864eab9f30ef327039da6607962a156a05c384a4a58ddd8f51a0d4fe91f64ae7b0a5199110a66f1e676392ec8d31b20a65f7a7fcff90b37a8a3962bff0c83ee6033a70c5b0af663ca48a8f22ced255839444fc51f5b6a6c1237eda5804289aa25fc93f14d0d4a63cecfa30d213eb3b2497af4a22396cc8c0e7c8b8bb57be8878bfc7fb29c038d39cf9fe0c964ebac13354a580527b1dbaced58500a292eb5f7cdafc772860f8d5c324a7079de9e0c1382228effaf2ac0278ebedad1117c5edacf08105a3f0905bca6e59cdf9fd074e1fbb53628a3d9bf3b7be28b33747438a12ae4fed62d035aa49965912839e41d35206a87fff7f79c686584cc23f38277db146dc4bebd0e612edf8b031021e88d1134188cde11bb6ea30883e6a0b0cc38ababe1eb55bf06f26955f25c25c93f40c77f27423131a7769719b09225723dd5283192f74c8a050829fc6fdec46e708111c2bcb1f562a00e831c804fad7a1f74a9be75a7e1720a552f8bd135b6d2b8e8e2a7712b562c33ec9e1030224c0cfc7a6f3b5dc2e6bd02a98d25c73b3a168daa768b70b8aef5bd12f362a89725571c4a82a06d55b22e071a30e15b006a8ea03012d2bb9a7c6a90b7fbd012efbb1c4fa4f35b2a2a2e4f0d54c4e125084208e096cdee54b2763c0f6fbd1f4f341d8829a2d551bfb889e30ca3af81b2fbecc10d0f106845b73475ec5033baab1bad777c23fa55704ba14e01d228597339f3ba6b6caaaa53a8c701b513c4272ff617494277baf9cdea37870ce0d3c03203f93a4ce87b63c577a9d41a7ccbf1c9d0bcdecd8b72a71e9b911b014e172ff24bc63ba064f6fde212df25c40e88257c92f8bc35c4139f058748b00fa511755d9ae5a7b2b2bdf7cdca13b3171ca85a0a1f75c3cae1983c7da7c748076a1c0d2669e7b2e6b71913677af2bc1a21f1c7c436509514320e248015798a050b2cbb1b076cd5eb72cc336d2aad290f959dc6636a050b0811933b01ea25ec006688da1b7e8b4bb963fbe8bc06b5f716a96b15e22be7f8b99b8feba54ac74f080b799ea3a7599daa723067bf837ca32d8921b7584b17d708971fb21cbb8a2808c7da811cff4967363fe7748f0f8378b7c14dd7a5bd10055c78ccb8b8e8b88206317f35dcad0cb2951e5eb7697d484c63764483f7bbc0ad3ca41630fc76a44006e310249d8a73d7f9ca7ce648b5602b331afb584a3e1db1ec9f2a1fc1d030650557c7dbc62008235911677709dea7b60c8d400c9da16b4b0a988b25e5cf26c00c3ef02812def049bb149ea635280e5b339db1035b7275e154b587cc50464a4c0bfd15c79f54faa10fbe571b73cf1aa4a20746b11c80c8c95899521fe5f0bb3104b0a050c55a79511e202fee30c005339694b18f4e18ab5e36ea21952a01864a0e067d9f19362e009a21c6c1a798f7c1325edd95e98fd1f9cb544909fdf9d076070d1233e183fb6d46a46fbc6e10452ef4c45fa0b88a84962ad6e91cbcc52bc000b12a82e93ae5998b20ee9000a8ef68ec8a44862cc108869fd388142692be6b0657e3fe79eff0e8b72f63aeec5874acf5fb0bfc9fa22645ed6ecaaf186eca690ecdf8a71b8f4789ac41b1f4f7539e04c53dd05e67488ea5849bf069d4eefc040273f6018819fdcbaa170c2ce078062b7bbe951d2214b077c4c836db85e1b138059c382ab408a65a3b94132136945cc4a3974c0f96d88eaa1b07cce02dce04ea0126e6210a9543129bb8296839949f6c3867243d4b0e1ff32be58c188ba905d40e32c53f7871920967210de94f71709f73e826036b4e3fa3e42c23f2912f4ea50557dff78aeb34cb35444965614812cbe14068a62be075fce6bf3310b9e8b12e0dd8379104360f728d47a327c172257134e2c0e7c32e01321f4d636f9047bd750e7993eeda7d39fc16f29696b1becee4d8026e967f8149935b947fce8517b2ce02b7831a232f3a29010129c49494ed2b84c7f881b7e4b02a00ebabf5a36023c404002d6cb88cee76c8ce97b03143ca867359d7e118d54e053b02c94998e6fd8409f8d46fc1741a2e56aebb1e7dab7ca3296a2566263d9be2f4bbef4872a49ee1082cbaf86e21b0c232c4182fc660f0c0b6aaeb0393750e553bc406e2a27842bd033da45a562ed1998ef9bd83e35ed813bef00a3e6147cb363bee63c543ba5e770b043dbacc155214a2496f91879bbc9170a2a513d7b48fad40c8c2d96f951e3a0932f6d12956789198430b352803852aa9726163fbe839979b33f8dbf7f76cd50755c1ce0c40a072aeec35057d06abaf59e878000b1d796e51908bfbf23b13900dcb30f9bd52b52994e7245a7017653a404a70d1c444b8c613ff10a2b057c02d062c5faf13cdc4445809fb6e096923cdbbdca18f59318ff86c7e449f596050b404d3cde0338dfdf9b1389178b1d4c70eefa2bbd76fefc1ee1f1688ef507821e40ae31d8d8e673d183b54563e2cbd27e0e042f61b046877d37a68c1b5784830690f2dd4ebbbd2dbdb35800b9e0ba8ea985fa106dd2ce8493e845586716c538ee9008b88a7c482f3c00c14c08468230d40cdc040e145282c4d61985cb5800306e305146204f63e96ad194bcdf1338ab8480341b6fbccf18fc32145f84bece4069c09e41096e94c24fa4f0db988e860a3bff3604143f2b17e8c219f28189e4cd49a0e506fe62dc419299bcd78c6ccb107f63eb31b4bd8ea1e2fed10e3ac17341d3505019e2376b01f7a7fcea3db110fb090c681c866ac86f13e6f8d44a32861e0580def063736b5c771b2b3b9067045867b4393f3eb2a4610bd0216e29906aaac370986451c6bf78264dda7e7a5fcbcf7bd6e024ff6003c6db780d89b97765cee8d0ff3ff25d94d4b4b919f722b26a6903a017daa62af387843087680c57952de06064de05b662af87be49b6e34cf0991cec7be3396e2eec9678ba259bd8de1c192014d02928f9113488215658df4078ed661fa4e79e58decaeb0ee5a00488b094b0b77f083b2b7844f481e7788ffe8004b96ccdf853532bfd9632a8a652c2d97d10173c90864fbb6facf47fae415df4acc0b099140a657b35d083d74dbdfbf107303e74c64471bed4b2199f2babcb4e1fc593d6f309e21f85e68ffd9904731559d0f2b673b36d3984e5d66d897dfa17d601edef3ed78cb70dc5115d4ae240c203e031263f0cf1e98075bac0361fde24cbcb852b8055d53ae01d61a0a1e1ba423d00833747e7364df7ebfd1f84598d801c249e1805279dc37d39fc7f7e27b067e4e0287aec432ed49e4d701a0ff377e88179968430d110cb20476ed4c6bf1624d1907ef24406d3295fcacde2a102cc85f4f3d0cb87a8fae7535a06e442833e58cfc04242ff85fb654d05f9874c0a6756f542db4e9d8b0366191fbb8b09a1bbcb6af04c069978417ca80d92f442b7dbd092f74e1268aa73b54e4b64e84543449ecd30b5ea392a1669a5f441d7208925e91c75df611cd26042630c6b98f160b8c0156048108d5465b71bbc54d31a9f90e34428d97590a427e1ae618d4a35fc1022d4e007c6108dcb1672b88d43ae4d886a5adcc26faf56bc5e5a0b08342fb88263fd80940d1edf794c6ad6d339b974e164b38439e11b4fa87cc793b080b4f8bf0eb56043f79ed3911da21092475fcf8320b55b9f558f194c6c8121b2e696039340d97057be2583726d762b5ae4327e5286a2d8c14ddbe0027c75aacbf7e9de13037390df7d72e13b46bc06bad0363b070e0174d034120d7fa7b4550e7dc28f7f0241f059ae266fc13dccd1d07f744208a7d6a2e565b6613d46e4550f79ef3209c46a805b97284df558719e131f44e419e690f4fc28ee4862b9d1f8f7e1a164ac18141076087693e70ac76a10f7851530d4cbc65def90d5544671ad64249569c3abf0200d09be3c63efaa7cb723b39ccffc9b3a3ba0c8426847123d2a881efbd4937a40cb3e8011c70ba427f80b3dc91a608086f8b61f8bd86fcb482ea388299a7cfbd00a3ddfadb4b6d0e51c1369276c25889a9f3592900b6502d9af1c732e1fb7db307d71e45deb1553ba1568d0480ea9e132b52564da6ac5c56eff823e7f37976cd075ce8f7a78aaef1b87f7437a0b84035677f266f7d0596e493101fec3e14fcf80b22322454587b51fda0636231c07d4e63e007f1b89137d8a37b03bf00f3a7c10169f757d9a74b7bffba797c746e3845decc3d0559d7cf6f08f3bd67dac5f33109b212582dc7df5d561ad63dddc794f2aea4e493db1a73c702d258c9b922c35d04c47f88f87c54c821a29f04abd91a079ce8cef252a21dc72d409fd1618c9be709af029ba98b0140e74666fcb01bced4f88ab68e6b63b8ed6febc0905d22cb2200493c071ce136833697406f8a04e77b21747dda997046cf3c7080096fe481790d77cf5904e7f7128ed95a6e576d109fdf10eb0c888db35a4a685b62253987b70fb1538e6c0932889460fa31c60d123266b7bcb828f846a35b2851127679b05f05a75266529c343a6075e54c455e02b2c83e6f7bf1ae23326506a5f532472d780815c5af425f7d8b543a8f014966e0538f48ca84d181695381a09701eb65c9ae084bf2a4dc84f1b2071be32be25d5f4fcdc59668fd800496ef7eb6dddf867ab908e543cb51f0451706cce4be6b9f68a537a79ea88e17fcd78965b3da68c0d9d30623a2a9e275e1c320f59e118e09c02eee527167bc06f7693e7b584e3b25ecc1093d46b80a1cacced87c2b32e2f90c5bbb9cd1b701aae69a04b16d535fac6eab0d091790fc5fdfa8a8842bfcb62dbf963cbf62c4afb4c468be98c770e6078b8c0a8cfcbae43dcfff17d3c6d587c3e4309fd39c66acd14781fea66fc57278b02302c0fa386280e67acff19955b6a428b0e22ceb1e54e913a37cd19eb6e9d2268a039f2b5fdda7d5804db79385f0e50082b128c952f8dfdedc4411d0675d95127f0bfc01710a869b10d7a8b9e632dad944062567439e6d192fb09329d058e87ecd0aa8981328f541e87ed02cfe4031f2d3a046ff517a2a80486b04ade31a647aec0884fb96ed753ffc47892431c6e6f08fd1c633a1a44e882d3d8b92c567e0fb8305327a354851464ca0f18d89c6ee2a91a4afef0c55883acf8fcb68c2c3b7402e005d8affc19c13f1f26fee0698dff181ab22cb84a2b31e0a6a81dc5d02e60a3c07090397ae58a985526b2ad6ee5725e82328062b68566b4871705ce3b9856e550d068c20fd9aaeb27740c07aad53d79fc20e46e40e7103e2d69626ee64b6aa600f6f1a86f37948ff4990d88f43c34994e2fe586cb779997be323da53329c10480aeb08fe440e9e4b979171371c73b94da9f928a3f6c8f6792f782f3d6432b86d06f54557327fef31fd6ae0a3f6d2f16c9ad947d132e14def33fa24cb4565370e0832fa50f5f5f93c9f3d65776cc22608b68a4f3719e9be47a19432991e4a2c49089c0ea20e7f7c73feaa47970da424d8543d80d622e2f2be9f4c65cc39dc369009a9d41a52bdea7cc0e8e04da87a633fd4f814fda1b646121a469ba0b5b8006d0e9118761d97b5d1856e2d690f27a81c42b176df853d07cf4a66ee83c9eb24ac0a382f5143a10a33ec3ddf17dcd8a8303fac8f279d31b4d04d74bd8804cefbb400c86174ad444e43ed33ee1e1e73f660b9814d5ca3cb1d650f1978a825a617bb05f84eab3b9b8359b991e1084cf4e8179ecb67f92398638e31227ff63427b67f0f232b454a341d85d4b56e31135c9035e231f7d9318ca12b5ab524f87bb0ca9b04b80effed202897ab016d5acc054c4fe62a5f0192f136cf2cd714998a4b164b0c2cdbace52243fdc9ea879b0d247d4fe8bd80481fad6b325cb5f2cfa2534dec0e47d41b6b99352e6e5faccb5ee28ca2fe96e04f9c83a0461ba34cfb499d864f05dc734b6c8f51cc0c994290b2a868cb8312df39fb6d457a81e62d872c65d4f3007094be42663bca3d64ebbcc8401158fce4f5a38a49c20f029c338f126451820459866e77c6984a467aad571cc7452392a9cb9f8e65099fff2f2acd170a833e01ed3d22a683356ee42dcbe6bab6d05d3edda2d40e9ba53884d430c2e0cd87c0067dc8cb68c868bd9f29db1dd73703c139ffc15e4f7264e727c70560ae02da100871f30e8a7c1275805f621752d73aafecddc2a7808b6c2ecbb8d0134a644bb603f30f8d18b3fc5efaa7f206ce180bfb14f5dbd3b0115145a227113eeaf1c1ec04244227b931388e72960833a40baa4319c5cf74aa94f7e3233e1d09f0a4f74409999684ad1cc836ac74563c85b164664dfab08ea084b25e2cbd7e7b94a781a10fcd455ee38b65126dcc52016127fd195c80b05660ed931e128b0cb92868955c0d032ada9fb951210a5442d21c1718ebc4702ad4a57967e15a63ffb05e1e072a0c41ebdf1e7205373eeaf4587f695de887fa3a8c96b8deb99e040fa1fc4dc2a402a017891943d734ae2f3798b22b1269d3d9f6d65581b9c637a6896a4fb554810bbd3db5c5737391a74150b43413b2e3824490b7911cbeb845147f1a8521620b0dd31306f13a9754a01bcdbd18bfdeade06b0ec97f48df56c45d3670a1fe18d00ef13e613c8a77aeb40401a814b377137cf44f29cb2cb94186ad1161ecb05a7c07837a5ab3474e57990cff2ab16b4d99f62e646da28e8bb712a5b561cf0e25be039c3e08583c8ebc3dd2fdb8fdc6e135ecc7851c73218a70b75e697cc84ea50504b9c34a33ed52f87230b9d192a940f3b7bb6d45b58dbf52f0afeb8dac85c77b06bdf9b70a10cb81c50055c9d8cf7e3a5c4b7dfae55beabcb3e8a8a1cb822d8d0bf6c01e32056929f853021eae6c97fdb0c5031df6b2e7c57f1318866769a9cc09c38ed62d8bf4663334c0df67c47236ed73f6ce7f54e0ada9270398c1aa558d0f993b0d25d97aea77b1635ee4832362cd590bae5fc1549402ddcd42b15efc930111a01535c0242116078d6d2d53b8612d378c4370e90d0d01b01bd7da591bec07981652a98485d8ed5c8f3def2bdac7d992ee5fc6a1ec7bd36940e1bc58c7050451248fc3ee6069e6b1b0d3ef122c6ef2a9b99aa0f145fb43341c58dbb472130b51730c956273a3ef6df9e000f6a87c2bacdefcdb5daef28b6170f61bc3a9c101f439755c86e6b85ee06a7a60688b3843eb359cd4acd9221a2ee131e2fd2e190652e5c47c0b98c41010eb99a991ec48a5de99cc8f403d6d76f8307d6657c1e007ebd64eec7bbd0d4f1ba2db7bb0efe27c7828f053e00def775943ab01a7e33d0fffcfe6f9a7285237f2c381b638758e373f8ceac672190664bb25fb5d355c240bd1773d61bda7f7ef1f4261b80ff5058ec6f7e024ab9459b1103815624b81f80c39db2f6fecb72de452b11636b0f71b16cb55f883d93bebb94328f13ef1ab6d0df449e32d27884f5139af584035547dace65ee25ba05cc461e74760d4468af90dcaa982e52cb902e2b84b3324019da575601ca54e91655913892e703257deaa01d14fd8459ff780c724161ba4d4280b70a5039dcfb5d775560714009724cb0d0b7e178c71e777b896bcfcde7d4c9c3dc6ab819d74a1a1fda8486448b1ad79be02fb134ea93a8600f1bc2a42e68d0213ab461a07cef3ad3965bc130beb76bab409102f82bf6c4cd626f6df3388e17b87584310c50832cde3191f6557f0014bdc0a68d924119e43111043bc6f26d16a5f2612dae6ab24984e2d87a71d93d5f4670dba2176d4f16633407bf7c10b51b6842dfdbc6fe3eaa4b6a12f0550700ece070ca382dec3b587e0e1fc317a48a83754d15aaf9a6971b8cb641fd8b32846d89002e6301700a0e7056e8002d8f269d29ebaf64f4493b1f1e676fc78e673067fb00625df15dc0490235b386ee14e55b335f3bc6dcedd7d3a80fd3a6e9bc2ccf3af0d89be71b5ca92bd7a9b97b9ff8976f75702419aa5bf9be34600496ca1bfa8ad0400602a23579365574252434f2bcd7efb360b0e8a495e8f7e78923b6fbf2207049e9179f0d4d7d6b4a4a10ca10f0ef4dd6cb5a74f7574e832044d6120fbc1580a68eddfbc65ab300bed960a6f24a102dc36b72937a8be4385daf5946e81ccde0619251babbff17e5685217a134d22f6130d0322483b3475227ffd27adc73ca202a6debfa37e5731747f4449ac70a33684f460eede65918c6d89acf4b50fd28d040ffbd436a944d3be0210606bfc2301e7ac66d462dba29a0489eb55af714a760e5302592cccc726e535b945ceb6126eb84e31f0f140ff54df8be0fa3a22f418036ad996787a5616a97a42049ebce351dc11857cab3dc914ef26833b0e75653004a8cafea099fb0750135255c41ef43e2f29c75714e2f0be2545e7c109b70c43004a471daa85b47befc65907d033f133b2f3ac2ad568df630ee80506610b8dc9052d442668dc06b13ea76ab1ab7b34870341d660af5d3007c21bb72512e4f8a60d8916a037b93f9e15ac9e4a6a1246d73ebb40e5fdd5a0d6dc0cf175023b891301f69fe5a3ca6f12cb8312d16333de1cd3ebb99339ab18c0715bfcd35b8365b407ad759e2c591d8270ad335381573e27ec18af7ca157b4a2bbca921db083d9b0009dc332a79dde14354a8c18bce76a1bfc1a25a1e702ccaa0feb521ee9279b8a01ceab6e237bbe4128b23cb53b1e5185f3266e20670a307ea0cfb5377025e0bb0790d48f1636c8b836c1a1f69ad61265f19057197e86cd526da6ddb94fd1ece80b60852f27ef2ce56ccb5a32d8cab6d16be06f380dfde3602ea4c1ae927173b2001ff0d9e29bc66b2b2a20c3e3ac174fcba187aacab0876c1356d30d4021e6dd0048c3bdfbf254108bb09d3ca9f2be423a92408bca52fbcd68f972c46fc8d20e0350d12c2f2d6c7da85e96bcec3ce61119793d44a210f81ece859fef6360ae3b0e1af0634fc141a8b50b3b383fb264e8a4fb84ea06db6becbf5e140edf66ee190da8968da579eb349fedea45e4c252a79570501278bab5fa984d7b1179d7c2460faa7beafee153bbae0a591701632aa94839528d3ef50cf809c1f7209b9e5c99010eaff7f921c45b6546358ee7a90948e3c710cd3e1796860839a345516fdf4f07c415029627abe1273a1f510c36a662562d18169b23305b4efadfefbfbb41a400ab533e61c14cafa49bc5d2818058ee4f3e1aeb329e150820d1de1f1eaaad31051a6dfdd3a1d5cec7b16bc0ea2c649d409917faa42138b1f824b4d534a050be0a99ea6772daf0b2e58623cc7a250ef37599bd556508f08886e663ef0917ecd3077072c3268ea5b9b89cbb6b761ee9f9c4765d8b267d9eb19728a28ce67a42ed0cad142b5dc0fc5313853860ec3f0ee2bc3d47cbe12dbe9633db809967d5b8bf0e45574eac657059530c30aeeade1e4f858a4a6e79d6e441b4af0127a13340d908d48cfec849ee93d53b1564231f048d34885e791a9d40c61a7b00f12f6f72a5050bcaabcc98480170ea6e20bef6b5c6f504c808108454fe2f3c275bf8f89a5e0a3304a7c4787e6d4fbe569930f7cfd38ab7d1d2ebd599bbb411950cec3e53b90cefb82234990d353c71ce4c21ef674a1c4070f71c90e1ea7edf35f5a421118f01b49a92ea97720e2d4df6b5885c181002656629a90eaa1904fe1c379b8291480ca15d0dc2b65a20c22f1e01d612d21ecb5738e5ebfc578a4a65066ee6e913e3030d3fdfb0168fd75022492728ee82869deb9ff2827f4e10759ecddb20f67e9808e707257a74d3dc0a6068f264066f95c9f772a3dcec0b4f0a327e3745517ad60ccbc5392890d2479b724d068fcdb83607e02291c06e1a5a1dac7604889cce2500f418da2f7080a7e9a1bdf28b87028a2bbb0c14f059f10f46d46716eac2cdfc06676cbec91b8c2c0f7c9bea7e27fa5048662398b23a9b488a49e1d3330c04e60179a4492c8b836780899899d2af17e6119a94d54a890ce8c0b550e87fd54cba0821fa7c48f6e09a60dcbddf853f82b47195aa44a5ceae14a9257296acd711c8073ff3345befca5d3ebf64901b283df96395fe9785d7090176bfe5a9f13ceae701c6c93af0e13d949bc3c7e9b06674a73e7affc508302258a27fb34569c3742201c0721aef282a31c69a5d98a67ac5c3d920d50c089896f7f8c8c237a81f803f0444f417246d695e89a3a523b62a3cd2203d42607cac7c7782dec1f9edbb806c0a7a37d1a969082a126bc726151a50233456a07d374399e74aaa8cc66821511d092615950d302e815cfcc021e1250cdea20fd9e1e4b5e88280d6e4283b918e780d12cbba59ef2ed2ce86135a48fba6c0dc2bf2efee190d9a3f9aa22a622b1953058f2bb3a371637d13e045d54e7eb54c0d25851f49283d7d34e9785d2d5c3f70086c48a8325a2083bdf5b3531fcc697cc0c9f63892a866c84585d673a2a63fd60e77995bfb0c0a44a4b63c0ff67e813027d3e84cddd393a0f4e6bc95525c5ae20eed9d0cea4a12aa748eb5209cfd75990b055f1ad0472f9f7599f569a8743a720755aa11555df4bb2e725fa93bc5dea603a964e8dc9fb1742e81825022866fc50a6b2a19b6a234a38ee27a74f2f5832b294143ca7ff8d07fd7d4e01f479e9792058871d90ee3aaa3329e82cebe41dff5e6d00a36268a7965466b80c6510ac1350cee797e1d6737f6aaff155266d2a2d611b2124affed1ac73a6a06515627b2230ce0d7fed33ecbde511f4d472cbc556cc8d9c5640e67657035112976b626847a0a4ea5fdd14d4a3eed57f0dfbe153393d8bd28c8b4f9e62940e8379790393fa20c617050c780a7d870193b4611bd7a12d26947a3cf4605e225da8b1646a76984015a5e317016a4d8301eaeec0db3ae0daa719182e2f4479154dbcccfcce1f365099de6c91934c395ce82abba8062a51d7773b418330921766cd3d275c689098e06039698db6f09accb292e7eb79e7a022d4257bb2f9ed993c519860919bc229a06ad88954c9ebf7f5b9fe95cf56e8181cb9175dac06be0be70fd28df20cdb4600ef0869668c645c9ea01360fdae7c922cb3d2b3583ae1de5ae7d899a83ff2bb00d7365c782a0fccfcba7f87bb29416469bb051f9b0755123e0f2fa76dc7644b70e452f49a84bc372b384c843b8161b7f9b63699adcadd5cb2b33b36c7eb3e1b00f25218bc16447968b939016242fceaebd796c17a24d1b9870991a9c3ae90e380302b7bb320adacc08cfb9249d29cd9275c52476dac6a7e9870ee3776cbc3352036f9c8f681d44856c6c5f90b7cde0877472ddd48719c449f59dca1f49442f7505e4809c6d323b37530ecccf3e41e19822f53d64dc90efb113405ee88799c37f0a342293b5bfc019a9057138326de6107b5613554dffc737aed7237fb16cd77e09f581d12220ac930c6ca279efd1d07a92125fb2606ec3ec35351987a15fc72806cfb3cb66fce8dcfabee5c1e586bf0f802fa12ae5ad5a708e3a5d54e1926dbd0202bf1150f1bb612b9a4590b5b520b86a90860ec3d9c2184f9975ced15ae1300882d9918021b43a1184ba88ddd7091539fe5a7017b8708d0f5c916f9c42de5103f8116863864b508f5880ca60b7492385c16a02b6ceb64d257a4838873b85d2041517c5c7c4508e4d5a5faa72729d73af0361e11828eeca992b8f20d903a5ef065976a9f322e34bd4b3984bb09e18be40e77e833c8c1a2e80093227d3f40d4a067f5e3aee9fce9bd234bb6ff4d0c34fc060d23e86b1f5a6d8d052e53e913182052a2d9c5e97bb0e0a51bb2fafbe7346bacfcbadb00ce2ba129f29d41a11f7d105cf19bb60b5f5b0dfd6a894698ef7f56a02d69cc03eb62a56563d3a77e3ac2302", + "as_json": "", + "block_height": 993442, + "block_timestamp": 1457749396, + "confirmations": 2201720, + "double_spend_seen": false, + "in_pool": false, + "output_indices": [198769,418598,176616,50345,509], + "prunable_as_hex": "", + "prunable_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "pruned_as_hex": "", + "tx_hash": "d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408" + }], + "txs_as_hex": ["0100940102ffc7afa02501b3056ebee1b651a8da723462b4891d471b990ddc226049a0866d3029b8e2f75b70120280a0b6cef785020190dd0a200bd02b70ee707441a8863c5279b4e4d9f376dc97a140b1e5bc7d72bc5080690280c0caf384a30201d0b12b751e8f6e2e31316110fa6631bf2eb02e88ac8d778ec70d42b24ef54843fd75d90280d0dbc3f40201c498358287895f16b62a000a3f2fd8fb2e70d8e376858fb9ba7d9937d3a076e36311bb0280f092cbdd0801e5a230c6250d5835877b735c71d41587082309bf593d06a78def1b4ec57355a37838b5028080bfb59dd20d01c36c6dd3a9826658642ba4d1d586366f2782c0768c7e9fb93f32e8fdfab18c0228ed0280d0b8e1981a01bfb0158a530682f78754ab5b1b81b15891b2c7a22d4d7a929a5b51c066ffd73ac360230280f092cbdd0801f9a330a1217984cc5d31bf0e76ed4f8e3d4115f470824bc214fa84929fcede137173a60280e0bcefa75701f3910e3a8b3c031e15573f7a69db9f8dda3b3f960253099d8f844169212f2de772f6ff0280d0b8e1981a01adc1157920f2c72d6140afd4b858da3f41d07fc1655f2ebe593d32f96d5335d11711ee0280d0dbc3f40201ca8635a1373fa829f58e8f46d72c8e52aa1ce53fa1d798284ed08b44849e2e9ad79b620280a094a58d1d01faf729e5ab208fa809dd2efc6f0b74d3e7eff2a66c689a3b5c31c33c8a14e2359ac484028080e983b1de1601eced0182c8d37d77ce439824ddb3c8ff7bd60642181e183c409545c9d6f9c36683908f028080d194b57401ead50b3eefebb5303e14a5087de37ad1799a4592cf0e897eafb46d9b57257b5732949e0280a094a58d1d01d3882a1e949b2d1b6fc1fd5e44df95bae9068b090677d76b6c307188da44dd4e343cef028090cad2c60e0196c73a74a60fc4ce3a7b14d1abdf7a0c70a9efb490a9de6ec6208a846f8282d878132b028080bb8b939b4401c03dbcbfd9fb02e181d99c0093e53aceecf42bf6ccc0ec611a5093fe6f2b2738a07f0280f092cbdd0801b98d30c27f297ae4cb89fb7bb29ed11adff17db9b71d39edf736172892784897488c5d0280d0dbc3f40201da9a353d39555c27a2d620bf69136e4c665aaa19557d6fc0255cbb67ec69bf2403b63e0280b09dc2df0101f8820caab7a5e736f5445b5624837de46e9ef906cb538f7c860f688a7f7d155e19e0ac0280808d93f5d77101b544e62708eb27ff140b58c521e4a90acab5eca36f1ce9516a6318306f7d48beddbc0280a0b6cef7850201abdd0a5453712326722427f66b865e67f8cdb7188001aaacb70f1a018403d3289fcb130280c0caf384a30201a2b32b2cfb06b7022668b2cd5b8263a162511c03154b259ce91c6c97270e4c19efe4710280c0caf384a302018db32bda81bfbe5f9cdf94b20047d12a7fb5f097a83099fafdfedc03397826fb4d18d50280c0fc82aa0201b2e60b825e8c0360b4b44f4fe0a30f4d2f18c80d5bbb7bfc5ddf671f27b6867461c51d028080e983b1de1601b2eb0156dd7ab6dcb0970d4a5dbcb4e04281c1db350198e31893cec9b9d77863fedaf60280e08d84ddcb0101e0960fe3cafedb154111449c5112fc1d9e065222ed0243de3207c3e6f974941a66f177028080df9ad7949f01019815c8c5032f2c28e7e6c9f9c70f6fccdece659d8df53e54ad99a0f7fa5d831cf762028090dfc04a01b4fb123d97504b9d832f7041c4d1db1cda3b7a6d307194aff104ec6b711cced2b005e2028080dd9da41701bef1179c9a459e75a0c4cf4aff1a81f31f477bd682e28a155231da1a1aa7a25ef219910280d88ee16f01facd0f043485225a1e708aa89d71f951bc092724b53942a67a35b2315bfeac4e8af0eb0280d0dbc3f40201c4e634d6e1f3a1b232ef130d4a5417379c4fcc9d078f71899f0617cec8b1e72a1844b60280f092cbdd0801f6b9300c8c94a337fefc1c19f12dee0f2551a09ee0aaa954d1762c93fec8dadae2146c0280c0f9decfae0101ce8d09f26c90144257b5462791487fd1b017eb283268b1c86c859c4194bf1a987c62bf0280c0caf384a30201cead2bbb01653d0d7ff8a42958040814c3cbf228ebb772e03956b367bace3b684b9b7f0280a0e5b9c2910101c1b40c1796904ac003f7a6dd72b4845625e99ba12bdd003e65b2dd2760a4e460821178028080e983b1de160186e9013f55160cd9166756ea8e2c9af065dcdfb16a684e9376c909d18b65fd5306f9690280a0e5b9c2910101aeb70c4433f95ff4cdc4aa54a1ede9ae725cec06350db5d3056815486e761e381ae4d00280c0a8ca9a3a01ebe2139bd558b63ebb9f4d12aca270159ccf565e9cffaadd717ce200db779f202b106f0280d0dbc3f40201b9963568acf599958be4e72f71c3446332a39c815876c185198fa2dcf13877eba3627b0280c0f4c198af0b01bccc01408cbb5a210ad152bd6138639673a6161efd2f85be310b477ae14891870985f90280a0b6cef7850201cadd0a82e7950f5d9e62d14d0f7c6af84002ea9822cdeefabbb866b7a5776c6436636b028080d287e2bc2d01d888013a7146b96a7abc5ce5249b7981fb54250eef751964ff00530915084479b5d6ba028080d287e2bc2d018c8901d8cc1933366dceb49416b2f48fd2ce297cfd8da8baadc7f63856c46130368fca0280a0b787e90501b6d242f93a6570ad920332a354b14ad93b37c0f3815bb5fa2dcc7ca5e334256bd165320280a0e5b9c2910101a3ac0c4ed5ebf0c11285c351ddfd0bb52bd225ee0f8a319025dc416a5e2ba8e84186680280c0f9decfae0101f18709daddc52dccb6e1527ac83da15e19c2272206ee0b2ac37ac478b4dd3e6bcac5dc0280f8cce2840201b7c80bc872a1e1323d61342a2a7ac480b4b061163811497e08698057a8774493a1abe50280e0bcefa75701f38b0e532d34791916b1f56c3f008b2231de5cc62bd1ec898c62d19fb1ec716d467ae20280c0fc82aa0201d6da0b7de4dc001430473852620e13e5931960c55ab6ebeff574fbddea995cbc9d7c010280c0f4c198af0b01edca017ec6af4ece2622edaf9914cfb1cc6663639285256912d7d9d70e905120a6961c028090cad2c60e01cad43abcc63a192d53fe8269ecaf2d9ca3171c2172d85956fe44fcc1ac01efe4c610dd0280809aa6eaafe30101a92fccd2bcadfa42dcbd28d483d32abde14b377b72c4e6ef31a1f1e0ff6c2c9f452f0280a0b787e9050197d9420ce413f5321a785cd5bea4952e6c32acd0b733240a2ea2835747bb916a032da7028080d287e2bc2d01c48a01a3c4afcbbdac70524b27585c68ed1f8ea3858c1c392aeb7ada3432f3eed7cab10280f092cbdd0801abb130d362484a00ea63b2a250a8ae7cf8b904522638838a460653a3c37db1b76ff3de0280e08d84ddcb0101cc980fce5c2d8b34b3039e612adeb707f9ab397c75f76c1f0da8af92c64cd021976ff3028080dd9da4170198c217e4a7b63fd645bd27aa5afc6fae7db1e66896cece0d4b84ac76428268b5c213c30280a0b6cef7850201e3e20acab086a758c56372cf461e5339c09745ee510a785b540d68c7f62c35b8b6c5c10280a094a58d1d01ab842ac0ba57e87a3f204a56cbecca405419e4c896c817c5ced715d903a104a09735660280e0a596bb1101ade921f1ef2fe56c74320ceb1f6c52506d0b835808474b928ad6309398b42434d27f3d0280d0b8e1981a01dab515d684a324cff4b00753a9ef0868f308b8121cbc79077ede84d70cf6015ec989be0280c0ee8ed20b01f99c227da8d616ce3669517282902cdf1ef75e75a427385270d1a94197b93cf6620c15028080dd9da41701d2d0175c8494f151e585f01e80c303c84eea460b69874b773ba01d20f28a05916111a0028090dfc04a01a4f312c12a9f52a99f69e43979354446fd4e2ba5e2d5fb8aaa17cd25cdf591543149da0280d0dbc3f40201969c3510fbca0efa6d5b0c45dca32a5a91b10608594a58e5154d6a453493d4a0f10cf70280f8cce2840201ddcb0b76ca6a2df4544ea2d9634223becf72b6d6a176eae609d8a496ee7c0a45bec8240280e0bcefa7570180920e95d8d04f9f7a4b678497ded16da4caca0934fc019f582d8e1db1239920914d35028090cad2c60e01c2d43a30bbb2dcbb2b6c361dc49649a6cf733b29df5f6e7504b03a55ee707ed3db2c4e028080d287e2bc2d01c38801941b46cb00712de68cebc99945dc7b472b352c9a2e582a9217ea6d0b8c3f07590280e0a596bb1101b6eb219463e6e8aa6395eefc4e7d2d7d6484b5f684e7018fe56d3d6ddca82f4b89c5840280d0dbc3f40201db9535db1fb02f4a45c21eae26f5c40c01ab1bca304deac2fb08d2b3d9ac4f65fd10c60280a094a58d1d01948c2a413da2503eb92880f02f764c2133ed6f2951ae86e8c8c17d1e9e024ca4dc72320280c0ee8ed20b01869f22d3e106082527d6f0b052106a4850801fcd59d0b6ce61b237c2321111ed8bdf47028080d194b57401acd20b9c0e61b23698c4b4d47965a597284d409f71d7f16f4997bc04ba042d3cbe044d028090cad2c60e0194b83ac3b448f0bd45f069df6a80e49778c289edeb93b9f213039e53a828e685c270f90280a094a58d1d01bdfb2984b37167dce720d3972eaa50ba42ae1c73ce8e8bc15b5b420e55c9ae96e5ca8c028090dfc04a01abf3120595fbef2082079af5448c6d0d6491aa758576881c1839f4934fa5f6276b33810280e0a596bb1101f9ea2170a571f721540ec01ae22501138fa808045bb8d86b22b1be686b258b2cc999c5028088aca3cf02019cb60d1ffda55346c6612364a9f426a8b9942d9269bef1360f20b8f3ccf57e9996b5f70280e8eda1ba0101aff90c87588ff1bb510a30907357afbf6c3292892c2d9ff41e363889af32e70891cb9b028080d49ca7981201d65ee875df2a98544318a5f4e9aa70a799374b40cff820c132a388736b86ff6c7b7d0280c0caf384a30201dab52bbf532aa44298858b0a313d0f29953ea90efd3ac3421c674dbda79530e4a6b0060280f092cbdd0801c3ab30b0fc9f93dddc6c3e4d976e9c5e4cfee5bfd58415c96a3e7ec05a3172c29f223f0280a094a58d1d01a2812a3e0ec75af0330302c35c582d9a14c8e5f00a0bf84da22eec672c4926ca6fccb10280a094a58d1d01ca842a2b03a22e56f164bae94e43d1c353217c1a1048375731c0c47bb63216e1ef6c480280e08d84ddcb0101d68b0fb2d29505b3f25a8e36f17a2fde13bce41752ecec8c2042a7e1a7d65a0fd35cdf028090cad2c60e0199ce3afa0b62692f1b87324fd4904bf9ffd45ed169d1f5096634a3ba8602919681e5660280c0f9decfae010193ed081977c266b88f1c3cb7027c0216adb506f0e929ce650cd178b81645458c3af4c6028090cad2c60e01eec13a9cce0e6750904e5649907b0bdc6c6963f62fef41ef3932765418d933fc1cd97a0280c0ee8ed20b019ea8228d467474d1073d5c906acdec6ee799b71e16141930c9d66b7f181dbd7a6e924a028080bb8b939b4401c23d3cb4e840ad6766bb0fd6d2b81462f1e4828d2eae341ce3bd8d7ce38b036ac6fb028080e983b1de1601b9e601e3cf485fa441836d83d1f1be6d8611599eccc29f3af832b922e45ab1cd7f31d00280f092cbdd0801fc9e30fff408d7b0c5d88f662dddb5d06ff382baa06191102278e54a0030f7e3246e7c0280d88ee16f01bfcd0f96a24f27ac225278701c6b54df41c6aa511dd86ce682516fb1824ff104c572fb0280f092cbdd0801cbb430bd5f343e45c62efcd6e0f62e77ceb3c95ef945b0cff7002872ea350b5dfffef10280c0caf384a30201bfb22b14dccbba4582da488aef91b530395979f73fa83511e3b3bcb311221c6832b18d0280a0b6cef7850201c4dc0a31fb268316ab21229072833191b7a77b9832afea700a1b93f2e86fb31be9480f028090cad2c60e01cab63a313af96a15c06fcf1f1cf10b69eae50e2c1d005357df457768d384b7a35fa0bb0280d0dbc3f40201fe9835fffd89020879ec3ca02db3eadbb81b0c34a6d77f9d1031438d55fd9c33827db00280d0dbc3f40201d19b354a25ddf2641fc7e000f9306df1c6bf731bddfe9ab148713781bbfab4814ed87e0280e08d84ddcb0101ba960fec80e1dcda9fae2d63e14500354f191f287811f5503e269c9ec1ae56cef4cd110280a0b787e90501acde42b0bdfd00ab0518c8bd6938f0a6efab1b1762704e86c71a154f6d6378dd63ce840280e0bcefa75701b5900eedc2ce12618978351788e46fefe8b9b776510ec32add7423f649c613b9db853a028080e983b1de1601edeb01d68b226cd6b71903a812aa6a6f0799381cf6f70870810df560042cd732b26526028080f696a6b6880101ca18a91fd53d6370a95ca2e0700aabc3784e355afcafb25656c70d780de90e30be31028090cad2c60e0184c03adc307ee3753a20f8f687344aae487639ab12276b604b1f74789d47f1371cac6b0280c0fc82aa0201a2dc0b000aa40a7e3e44d0181aaa5cc64df6307cf119798797fbf82421e3b78a0aa2760280e8eda1ba0101daf20caa8a7c80b400f4dd189e4a00ef1074e26fcc186fed46f0d97814c464aa7561e20280c0f9decfae0101a18b092ee7d48a9fb88cefb22874e5a1ed7a1bf99cc06e93e55c7f75ca4bf38ad185a60280a094a58d1d01dff92904ef53b1415cdb435a1589c072a7e6bd8e69a31bf31153c3beb07ebf585aa838028080bfb59dd20d01916c9d21b580aed847f256b4f507f562858396a9d392adc92b7ed3585d78acf9b38b028080a2a9eae80101fab80b153098181e5fabf1056b4e88db7ce5ed875132e3b7d78ed3b6fc528edda921050280d88ee16f019fcf0fd5f4d68c9afe2e543c125963043024fe557e817c279dbd0602b158fe96ec4b6f0280e0bcefa75701d1910e44b59722c588c30a65b920fc72e0e58c5acc1535b4cad4fc889a89fccfa271510280d0dbc3f40201b78b358b066d485145ada1c39153caacf843fcd9c2f4681d226d210a9a9942109314d90280e0bcefa75701a88b0e5f100858379f9edbbfe92d8f3de825990af354e38edc3b0d246d8a62f01ab3220280d0dbc3f40201959135c6a904269f0bf29fbf8cef1f705dde8c7364ba4618ad9ee378b69a3d590af5680280e0a596bb1101edeb2140e07858aa36619a74b0944c52663b7d3818ab6bf9e66ee792cda1b6dd39842e0280c0a8ca9a3a01aae213a90a6708e741f688a32fb5f1b800800e64cfd341c0f82f8e1ca822336d70c78e0280c0fc82aa02018ddf0b5e03adc078c32952c9077fee655a65a933558c610f23245cd7416669da12611e0280f092cbdd0801aca0305b7157269b35d5068d64f8d43386e8463f2893695bc94f07b4a14f9f5c85e8c50280e0bcefa75701b18f0efd26a0ad840829429252c7e6db2ff0eb7980a8f4c4e400b3a68475f6831cc5f50280b09dc2df0101a6830c2b7555fd29e82d1f0cf6a00f2c671c94c3c683254853c045519d1c5d5dc314fb028080bb8b939b4401be3d76fcfea2c6216513382a75aedaba8932f339ed56f4aad33fb04565429c7f7fa50280c0ee8ed20b01b4a322218a5fd3a13ed0847e8165a28861fb3edc0e2c1603e95e042e2cbb0040e49ab50280c0caf384a30201ecb42b7c10020495d95b3c1ea45ce8709ff4a181771fc053911e5ec51d237d585f19610280f092cbdd0801eba3309533ea103add0540f9624cb24c5cecadf4b93ceb39aa2e541668a0dd23bf3f2f028090dfc04a01a6f3121520ad99387cf4dd779410542b3f5ed9991f0fadbb40f292be0057f4a1dfbf10028090cad2c60e019ac83a125492706ba043a4e3b927ab451c8dccb4b798f83312320dcf4d306bc45c3016028080a2a9eae80101b4ba0bd413da8f7f0aad9cd41d728b4fef20e31fbc61fc397a585c6755134406680b14028080d49ca798120192600ef342c8cf4c0e9ebf52986429add3de7f7757c3d5f7c951810b2fb5352aec620280a0b787e90501afe442797256544eb3515e6fa45b1785d65816dd179bd7f0372a561709f87fae7f95f10280a094a58d1d01dc882aacbd3e13a0b97c2a08b6b6deec5e9685b94409d30c774c85a373b252169d588f028090dfc04a0184f81225e7ded2e83d4f9f0ee64f60c9c8bce2dcb110fd2f3d66c17aafdff53fbf6bbe028080d287e2bc2d01d18901e2fd0eeb4fe9223b4610e05022fcc194240e8afe5472fceda8346cb5b66a0a5902808095e789c60401cf88036cf7317af6dc47cd0ce319a51aaaf936854287c07a24afad791a1431cbd2df5c0280c0f9decfae0101999909d038b9c30a2de009813e56ba2ba17964a24d5f195aaa5f7f2f5fefacd69893e80280a0e5b9c291010199b10cf336c49e2864d07ad3c7a0b9a19e0c17aaf0e72f9fcc980180272000fe5ba1260280a0b6cef7850201a2e20a7a870af412e8fff7eba50b2f8f3be318736996a347fa1222019be9971b6f9b81028090dfc04a01bae5127889a54246328815e9819a05eea4c93bdfffaa2a2cc9747c5d8e74a9a4a8bfe10280f8cce284020191da0b24ee29cd3f554bb618f336dd2841ba23168bf123ee88ebdb48bcbb033a67a02f0280f8cce2840201e6c30b2756e87b0b6ff35103c20c1ddb3b0502f712977fd7909a0b552f1c7dfc3e0c3c028080e983b1de16018fed01a3c245ee280ff115f7e92b16dc2c25831a2da6af5321ad76a1fbbcdd6afc780c0280e0bcefa7570183920ef957193122bb2624d28c0a3cbd4370a1cfff4e1c2e0c8bb22d4c4b47e7f0a5a60280f092cbdd0801ccab30f5440aceabe0c8c408dddf755f789fae2afbf21a64bc183f2d4218a8a792f2870280e08d84ddcb0101f8870f8e26eacca06623c8291d2b29d26ca7f476f09e89c21302d0b85e144267b2712a028080aace938c0901b0b1014c9b9fab49660c2067f4b60151427cf415aa0887447da450652f83a8027524170580b09dc2df01028c792dea94dab48160e067fb681edd6247ba375281fbcfedc03cb970f3b98e2d80b081daaf14021ab33e69737e157d23e33274c42793be06a8711670e73fa10ecebc604f87cc7180a0b6cef78502020752a6308df9466f0838c978216926cb69e113761e84446d5c8453863f06a05c808095e789c60402edc8db59ee3f13d361537cb65c6c89b218e5580a8fbaf9734e9dc71c26a996d780809ce5fd9ed40a024d3ae5019faae01f3e7ae5e978ae0f6a4344976c414b617146f7e76d9c6020c52101038c6d9ccd2f949909115d5321a26e98018b4679138a0a2c06378cf27c8fdbacfd82214a59c99d9251fa00126d353f9cf502a80d8993a6c223e3c802a40ab405555637f495903d3ba558312881e586d452e6e95826d8e128345f6c0a8f9f350e8c04ef50cf34afa3a9ec19c457143496f8cf7045ed869b581f9efa2f1d65e30f1cec5272b00e9c61a34bdd3c78cf82ae8ef4df3132f70861391069b9c255cd0875496ee376e033ee44f8a2d5605a19c88c07f354483a4144f1116143bb212f02fafb5ef7190ce928d2ab32601de56eb944b76935138ca496a345b89b54526a0265614d7932ac0b99289b75b2e18eb4f2918de8cb419bf19d916569b8f90e450bb5bc0da806b7081ecf36da21737ec52379a143632ef483633059741002ee520807713c344b38f710b904c806cf93d3f604111e6565de5f4a64e9d7ea5c24140965684e03cefcb9064ecefb46b82bb5589a6b9baac1800bed502bbc6636ad92026f57fdf2839f36726b0d69615a03b35bb182ec1ef1dcd790a259127a65208e08ea0dd55c8f8cd993c32458562638cf1fb09f77aa7f40e3fecc432f16b2396d0cb7239f7e9f5600bdface5d5f5c0285a9dca1096bd033c4ccf9ceebe063c01e0ec6e2d551189a3d70ae6214a22cd79322de7710ac834c98955d93a5aeed21f900792a98210a1a4a44a17901de0d93e20863a04903e2e77eb119b31c9971653f070ddec02bd08a577bf132323ccf763d6bfc615f1a35802877c6703b70ab7216089a3d5f9b9eacb55ba430484155cb195f736d6c094528b29d3e01032fe61c2c07da6618cf5edad594056db4f6db44adb47721616c4c70e770661634d436e6e90cbcdfdb44603948338401a6ba60c64ca6b51bbf493ecd99ccddd92e6cad20160b0b983744f90cdc4260f60b0776af7c9e664eeb5394ee1182fb6881026271db0a9aad0764782ba106074a0576239681ecae941a9ef56b7b6dda7dbf08ecafac08ab8302d52ee495e4403f2c8b9b18d53ac3863e22d4181688f2bda37943afbf04a436302498f2298b50761eb6e1f43f6354bdc79671b9e97fa239f77924683904e0cf6b1351d4535393a9352d27b007dfda7a8ae8b767e2b5241313d7b5daf20523a80dd6cc9c049da66a5d23f76c132a85d772c45b4c10f2032f58b90c862f09f625cbd18c91a37bb3fc3a413a2e081618da845910cf5b2e6bffea555e883b0bb9c5f9063380a1c33ebdb764d9ffefe9e3169de40b18eeb9bfca48296457bb0b4e29d7b2b5bc4e0021ba0a1d389f77a8e253d6db149d400f675a9330f3bcfd09c7169224a947b6b4e0745ae08cd7adea4151277a94f51f85292ba082cf28300cca233ff4966b093c9cb6abcef476026040fec2b435021aff717b8bb90a40950e010f70bb416a618dc3c5c03f590c5b7ec8e0c05b85ba94078de4817918f783022364b8aa228b5df43b38fba3060c30616f265022584ab6034ddbc832450f90047d0cf41a4af8a20fb1aa66406133a17c2e905ee28d8acd186c872859c196db0474dfaaaded2d63768143cf6b5e2e34662f7bae573a08cb15069ef881892e5a0c08b5c6c7b2e6376cd2080fb29e8d3d5aa5b853662b4f1784ba7f072130e4dc00cba3cc9278fc4213f2ce2fc82bd1ea9fa91bb17b4f7c36962c78d864eab9f30ef327039da6607962a156a05c384a4a58ddd8f51a0d4fe91f64ae7b0a5199110a66f1e676392ec8d31b20a65f7a7fcff90b37a8a3962bff0c83ee6033a70c5b0af663ca48a8f22ced255839444fc51f5b6a6c1237eda5804289aa25fc93f14d0d4a63cecfa30d213eb3b2497af4a22396cc8c0e7c8b8bb57be8878bfc7fb29c038d39cf9fe0c964ebac13354a580527b1dbaced58500a292eb5f7cdafc772860f8d5c324a7079de9e0c1382228effaf2ac0278ebedad1117c5edacf08105a3f0905bca6e59cdf9fd074e1fbb53628a3d9bf3b7be28b33747438a12ae4fed62d035aa49965912839e41d35206a87fff7f79c686584cc23f38277db146dc4bebd0e612edf8b031021e88d1134188cde11bb6ea30883e6a0b0cc38ababe1eb55bf06f26955f25c25c93f40c77f27423131a7769719b09225723dd5283192f74c8a050829fc6fdec46e708111c2bcb1f562a00e831c804fad7a1f74a9be75a7e1720a552f8bd135b6d2b8e8e2a7712b562c33ec9e1030224c0cfc7a6f3b5dc2e6bd02a98d25c73b3a168daa768b70b8aef5bd12f362a89725571c4a82a06d55b22e071a30e15b006a8ea03012d2bb9a7c6a90b7fbd012efbb1c4fa4f35b2a2a2e4f0d54c4e125084208e096cdee54b2763c0f6fbd1f4f341d8829a2d551bfb889e30ca3af81b2fbecc10d0f106845b73475ec5033baab1bad777c23fa55704ba14e01d228597339f3ba6b6caaaa53a8c701b513c4272ff617494277baf9cdea37870ce0d3c03203f93a4ce87b63c577a9d41a7ccbf1c9d0bcdecd8b72a71e9b911b014e172ff24bc63ba064f6fde212df25c40e88257c92f8bc35c4139f058748b00fa511755d9ae5a7b2b2bdf7cdca13b3171ca85a0a1f75c3cae1983c7da7c748076a1c0d2669e7b2e6b71913677af2bc1a21f1c7c436509514320e248015798a050b2cbb1b076cd5eb72cc336d2aad290f959dc6636a050b0811933b01ea25ec006688da1b7e8b4bb963fbe8bc06b5f716a96b15e22be7f8b99b8feba54ac74f080b799ea3a7599daa723067bf837ca32d8921b7584b17d708971fb21cbb8a2808c7da811cff4967363fe7748f0f8378b7c14dd7a5bd10055c78ccb8b8e8b88206317f35dcad0cb2951e5eb7697d484c63764483f7bbc0ad3ca41630fc76a44006e310249d8a73d7f9ca7ce648b5602b331afb584a3e1db1ec9f2a1fc1d030650557c7dbc62008235911677709dea7b60c8d400c9da16b4b0a988b25e5cf26c00c3ef02812def049bb149ea635280e5b339db1035b7275e154b587cc50464a4c0bfd15c79f54faa10fbe571b73cf1aa4a20746b11c80c8c95899521fe5f0bb3104b0a050c55a79511e202fee30c005339694b18f4e18ab5e36ea21952a01864a0e067d9f19362e009a21c6c1a798f7c1325edd95e98fd1f9cb544909fdf9d076070d1233e183fb6d46a46fbc6e10452ef4c45fa0b88a84962ad6e91cbcc52bc000b12a82e93ae5998b20ee9000a8ef68ec8a44862cc108869fd388142692be6b0657e3fe79eff0e8b72f63aeec5874acf5fb0bfc9fa22645ed6ecaaf186eca690ecdf8a71b8f4789ac41b1f4f7539e04c53dd05e67488ea5849bf069d4eefc040273f6018819fdcbaa170c2ce078062b7bbe951d2214b077c4c836db85e1b138059c382ab408a65a3b94132136945cc4a3974c0f96d88eaa1b07cce02dce04ea0126e6210a9543129bb8296839949f6c3867243d4b0e1ff32be58c188ba905d40e32c53f7871920967210de94f71709f73e826036b4e3fa3e42c23f2912f4ea50557dff78aeb34cb35444965614812cbe14068a62be075fce6bf3310b9e8b12e0dd8379104360f728d47a327c172257134e2c0e7c32e01321f4d636f9047bd750e7993eeda7d39fc16f29696b1becee4d8026e967f8149935b947fce8517b2ce02b7831a232f3a29010129c49494ed2b84c7f881b7e4b02a00ebabf5a36023c404002d6cb88cee76c8ce97b03143ca867359d7e118d54e053b02c94998e6fd8409f8d46fc1741a2e56aebb1e7dab7ca3296a2566263d9be2f4bbef4872a49ee1082cbaf86e21b0c232c4182fc660f0c0b6aaeb0393750e553bc406e2a27842bd033da45a562ed1998ef9bd83e35ed813bef00a3e6147cb363bee63c543ba5e770b043dbacc155214a2496f91879bbc9170a2a513d7b48fad40c8c2d96f951e3a0932f6d12956789198430b352803852aa9726163fbe839979b33f8dbf7f76cd50755c1ce0c40a072aeec35057d06abaf59e878000b1d796e51908bfbf23b13900dcb30f9bd52b52994e7245a7017653a404a70d1c444b8c613ff10a2b057c02d062c5faf13cdc4445809fb6e096923cdbbdca18f59318ff86c7e449f596050b404d3cde0338dfdf9b1389178b1d4c70eefa2bbd76fefc1ee1f1688ef507821e40ae31d8d8e673d183b54563e2cbd27e0e042f61b046877d37a68c1b5784830690f2dd4ebbbd2dbdb35800b9e0ba8ea985fa106dd2ce8493e845586716c538ee9008b88a7c482f3c00c14c08468230d40cdc040e145282c4d61985cb5800306e305146204f63e96ad194bcdf1338ab8480341b6fbccf18fc32145f84bece4069c09e41096e94c24fa4f0db988e860a3bff3604143f2b17e8c219f28189e4cd49a0e506fe62dc419299bcd78c6ccb107f63eb31b4bd8ea1e2fed10e3ac17341d3505019e2376b01f7a7fcea3db110fb090c681c866ac86f13e6f8d44a32861e0580def063736b5c771b2b3b9067045867b4393f3eb2a4610bd0216e29906aaac370986451c6bf78264dda7e7a5fcbcf7bd6e024ff6003c6db780d89b97765cee8d0ff3ff25d94d4b4b919f722b26a6903a017daa62af387843087680c57952de06064de05b662af87be49b6e34cf0991cec7be3396e2eec9678ba259bd8de1c192014d02928f9113488215658df4078ed661fa4e79e58decaeb0ee5a00488b094b0b77f083b2b7844f481e7788ffe8004b96ccdf853532bfd9632a8a652c2d97d10173c90864fbb6facf47fae415df4acc0b099140a657b35d083d74dbdfbf107303e74c64471bed4b2199f2babcb4e1fc593d6f309e21f85e68ffd9904731559d0f2b673b36d3984e5d66d897dfa17d601edef3ed78cb70dc5115d4ae240c203e031263f0cf1e98075bac0361fde24cbcb852b8055d53ae01d61a0a1e1ba423d00833747e7364df7ebfd1f84598d801c249e1805279dc37d39fc7f7e27b067e4e0287aec432ed49e4d701a0ff377e88179968430d110cb20476ed4c6bf1624d1907ef24406d3295fcacde2a102cc85f4f3d0cb87a8fae7535a06e442833e58cfc04242ff85fb654d05f9874c0a6756f542db4e9d8b0366191fbb8b09a1bbcb6af04c069978417ca80d92f442b7dbd092f74e1268aa73b54e4b64e84543449ecd30b5ea392a1669a5f441d7208925e91c75df611cd26042630c6b98f160b8c0156048108d5465b71bbc54d31a9f90e34428d97590a427e1ae618d4a35fc1022d4e007c6108dcb1672b88d43ae4d886a5adcc26faf56bc5e5a0b08342fb88263fd80940d1edf794c6ad6d339b974e164b38439e11b4fa87cc793b080b4f8bf0eb56043f79ed3911da21092475fcf8320b55b9f558f194c6c8121b2e696039340d97057be2583726d762b5ae4327e5286a2d8c14ddbe0027c75aacbf7e9de13037390df7d72e13b46bc06bad0363b070e0174d034120d7fa7b4550e7dc28f7f0241f059ae266fc13dccd1d07f744208a7d6a2e565b6613d46e4550f79ef3209c46a805b97284df558719e131f44e419e690f4fc28ee4862b9d1f8f7e1a164ac18141076087693e70ac76a10f7851530d4cbc65def90d5544671ad64249569c3abf0200d09be3c63efaa7cb723b39ccffc9b3a3ba0c8426847123d2a881efbd4937a40cb3e8011c70ba427f80b3dc91a608086f8b61f8bd86fcb482ea388299a7cfbd00a3ddfadb4b6d0e51c1369276c25889a9f3592900b6502d9af1c732e1fb7db307d71e45deb1553ba1568d0480ea9e132b52564da6ac5c56eff823e7f37976cd075ce8f7a78aaef1b87f7437a0b84035677f266f7d0596e493101fec3e14fcf80b22322454587b51fda0636231c07d4e63e007f1b89137d8a37b03bf00f3a7c10169f757d9a74b7bffba797c746e3845decc3d0559d7cf6f08f3bd67dac5f33109b212582dc7df5d561ad63dddc794f2aea4e493db1a73c702d258c9b922c35d04c47f88f87c54c821a29f04abd91a079ce8cef252a21dc72d409fd1618c9be709af029ba98b0140e74666fcb01bced4f88ab68e6b63b8ed6febc0905d22cb2200493c071ce136833697406f8a04e77b21747dda997046cf3c7080096fe481790d77cf5904e7f7128ed95a6e576d109fdf10eb0c888db35a4a685b62253987b70fb1538e6c0932889460fa31c60d123266b7bcb828f846a35b2851127679b05f05a75266529c343a6075e54c455e02b2c83e6f7bf1ae23326506a5f532472d780815c5af425f7d8b543a8f014966e0538f48ca84d181695381a09701eb65c9ae084bf2a4dc84f1b2071be32be25d5f4fcdc59668fd800496ef7eb6dddf867ab908e543cb51f0451706cce4be6b9f68a537a79ea88e17fcd78965b3da68c0d9d30623a2a9e275e1c320f59e118e09c02eee527167bc06f7693e7b584e3b25ecc1093d46b80a1cacced87c2b32e2f90c5bbb9cd1b701aae69a04b16d535fac6eab0d091790fc5fdfa8a8842bfcb62dbf963cbf62c4afb4c468be98c770e6078b8c0a8cfcbae43dcfff17d3c6d587c3e4309fd39c66acd14781fea66fc57278b02302c0fa386280e67acff19955b6a428b0e22ceb1e54e913a37cd19eb6e9d2268a039f2b5fdda7d5804db79385f0e50082b128c952f8dfdedc4411d0675d95127f0bfc01710a869b10d7a8b9e632dad944062567439e6d192fb09329d058e87ecd0aa8981328f541e87ed02cfe4031f2d3a046ff517a2a80486b04ade31a647aec0884fb96ed753ffc47892431c6e6f08fd1c633a1a44e882d3d8b92c567e0fb8305327a354851464ca0f18d89c6ee2a91a4afef0c55883acf8fcb68c2c3b7402e005d8affc19c13f1f26fee0698dff181ab22cb84a2b31e0a6a81dc5d02e60a3c07090397ae58a985526b2ad6ee5725e82328062b68566b4871705ce3b9856e550d068c20fd9aaeb27740c07aad53d79fc20e46e40e7103e2d69626ee64b6aa600f6f1a86f37948ff4990d88f43c34994e2fe586cb779997be323da53329c10480aeb08fe440e9e4b979171371c73b94da9f928a3f6c8f6792f782f3d6432b86d06f54557327fef31fd6ae0a3f6d2f16c9ad947d132e14def33fa24cb4565370e0832fa50f5f5f93c9f3d65776cc22608b68a4f3719e9be47a19432991e4a2c49089c0ea20e7f7c73feaa47970da424d8543d80d622e2f2be9f4c65cc39dc369009a9d41a52bdea7cc0e8e04da87a633fd4f814fda1b646121a469ba0b5b8006d0e9118761d97b5d1856e2d690f27a81c42b176df853d07cf4a66ee83c9eb24ac0a382f5143a10a33ec3ddf17dcd8a8303fac8f279d31b4d04d74bd8804cefbb400c86174ad444e43ed33ee1e1e73f660b9814d5ca3cb1d650f1978a825a617bb05f84eab3b9b8359b991e1084cf4e8179ecb67f92398638e31227ff63427b67f0f232b454a341d85d4b56e31135c9035e231f7d9318ca12b5ab524f87bb0ca9b04b80effed202897ab016d5acc054c4fe62a5f0192f136cf2cd714998a4b164b0c2cdbace52243fdc9ea879b0d247d4fe8bd80481fad6b325cb5f2cfa2534dec0e47d41b6b99352e6e5faccb5ee28ca2fe96e04f9c83a0461ba34cfb499d864f05dc734b6c8f51cc0c994290b2a868cb8312df39fb6d457a81e62d872c65d4f3007094be42663bca3d64ebbcc8401158fce4f5a38a49c20f029c338f126451820459866e77c6984a467aad571cc7452392a9cb9f8e65099fff2f2acd170a833e01ed3d22a683356ee42dcbe6bab6d05d3edda2d40e9ba53884d430c2e0cd87c0067dc8cb68c868bd9f29db1dd73703c139ffc15e4f7264e727c70560ae02da100871f30e8a7c1275805f621752d73aafecddc2a7808b6c2ecbb8d0134a644bb603f30f8d18b3fc5efaa7f206ce180bfb14f5dbd3b0115145a227113eeaf1c1ec04244227b931388e72960833a40baa4319c5cf74aa94f7e3233e1d09f0a4f74409999684ad1cc836ac74563c85b164664dfab08ea084b25e2cbd7e7b94a781a10fcd455ee38b65126dcc52016127fd195c80b05660ed931e128b0cb92868955c0d032ada9fb951210a5442d21c1718ebc4702ad4a57967e15a63ffb05e1e072a0c41ebdf1e7205373eeaf4587f695de887fa3a8c96b8deb99e040fa1fc4dc2a402a017891943d734ae2f3798b22b1269d3d9f6d65581b9c637a6896a4fb554810bbd3db5c5737391a74150b43413b2e3824490b7911cbeb845147f1a8521620b0dd31306f13a9754a01bcdbd18bfdeade06b0ec97f48df56c45d3670a1fe18d00ef13e613c8a77aeb40401a814b377137cf44f29cb2cb94186ad1161ecb05a7c07837a5ab3474e57990cff2ab16b4d99f62e646da28e8bb712a5b561cf0e25be039c3e08583c8ebc3dd2fdb8fdc6e135ecc7851c73218a70b75e697cc84ea50504b9c34a33ed52f87230b9d192a940f3b7bb6d45b58dbf52f0afeb8dac85c77b06bdf9b70a10cb81c50055c9d8cf7e3a5c4b7dfae55beabcb3e8a8a1cb822d8d0bf6c01e32056929f853021eae6c97fdb0c5031df6b2e7c57f1318866769a9cc09c38ed62d8bf4663334c0df67c47236ed73f6ce7f54e0ada9270398c1aa558d0f993b0d25d97aea77b1635ee4832362cd590bae5fc1549402ddcd42b15efc930111a01535c0242116078d6d2d53b8612d378c4370e90d0d01b01bd7da591bec07981652a98485d8ed5c8f3def2bdac7d992ee5fc6a1ec7bd36940e1bc58c7050451248fc3ee6069e6b1b0d3ef122c6ef2a9b99aa0f145fb43341c58dbb472130b51730c956273a3ef6df9e000f6a87c2bacdefcdb5daef28b6170f61bc3a9c101f439755c86e6b85ee06a7a60688b3843eb359cd4acd9221a2ee131e2fd2e190652e5c47c0b98c41010eb99a991ec48a5de99cc8f403d6d76f8307d6657c1e007ebd64eec7bbd0d4f1ba2db7bb0efe27c7828f053e00def775943ab01a7e33d0fffcfe6f9a7285237f2c381b638758e373f8ceac672190664bb25fb5d355c240bd1773d61bda7f7ef1f4261b80ff5058ec6f7e024ab9459b1103815624b81f80c39db2f6fecb72de452b11636b0f71b16cb55f883d93bebb94328f13ef1ab6d0df449e32d27884f5139af584035547dace65ee25ba05cc461e74760d4468af90dcaa982e52cb902e2b84b3324019da575601ca54e91655913892e703257deaa01d14fd8459ff780c724161ba4d4280b70a5039dcfb5d775560714009724cb0d0b7e178c71e777b896bcfcde7d4c9c3dc6ab819d74a1a1fda8486448b1ad79be02fb134ea93a8600f1bc2a42e68d0213ab461a07cef3ad3965bc130beb76bab409102f82bf6c4cd626f6df3388e17b87584310c50832cde3191f6557f0014bdc0a68d924119e43111043bc6f26d16a5f2612dae6ab24984e2d87a71d93d5f4670dba2176d4f16633407bf7c10b51b6842dfdbc6fe3eaa4b6a12f0550700ece070ca382dec3b587e0e1fc317a48a83754d15aaf9a6971b8cb641fd8b32846d89002e6301700a0e7056e8002d8f269d29ebaf64f4493b1f1e676fc78e673067fb00625df15dc0490235b386ee14e55b335f3bc6dcedd7d3a80fd3a6e9bc2ccf3af0d89be71b5ca92bd7a9b97b9ff8976f75702419aa5bf9be34600496ca1bfa8ad0400602a23579365574252434f2bcd7efb360b0e8a495e8f7e78923b6fbf2207049e9179f0d4d7d6b4a4a10ca10f0ef4dd6cb5a74f7574e832044d6120fbc1580a68eddfbc65ab300bed960a6f24a102dc36b72937a8be4385daf5946e81ccde0619251babbff17e5685217a134d22f6130d0322483b3475227ffd27adc73ca202a6debfa37e5731747f4449ac70a33684f460eede65918c6d89acf4b50fd28d040ffbd436a944d3be0210606bfc2301e7ac66d462dba29a0489eb55af714a760e5302592cccc726e535b945ceb6126eb84e31f0f140ff54df8be0fa3a22f418036ad996787a5616a97a42049ebce351dc11857cab3dc914ef26833b0e75653004a8cafea099fb0750135255c41ef43e2f29c75714e2f0be2545e7c109b70c43004a471daa85b47befc65907d033f133b2f3ac2ad568df630ee80506610b8dc9052d442668dc06b13ea76ab1ab7b34870341d660af5d3007c21bb72512e4f8a60d8916a037b93f9e15ac9e4a6a1246d73ebb40e5fdd5a0d6dc0cf175023b891301f69fe5a3ca6f12cb8312d16333de1cd3ebb99339ab18c0715bfcd35b8365b407ad759e2c591d8270ad335381573e27ec18af7ca157b4a2bbca921db083d9b0009dc332a79dde14354a8c18bce76a1bfc1a25a1e702ccaa0feb521ee9279b8a01ceab6e237bbe4128b23cb53b1e5185f3266e20670a307ea0cfb5377025e0bb0790d48f1636c8b836c1a1f69ad61265f19057197e86cd526da6ddb94fd1ece80b60852f27ef2ce56ccb5a32d8cab6d16be06f380dfde3602ea4c1ae927173b2001ff0d9e29bc66b2b2a20c3e3ac174fcba187aacab0876c1356d30d4021e6dd0048c3bdfbf254108bb09d3ca9f2be423a92408bca52fbcd68f972c46fc8d20e0350d12c2f2d6c7da85e96bcec3ce61119793d44a210f81ece859fef6360ae3b0e1af0634fc141a8b50b3b383fb264e8a4fb84ea06db6becbf5e140edf66ee190da8968da579eb349fedea45e4c252a79570501278bab5fa984d7b1179d7c2460faa7beafee153bbae0a591701632aa94839528d3ef50cf809c1f7209b9e5c99010eaff7f921c45b6546358ee7a90948e3c710cd3e1796860839a345516fdf4f07c415029627abe1273a1f510c36a662562d18169b23305b4efadfefbfbb41a400ab533e61c14cafa49bc5d2818058ee4f3e1aeb329e150820d1de1f1eaaad31051a6dfdd3a1d5cec7b16bc0ea2c649d409917faa42138b1f824b4d534a050be0a99ea6772daf0b2e58623cc7a250ef37599bd556508f08886e663ef0917ecd3077072c3268ea5b9b89cbb6b761ee9f9c4765d8b267d9eb19728a28ce67a42ed0cad142b5dc0fc5313853860ec3f0ee2bc3d47cbe12dbe9633db809967d5b8bf0e45574eac657059530c30aeeade1e4f858a4a6e79d6e441b4af0127a13340d908d48cfec849ee93d53b1564231f048d34885e791a9d40c61a7b00f12f6f72a5050bcaabcc98480170ea6e20bef6b5c6f504c808108454fe2f3c275bf8f89a5e0a3304a7c4787e6d4fbe569930f7cfd38ab7d1d2ebd599bbb411950cec3e53b90cefb82234990d353c71ce4c21ef674a1c4070f71c90e1ea7edf35f5a421118f01b49a92ea97720e2d4df6b5885c181002656629a90eaa1904fe1c379b8291480ca15d0dc2b65a20c22f1e01d612d21ecb5738e5ebfc578a4a65066ee6e913e3030d3fdfb0168fd75022492728ee82869deb9ff2827f4e10759ecddb20f67e9808e707257a74d3dc0a6068f264066f95c9f772a3dcec0b4f0a327e3745517ad60ccbc5392890d2479b724d068fcdb83607e02291c06e1a5a1dac7604889cce2500f418da2f7080a7e9a1bdf28b87028a2bbb0c14f059f10f46d46716eac2cdfc06676cbec91b8c2c0f7c9bea7e27fa5048662398b23a9b488a49e1d3330c04e60179a4492c8b836780899899d2af17e6119a94d54a890ce8c0b550e87fd54cba0821fa7c48f6e09a60dcbddf853f82b47195aa44a5ceae14a9257296acd711c8073ff3345befca5d3ebf64901b283df96395fe9785d7090176bfe5a9f13ceae701c6c93af0e13d949bc3c7e9b06674a73e7affc508302258a27fb34569c3742201c0721aef282a31c69a5d98a67ac5c3d920d50c089896f7f8c8c237a81f803f0444f417246d695e89a3a523b62a3cd2203d42607cac7c7782dec1f9edbb806c0a7a37d1a969082a126bc726151a50233456a07d374399e74aaa8cc66821511d092615950d302e815cfcc021e1250cdea20fd9e1e4b5e88280d6e4283b918e780d12cbba59ef2ed2ce86135a48fba6c0dc2bf2efee190d9a3f9aa22a622b1953058f2bb3a371637d13e045d54e7eb54c0d25851f49283d7d34e9785d2d5c3f70086c48a8325a2083bdf5b3531fcc697cc0c9f63892a866c84585d673a2a63fd60e77995bfb0c0a44a4b63c0ff67e813027d3e84cddd393a0f4e6bc95525c5ae20eed9d0cea4a12aa748eb5209cfd75990b055f1ad0472f9f7599f569a8743a720755aa11555df4bb2e725fa93bc5dea603a964e8dc9fb1742e81825022866fc50a6b2a19b6a234a38ee27a74f2f5832b294143ca7ff8d07fd7d4e01f479e9792058871d90ee3aaa3329e82cebe41dff5e6d00a36268a7965466b80c6510ac1350cee797e1d6737f6aaff155266d2a2d611b2124affed1ac73a6a06515627b2230ce0d7fed33ecbde511f4d472cbc556cc8d9c5640e67657035112976b626847a0a4ea5fdd14d4a3eed57f0dfbe153393d8bd28c8b4f9e62940e8379790393fa20c617050c780a7d870193b4611bd7a12d26947a3cf4605e225da8b1646a76984015a5e317016a4d8301eaeec0db3ae0daa719182e2f4479154dbcccfcce1f365099de6c91934c395ce82abba8062a51d7773b418330921766cd3d275c689098e06039698db6f09accb292e7eb79e7a022d4257bb2f9ed993c519860919bc229a06ad88954c9ebf7f5b9fe95cf56e8181cb9175dac06be0be70fd28df20cdb4600ef0869668c645c9ea01360fdae7c922cb3d2b3583ae1de5ae7d899a83ff2bb00d7365c782a0fccfcba7f87bb29416469bb051f9b0755123e0f2fa76dc7644b70e452f49a84bc372b384c843b8161b7f9b63699adcadd5cb2b33b36c7eb3e1b00f25218bc16447968b939016242fceaebd796c17a24d1b9870991a9c3ae90e380302b7bb320adacc08cfb9249d29cd9275c52476dac6a7e9870ee3776cbc3352036f9c8f681d44856c6c5f90b7cde0877472ddd48719c449f59dca1f49442f7505e4809c6d323b37530ecccf3e41e19822f53d64dc90efb113405ee88799c37f0a342293b5bfc019a9057138326de6107b5613554dffc737aed7237fb16cd77e09f581d12220ac930c6ca279efd1d07a92125fb2606ec3ec35351987a15fc72806cfb3cb66fce8dcfabee5c1e586bf0f802fa12ae5ad5a708e3a5d54e1926dbd0202bf1150f1bb612b9a4590b5b520b86a90860ec3d9c2184f9975ced15ae1300882d9918021b43a1184ba88ddd7091539fe5a7017b8708d0f5c916f9c42de5103f8116863864b508f5880ca60b7492385c16a02b6ceb64d257a4838873b85d2041517c5c7c4508e4d5a5faa72729d73af0361e11828eeca992b8f20d903a5ef065976a9f322e34bd4b3984bb09e18be40e77e833c8c1a2e80093227d3f40d4a067f5e3aee9fce9bd234bb6ff4d0c34fc060d23e86b1f5a6d8d052e53e913182052a2d9c5e97bb0e0a51bb2fafbe7346bacfcbadb00ce2ba129f29d41a11f7d105cf19bb60b5f5b0dfd6a894698ef7f56a02d69cc03eb62a56563d3a77e3ac2302"], + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_alt_blocks_hashes, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 288..=308, -// GetAltBlocksHashes, -// Request {}, -// AccessResponseBase { -// blks_hashes: Vec, -// } -// } +define_request_and_response! { + get_alt_blocks_hashes (other), + GET_ALT_BLOCKS_HASHES: &str, + Request = +r#"{}"#; + Response = +r#"{ + "blks_hashes": ["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109"], + "credits": 0, + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} -// define_request_and_response! { -// is_key_image_spent, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 454..=484, -// IsKeyImageSpent, -// Request { -// key_images: Vec, -// }, -// AccessResponseBase { -// spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. -// } -// } +define_request_and_response! { + is_key_image_spent (other), + IS_KEY_IMAGE_SPENT: &str, + Request = +r#"{ + "key_images": [ + "8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3", + "7319134bfc50668251f5b899c66b005805ee255c136f0e1cecbb0f3a912e09d4" + ] +}"#; + Response = +r#"{ + "credits": 0, + "spent_status": [1,1], + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} -// define_request_and_response! { -// send_raw_transaction, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 370..=451, -// SendRawTransaction, -// Request { -// tx_as_hex: String, -// do_not_relay: bool = default_false(), "default_false", -// do_sanity_checks: bool = default_true(), "default_true", -// }, -// AccessResponseBase { -// double_spend: bool, -// fee_too_low: bool, -// invalid_input: bool, -// invalid_output: bool, -// low_mixin: bool, -// nonzero_unlock_time: bool, -// not_relayed: bool, -// overspend: bool, -// reason: String, -// sanity_check_failed: bool, -// too_big: bool, -// too_few_outputs: bool, -// tx_extra_too_big: bool, -// } -// } +define_request_and_response! { + send_raw_transaction (other), + SEND_RAW_TRANSACTION: &str, + Request = +r#"{ + "tx_as_hex": "dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308", + "do_not_relay": false +}"#; + Response = +r#"{ + "credits": 0, + "double_spend": false, + "fee_too_low": false, + "invalid_input": false, + "invalid_output": false, + "low_mixin": false, + "not_relayed": false, + "overspend": false, + "reason": "", + "sanity_check_failed": false, + "status": "Failed", + "too_big": false, + "too_few_outputs": false, + "top_hash": "", + "tx_extra_too_big": false, + "untrusted": false +}"#; +} -// define_request_and_response! { -// start_mining, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 665..=691, -// StartMining, -// Request { -// miner_address: String, -// threads_count: u64, -// do_background_mining: bool, -// ignore_battery: bool, -// }, -// ResponseBase {} -// } +define_request_and_response! { + start_mining (other), + START_MINING: &str, + Request = +r#"{ + "do_background_mining": false, + "ignore_battery": true, + "miner_address": "47xu3gQpF569au9C2ajo5SSMrWji6xnoE5vhr94EzFRaKAGw6hEGFXYAwVADKuRpzsjiU1PtmaVgcjUJF89ghGPhUXkndHc", + "threads_count": 1 +}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// stop_mining, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 825..=843, -// StopMining, -// Request {}, -// ResponseBase {} -// } +define_request_and_response! { + stop_mining (other), + STOP_MINING: &str, + Request = +r#"{}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// mining_status, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 846..=895, -// MiningStatus, -// Request {}, -// ResponseBase { -// active: bool, -// address: String, -// bg_idle_threshold: u8, -// bg_ignore_battery: bool, -// bg_min_idle_seconds: u8, -// bg_target: u8, -// block_reward: u64, -// block_target: u32, -// difficulty: u64, -// difficulty_top64: u64, -// is_background_mining_enabled: bool, -// pow_algorithm: String, -// speed: u64, -// threads_count: u32, -// wide_difficulty: String, -// } -// } +define_request_and_response! { + mining_status (other), + MINING_STATUS: &str, + Request = +r#"{}"#; + Response = +r#"{ + "active": false, + "address": "", + "bg_idle_threshold": 0, + "bg_ignore_battery": false, + "bg_min_idle_seconds": 0, + "bg_target": 0, + "block_reward": 0, + "block_target": 120, + "difficulty": 292022797663, + "difficulty_top64": 0, + "is_background_mining_enabled": false, + "pow_algorithm": "RandomX", + "speed": 0, + "status": "OK", + "threads_count": 0, + "untrusted": false, + "wide_difficulty": "0x43fdea455f" +}"#; +} -// define_request_and_response! { -// save_bc, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 898..=916, -// SaveBc, -// Request {}, -// ResponseBase {} -// } +define_request_and_response! { + save_bc (other), + SAVE_BC: &str, + Request = +r#"{}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_peer_list, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1369..=1417, -// GetPeerList, -// Request { -// public_only: bool = default_true(), "default_true", -// include_blocked: bool = default_false(), "default_false", -// }, -// ResponseBase { -// white_list: Vec, -// gray_list: Vec, -// } -// } +define_request_and_response! { + get_peer_list (other), + GET_PEER_LIST: &str, + Request = +r#"{}"#; + Response = +r#"{ + "gray_list": [{ + "host": "161.97.193.0", + "id": 18269586253849566614, + "ip": 12673441, + "last_seen": 0, + "port": 18080 + },{ + "host": "193.142.4.2", + "id": 10865563782170056467, + "ip": 33853121, + "last_seen": 0, + "port": 18085, + "pruning_seed": 387, + "rpc_port": 19085 + }], + "status": "OK", + "untrusted": false, + "white_list": [{ + "host": "78.27.98.0", + "id": 11368279936682035606, + "ip": 6429518, + "last_seen": 1721246387, + "port": 18080, + "pruning_seed": 384 + },{ + "host": "67.4.163.2", + "id": 16545113262826842499, + "ip": 44237891, + "last_seen": 1721246387, + "port": 18080 + },{ + "host": "70.52.75.3", + "id": 3863337548778177169, + "ip": 55260230, + "last_seen": 1721246387, + "port": 18080, + "rpc_port": 18081 + }] +}"#; +} -// define_request_and_response! { -// set_log_hash_rate, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1450..=1470, -// SetLogHashRate, -// #[derive(Copy)] -// Request { -// visible: bool, -// }, -// ResponseBase {} -// } +define_request_and_response! { + set_log_hash_rate (other), + SET_LOG_HASH_RATE: &str, + Request = +r#"{}"#; + Response = +r#" +{ + "status": "OK" + "untrusted": false +}"#; +} -// define_request_and_response! { -// set_log_level, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1450..=1470, -// SetLogLevel, -// #[derive(Copy)] -// Request { -// level: u8, -// }, -// ResponseBase {} -// } +define_request_and_response! { + set_log_level (other), + SET_LOG_LEVEL: &str, + Request = +r#"{ + "level": 1 +}"#; + Response = +r#"{ + "status": "OK" + "untrusted": false +}"#; +} -// define_request_and_response! { -// set_log_categories, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1494..=1517, -// SetLogCategories, -// Request { -// categories: String = default_string(), "default_string", -// }, -// ResponseBase { -// categories: String, -// } -// } +define_request_and_response! { + set_log_categories (other), + SET_LOG_CATEGORIES: &str, + Request = +r#"{ + "categories": "*:INFO" +}"#; + Response = +r#" +{ + "categories": "*:INFO", + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// set_bootstrap_daemon, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1785..=1812, -// SetBootstrapDaemon, -// Request { -// address: String, -// username: String, -// password: String, -// proxy: String, -// }, -// #[derive(Copy)] -// Response { -// status: Status, -// } -// } +define_request_and_response! { + set_bootstrap_daemon (other), + SET_BOOTSTRAP_DAEMON: &str, + Request = +r#"{ + "address": "http://getmonero.org:18081" +}"#; + Response = +r#"{ + "status": "OK" +}"#; +} -// define_request_and_response! { -// get_transaction_pool, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1569..=1591, -// GetTransactionPool, -// Request {}, -// AccessResponseBase { -// transactions: Vec, -// spent_key_images: Vec, -// } -// } +define_request_and_response! { + get_transaction_pool (other), + GET_TRANSACTION_POOL: &str, + Request = +r#"{}"#; + Response = +r#"{ + "credits": 0, + "spent_key_images": [{ + "id_hash": "563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393", + "txs_hashes": ["63b7d903d41ab2605043be9df08eb45b752727bf7a02d0d686c823d5863d7d83"] + },{ + "id_hash": "913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b", + "txs_hashes": ["3fd963b931b1ac20e3709ba0249143fe8cff4856200055336ba9330970e6306a"] + },{ + "id_hash": "0007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c0", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "1cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "38d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d6", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "52418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba48", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "65bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "7d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "88f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b8553", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "c5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3", + "txs_hashes": ["7c32ac906393a55797b17efef623ca9577ba5e3d26c1cf54231dcf06459eff81"] + },{ + "id_hash": "ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "d656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9", + "txs_hashes": ["b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986"] + },{ + "id_hash": "fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d", + "txs_hashes": ["c072513a1e96497ad7a99c2cc39182bcb4f820e42cce0f04718048424713d9b1"] + },{ + "id_hash": "4ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68", + "txs_hashes": ["88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412"] + },{ + "id_hash": "f64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff", + "txs_hashes": ["88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412"] + },{ + "id_hash": "d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f", + "txs_hashes": ["a60834967cc6d22e61acbc298d5d2c725cbf5c8c492b999f3420da126f41b6c7"] + },{ + "id_hash": "428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f", + "txs_hashes": ["d696e0a07d4a5315239fda1d2fec3fa94c7f87148e254a2e6ce8a648bed86bb3"] + },{ + "id_hash": "368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d", + "txs_hashes": ["9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e"] + },{ + "id_hash": "45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0", + "txs_hashes": ["9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e"] + },{ + "id_hash": "6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf", + "txs_hashes": ["dbabb82a5f97d9da58d587c179b5861576144ea0cd14b96bef62b83d8838f363"] + },{ + "id_hash": "2c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee5498", + "txs_hashes": ["aef60754dc1b2cd788faf23dd3c62afd3a0ac14e088cd6c8d22f1597860e47cd"] + },{ + "id_hash": "a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4", + "txs_hashes": ["8a6ebea82ede84b743c256c050a40ae7999e67b443c06ccb2322db626d62d970"] + }], + "status": "OK", + "top_hash": "", + "transactions": [{ + "blob_size": 2221, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 44420000, + "id_hash": "88504bd6a72b26bccbc7563efe365baeedb295011a4022089bdc735f508a9412", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261656, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261656, + "relayed": true, + "tx_blob": "020002020010f0bcd533b7d71bdb8915caa004b3a214f99f0993a303fd9804d1f101aa6c870de32a932ab774f80fc92cf64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff020010bc85cd27e4bfb407c598a104bc5e8e8c2d9bfc40add114c0e501b09e04edb204d1a901d2f4019603d50d9f07c4354ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68020003bbb37ea2c935e3c7245150d154748b59bdf280d557779b6cf8063165a7d9b5b9210003ffc23770cf9e4536c0db95978dbc937d1de339cb8dedf909f5adf7ce6fb8c4ea5b2c01d3b65a92cbd04597cc4f3da6a003ca5309af343f2d0f190102fdd7ed13e5b9ee020901d979920cfa70c6ef06a0979715e6d4eb51a032a1511050389f0d8eafbe5f91af7e37907d37de11bae31af65af35e1fabe13cece1d503a9987d7f4813c8ad2b6ef2b5e77281113637ace74d9ac41f16f431a1a49e6d8de1b73fe877c5c301fcaac875ca31629041923a1bb014f86a232227c41e1611f5961a0e095c6201ea34a7b7367b0045c3a841d57ebac2a9b323ab21f6d954f4441fc79fd98414e5c4acdbff571108da31d0face012eb149b24bc16de858b4cb8d96fdc0bb614cc2895a6859cdeb086e647983308714da41be9ada21abfbec1ed4d224315017dacf01c5b2a59d18ac5c3b81c9bfd5c031b9929c1dc802a22593bdea39612039601c0e09f64702dde1507e3daef5655b0f1f32e19fcbcbaeea6b495fed05543cbb65010730de65cd66a314cfdbe7474a387045b3000dd43eedc021ed492075d314da6d8c6a3905275d41cdc8758e258c4a71a64d2ba1aec68b7ad68018aa8fdcf97538898c61392ded8e0715ddd471638be54eda62622f5787cafc577da4ff7dda01578982328c51f59ad3d9218eb0d3201d1136d54e7567e15c3f8bc956772bee20f5976b0f343096ab4a0c2b68099bca4d61eff7a078c91875483213f4cd226b587b5c12bf7a41abc9079e274e6229187f4c3cc1a8579f60f2a8112aafa78eaefa765ed7588be97d471720979fa5b907c5b83be30d62d5a2b0b9a59f1330dface4cddd07f591829caac227efef5fe5076e3fa93dc9a787be8f57c3d2ec216342784321c80b956f44dec2d484500371f9a4fdad1de571f16d2cccca13f2f3bb65718dc4a861276d08d11bc72536b787537aa0b26d68462500baa1b5b47a1ff669346481ac5c0d2199d6197dfc9c74cdd6adf13e06223af430e48bcafce9cd8765ae6411d5a3ff2c8827ca2fb9ec63cfd0c84c2e1cc76d2fb0a3f9619034adc3d0fa60b729fa3352433a1f4f2c7bbb51fc61673b61833f70d8700446442d57e0a6fde600fc1cd0d659f8b6b6ca8e320395b831d2b79b95d006c2fc5afb72635535ce1e953d9e70a0022ac9091cb5810450d72edf9bff63c2b64933e0d69881b6ae9c9bc402b11bcd2ca24cea5171ce4040398ba42f87dec9791ac7376adce1cb47be22bc0ba083395b214bf88c5e81357eb95b461c1ea4c814357b5fa7dda0b7083a3360089af604f25927d738dfb3806a559285f04435a9245356051d27cbb0f5c020f40612a4ed7e2d124a8d6dc7d2e39127f5d66bbcad0bd8af4cf173b89283d09c610ec53ffb5b2e0aaedad8700de5555decda90d9b1f022ec0f1cabe3627ea89bb80420d73a60f7f33886541626e5aa0cb758bc9775a80c2427bd9fd373ce1492f90e93d0cd063f1233975d5ae4d732970c686b21850cd4146e5775af2a3b48f6920f021bc91e59b79200bacd2ebef0e2045ae01d7287f14a3ef08de813492f92c6034a7ef9b7a74660125a9761379d3495eb8cbd5e0461a0ae90ac7bb4dd6ee57902158dab0fbe495663b5536ac17e444ea5d6a5ec67f2145d5ac6eb033fe3ed88023133d514073957b3ab9506b375b52ab4e25df97c81f210d2a5d0ac2fd625c00522ee9cecb7dc0c84a47eef9338472bfa766f0bf5919be6a5b56bf5b84f1be1058d570d7ea8f372c5c253c189f006b314d377610bbc9a41fc162b3df3d860d904c74d0451fccecdcb8c0fdb66f55f10a955f49406f16c6ce397b78af25dcbfa001a1cc63df1dfcd2918dba5e64532af7a24f3a95c722815ad2192f488fe8da9080f8c295fdf955dbfe98666d411605e11598745385d7b639d8aed5b5499ffd007143ff548f1f2956da85253ed716d16f7ed1ba3ed100426e2a81dfa2bdd952f06997389359aef4673cff1fcf634c4261c3f8a028c25712896381ef8e88b53ce0996cd93d9bfc6fa1a578554d1b0767962bbfb88f553d5bb129cf18ed93685b50d60d8e13ef8c06f14e7f4fb212ac28be059f83bd3c375220c4368d405ecc9f601a31d48f081ab49014d562c39b464f850af6679daffddb75935f4bdf2d8735a013df11848f92dca8088339595f99024bb766c19e863175c0234157738925e4c0f4b5a83686667e9711547b3a2a96946fc126a026cdfc477de41e6c85835dad80a1a370f59950b9c9759595425609d6371d41801098202cb87ff96fcfb0247730bb2178497eeb94f794d151fd5393082ec7a0359b409b7508303493f749723780159badacf201cb6bf41691ba3ed894dc4a3b22a3a829ff13a349256379e65b108b53c62247b27176ae5d22295393e1856372f1a89fa7d364173647ba296b76c04950e768eec5f38634e8cf3beff55bd7ce266b5bebe5b854a02cad0307b0b670433bdbf8bc2631430773465cb091c7a666709285816e6d503acd5e649045d3c07baad6c71df6b89a0481a1fe7f45f6aa8837625ccb43ed5f9e8e9f7daf2cdee0e7f1baa61b625d9f5d0c9ee49605d403afa625927e9bf41d7e5d48b454dc1520b19e1c35dd25fc5dff641ea05bc2b6b5697485f96bd3664f90077c567923d4f0404107251310935d78e2d06471962f23277b5207500917d528aeef43e2b670c03e614bd3ee3fd58b486a3d0a2494916785325e6546dec8fb880cc401319a7f90b4d3832853ea6e0ae698543e20975eaa9c6606068f2465bade18d110a6937e199229fc569e4dbb09f253fdc89279b76b70f3bf61d6808d7fa8ff438c795464101a97d68d18f240c9d7137f2db1d38013ba94cf478338fa0353b2a5cbf937f00bb", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 108355184, 453559, 345307, 69706, 332083, 151545, 53651, 68733, 30929, 13866, 1671, 5475, 5395, 14903, 2040, 5705\n ], \n \"k_image\": \"f64056280ede74b3b1fe275cf9b9aa1feda77b3b5fd5218d6a765384e3d180ff\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83051196, 15540196, 8932421, 12092, 738830, 1064475, 338093, 29376, 69424, 72045, 21713, 31314, 406, 1749, 927, 6852\n ], \n \"k_image\": \"4ffd1487bf46e5a1929ca0dd48077cb8ddbff923e74517f1aeb7c54317c0fd68\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"bbb37ea2c935e3c7245150d154748b59bdf280d557779b6cf8063165a7d9b5b9\", \n \"view_tag\": \"21\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"ffc23770cf9e4536c0db95978dbc937d1de339cb8dedf909f5adf7ce6fb8c4ea\", \n \"view_tag\": \"5b\"\n }\n }\n }\n ], \n \"extra\": [ 1, 211, 182, 90, 146, 203, 208, 69, 151, 204, 79, 61, 166, 160, 3, 202, 83, 9, 175, 52, 63, 45, 15, 25, 1, 2, 253, 215, 237, 19, 229, 185, 238, 2, 9, 1, 217, 121, 146, 12, 250, 112, 198, 239\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 44420000, \n \"ecdhInfo\": [ {\n \"amount\": \"e6d4eb51a032a151\"\n }, {\n \"amount\": \"1050389f0d8eafbe\"\n }], \n \"outPk\": [ \"5f91af7e37907d37de11bae31af65af35e1fabe13cece1d503a9987d7f4813c8\", \"ad2b6ef2b5e77281113637ace74d9ac41f16f431a1a49e6d8de1b73fe877c5c3\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fcaac875ca31629041923a1bb014f86a232227c41e1611f5961a0e095c6201ea\", \n \"A1\": \"34a7b7367b0045c3a841d57ebac2a9b323ab21f6d954f4441fc79fd98414e5c4\", \n \"B\": \"acdbff571108da31d0face012eb149b24bc16de858b4cb8d96fdc0bb614cc289\", \n \"r1\": \"5a6859cdeb086e647983308714da41be9ada21abfbec1ed4d224315017dacf01\", \n \"s1\": \"c5b2a59d18ac5c3b81c9bfd5c031b9929c1dc802a22593bdea39612039601c0e\", \n \"d1\": \"09f64702dde1507e3daef5655b0f1f32e19fcbcbaeea6b495fed05543cbb6501\", \n \"L\": [ \"30de65cd66a314cfdbe7474a387045b3000dd43eedc021ed492075d314da6d8c\", \"6a3905275d41cdc8758e258c4a71a64d2ba1aec68b7ad68018aa8fdcf9753889\", \"8c61392ded8e0715ddd471638be54eda62622f5787cafc577da4ff7dda015789\", \"82328c51f59ad3d9218eb0d3201d1136d54e7567e15c3f8bc956772bee20f597\", \"6b0f343096ab4a0c2b68099bca4d61eff7a078c91875483213f4cd226b587b5c\", \"12bf7a41abc9079e274e6229187f4c3cc1a8579f60f2a8112aafa78eaefa765e\", \"d7588be97d471720979fa5b907c5b83be30d62d5a2b0b9a59f1330dface4cddd\"\n ], \n \"R\": [ \"f591829caac227efef5fe5076e3fa93dc9a787be8f57c3d2ec216342784321c8\", \"0b956f44dec2d484500371f9a4fdad1de571f16d2cccca13f2f3bb65718dc4a8\", \"61276d08d11bc72536b787537aa0b26d68462500baa1b5b47a1ff669346481ac\", \"5c0d2199d6197dfc9c74cdd6adf13e06223af430e48bcafce9cd8765ae6411d5\", \"a3ff2c8827ca2fb9ec63cfd0c84c2e1cc76d2fb0a3f9619034adc3d0fa60b729\", \"fa3352433a1f4f2c7bbb51fc61673b61833f70d8700446442d57e0a6fde600fc\", \"1cd0d659f8b6b6ca8e320395b831d2b79b95d006c2fc5afb72635535ce1e953d\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"9e70a0022ac9091cb5810450d72edf9bff63c2b64933e0d69881b6ae9c9bc402\", \"b11bcd2ca24cea5171ce4040398ba42f87dec9791ac7376adce1cb47be22bc0b\", \"a083395b214bf88c5e81357eb95b461c1ea4c814357b5fa7dda0b7083a336008\", \"9af604f25927d738dfb3806a559285f04435a9245356051d27cbb0f5c020f406\", \"12a4ed7e2d124a8d6dc7d2e39127f5d66bbcad0bd8af4cf173b89283d09c610e\", \"c53ffb5b2e0aaedad8700de5555decda90d9b1f022ec0f1cabe3627ea89bb804\", \"20d73a60f7f33886541626e5aa0cb758bc9775a80c2427bd9fd373ce1492f90e\", \"93d0cd063f1233975d5ae4d732970c686b21850cd4146e5775af2a3b48f6920f\", \"021bc91e59b79200bacd2ebef0e2045ae01d7287f14a3ef08de813492f92c603\", \"4a7ef9b7a74660125a9761379d3495eb8cbd5e0461a0ae90ac7bb4dd6ee57902\", \"158dab0fbe495663b5536ac17e444ea5d6a5ec67f2145d5ac6eb033fe3ed8802\", \"3133d514073957b3ab9506b375b52ab4e25df97c81f210d2a5d0ac2fd625c005\", \"22ee9cecb7dc0c84a47eef9338472bfa766f0bf5919be6a5b56bf5b84f1be105\", \"8d570d7ea8f372c5c253c189f006b314d377610bbc9a41fc162b3df3d860d904\", \"c74d0451fccecdcb8c0fdb66f55f10a955f49406f16c6ce397b78af25dcbfa00\", \"1a1cc63df1dfcd2918dba5e64532af7a24f3a95c722815ad2192f488fe8da908\"], \n \"c1\": \"0f8c295fdf955dbfe98666d411605e11598745385d7b639d8aed5b5499ffd007\", \n \"D\": \"143ff548f1f2956da85253ed716d16f7ed1ba3ed100426e2a81dfa2bdd952f06\"\n }, {\n \"s\": [ \"997389359aef4673cff1fcf634c4261c3f8a028c25712896381ef8e88b53ce09\", \"96cd93d9bfc6fa1a578554d1b0767962bbfb88f553d5bb129cf18ed93685b50d\", \"60d8e13ef8c06f14e7f4fb212ac28be059f83bd3c375220c4368d405ecc9f601\", \"a31d48f081ab49014d562c39b464f850af6679daffddb75935f4bdf2d8735a01\", \"3df11848f92dca8088339595f99024bb766c19e863175c0234157738925e4c0f\", \"4b5a83686667e9711547b3a2a96946fc126a026cdfc477de41e6c85835dad80a\", \"1a370f59950b9c9759595425609d6371d41801098202cb87ff96fcfb0247730b\", \"b2178497eeb94f794d151fd5393082ec7a0359b409b7508303493f7497237801\", \"59badacf201cb6bf41691ba3ed894dc4a3b22a3a829ff13a349256379e65b108\", \"b53c62247b27176ae5d22295393e1856372f1a89fa7d364173647ba296b76c04\", \"950e768eec5f38634e8cf3beff55bd7ce266b5bebe5b854a02cad0307b0b6704\", \"33bdbf8bc2631430773465cb091c7a666709285816e6d503acd5e649045d3c07\", \"baad6c71df6b89a0481a1fe7f45f6aa8837625ccb43ed5f9e8e9f7daf2cdee0e\", \"7f1baa61b625d9f5d0c9ee49605d403afa625927e9bf41d7e5d48b454dc1520b\", \"19e1c35dd25fc5dff641ea05bc2b6b5697485f96bd3664f90077c567923d4f04\", \"04107251310935d78e2d06471962f23277b5207500917d528aeef43e2b670c03\"], \n \"c1\": \"e614bd3ee3fd58b486a3d0a2494916785325e6546dec8fb880cc401319a7f90b\", \n \"D\": \"4d3832853ea6e0ae698543e20975eaa9c6606068f2465bade18d110a6937e199\"\n }], \n \"pseudoOuts\": [ \"229fc569e4dbb09f253fdc89279b76b70f3bf61d6808d7fa8ff438c795464101\", \"a97d68d18f240c9d7137f2db1d38013ba94cf478338fa0353b2a5cbf937f00bb\"]\n }\n}", + "weight": 2221 + },{ + "blob_size": 2348, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 56160000, + "id_hash": "9d1bcbdb17d24a4e615a9af7100da671ab34bffc808da978004dcef86ddf831e", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "0200020200108df79b209386c61387df3decb508a9bb05acc3028d9902deaf01bf208d05ac06ac09b014fd05b501de0d45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0020010ace4d528efdb8009c5f8ca01a6f859b4a204c6bc24e3c306d68a01b1e203d401cbc6038103cb61840ad204a40f368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d03000300b4750401a83c37a01ad5e0a9404faacc1356e0b553d17f31e16e8b8570e86cea0003befd3d4f80d897085c68417d4af03a4c2520b5ff75c400d1d018ec0d4617e6770a00037b2e69e0187086e600aa2ee921c61ff6847ebf80512e5f86c0500a889da2e8205c210147aa7f30ddab6c818f008f074a4a20528d522fbed0be3f581a8f574e697876a00680dee31a0a24de88aa0fcb7e567bad3c08c3a7247b06d68e46efce9940d6036d4b2b7e03c383c26989ac5176cef29aa6592d688747a7bc1989d88311aaad63d60db25d64d04ed7d40ebbbd4b6adeff521ddc60c9b00594381c89b9d9e4afabcff8a906fe5120b70bd38328753d4cee997f92552087ba220aa427758f010c8c7c9eb2e43443be439372d50e3cad3141de924fa118bad635f8105a086eb741d4b609c9a7fca073984dd6b9561b8a5dd0bc1fefd32a839ba25fa34b2c3d5021cd4b2157a936ecd28cbd4ad243876c84a0c09447b0ecfcf216c7c9f7ae6ec8c58d694f185dc9c4d4115e5d8d58dbffec407c39cd455c5842410c92ff9c0107359836b8b462f81e20852f2b14ad81c2931a6bc41097d824d175310f15d7890a99ac8ab0cf3e9cf0048da193a3a7706c824dbc98d3be3c7aee69d296c519380d085c64adc25186867f568afbd0ab351aeaebda94ff0832ea71771c255ff165669495211b42b692e6f6f745365f45baa6af535b6bf4868eb9699d7fd7b5d8a00a4c35bb4a24827cfc8d6f77f56a907a20dc80437a3f9cd3a4a889ad30eae65bfe3481af3a7f398933896be7bb06bce96c64113c3da8732c4f28f0a83b12ebc7ec329627128e8f6332b0c89fc1f18850786f594a7ef566a2970a4c308d5a0a7649dd81890916da41de7d8f74349fdea603f529d6aa77747ef2bda70d02688b4f121399f25ddbed7f3d2fe2cb136daa069b3548c9f0736057b68e249165662f8782f6cdbfe3e6a9eee2623dcb25eb492ad6ad825ae9917453c2772643e8bcb44b8122085661c1ea7185fdabc273bd62dffd938e1bee1b95c6a5940900331254bdb7f0361d66af8aa68615444bc98631b9cbee7ad6fb2c64fa92e42b472b7098acf2564206e152560f919105aee65e4116fd730f6201e639b585a0886262aef0e65db50b3413e399527b6a031081a272c0bdc89516b55107d186038fd8b2641690dbed6148e3084de6599e25bda1c719290dc31742b6863d3fcc57980ccb808215dd17a4b949f725231f2c29eabf7487e7c93bdf92cc5786c5614e77b2d2d10d9b9ccd475a2bf8992c52219480706e5676b40c203ee6799bca4636aeb7011875eb13dc2da7e6a777c7ccf246a19195e7aa4d7a617f0e5b7797a13228abfa6d288799b17702e263aa90ab2883591877afd734279ccf3442e40437401e4a6875b6603bb10db2503ff62c27a6ee89ba54efff4a15c149aae234cbdd165b6acd87f8f68d0408c3357e22b6ac83ea40186091d41ee82a467e6eedfb81065e1a6261493572820bb3a02732c48afe03a04344e108a16c6ed9b57f8bc70c0373c14e02aa58c39d081b9d63df16411a24065419e149dd5cf0d16c361d52077e372668658217ae0008052f944bf07460dc2650b271abbd3470f9d9f1151915b47f4e843c2cb66e8104b954b9b11711f668d700a7e8e73bba0e1f3325c7ee168c2e52fbbc42a9a37c0a5acfbd247117d89d3dec678cd2f1e5a358adba45cd96eb83bdefa574fe7ee70462e723e878f558b4ff133353015d12039d88ff4e4a42542274a29eec9d48f606f819b255f796a383db4db031d966f0512c0be65bcb570be40c5afeb301ced60bbd96cb11101317b582c5a4938f4b42aa4e4764923917fe03a849783821052701b601b95768bf304145239a0dcad54850a49431706b58139f92ab1aff4083ce021da8fbb5e19ef43bdcb40a61b67d615fdcc7d1ef9937efe0f64c65a6fc7d3a0f656bc19201f87d279c7dec96160b8cd0001f5064609b3561259728e19b5da20e57181f2d5e36ab9edc5fe93b71fde30f262d4e60525df697dcd66d45f079da0e5d95974c8bff7e89d06a64c25714eaa1b65522218c996c7ddb6cf902519f340524e084e6b195f994bc9be4cbfd8e6e9c5a1e00551148e1f8b6c154b2115b1f088c5ab9747f5d6f49a819f0eb06a4277090292c3b14f219196d91a3d8333b11278b5ca04440eb42f46cca567d8a2c3e9fd2880078544e47a6e932a828b494a7056038560559de1bdefeda1e9da672b6e931f22d530a7dd7ff8586184b5d593d0ebed9c5263818225fb9db1e64cbfc0174f872a3ea5e92f314c87b96daa8e7a400742ea90a1c357194d610b998a0c07dbad2bd2541158439be096b8f59ad7dcc073345072ecdb181af27eecaa21642d23abca25c1cd6a45daec5d423c8c002f60a99e4218d23ae7bd8055f9d473a8de7a36d5c00c64eea4be99c751655ab904604992a602c49416d56179835e3c6c5d0e89aca53a372dc61cfd9d105c7c0bdeb02765e41608cec6275ba3b7c19f2dcecd8cf7c02eed764b953066535c9395ba50ea8e8223b185992e646d89d4ab71009b3244dd2c2949d98557b21ad8cd2f03d0f140613a8e1366fe9d73e2282e471274b7304b94dc5a99a84411c5504b86176042351e695642aa3608fb3c413e7221b153d2ba3c0f8adca0d9636b3f95922ed040c19b92c35505041a39b8c9492c08577c9689bebbf1955827ab7298779d9d50b861f800ae0baa6761569a92e7b74d2d9dde4d8c98ffb37737edba691f4c94a0c78ef2c281ffe9c1d7f290cc7c60efd992f2b436c73b04f920984a1e8b8256a053d938f9f385b823aeee4f760e7c4346a3be6921700c433664d6806deaa3a6c039a1dc227d0fb1d07b26156b4b998053b2d07e037c024eb305212852edea364010936723eed3db539fd792c5acfe56bf2444998a107cff83eb7e37504b05c990f78111416918e784305b77030f0e2a14124afce9bf4d285c09a77264b2cf85e7fe99253575d3a7dbca07223afc25c085bc2e45aea9b399d58339b4ac1edfcd5e9ef14d6f64ab94d4f94eca4765dfabac9c91a423283387f51301a31d35de5b61d", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 67566477, 40993555, 1011591, 137964, 89513, 41388, 35981, 22494, 4159, 653, 812, 1196, 2608, 765, 181, 1758\n ], \n \"k_image\": \"45a88adb7fcac982f5f4d8367f84e0f205235f58ad997f5dfa4707192fd3d9e0\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 85291564, 18886127, 3324997, 1473574, 69940, 597574, 106979, 17750, 61745, 212, 58187, 385, 12491, 1284, 594, 1956\n ], \n \"k_image\": \"368fbc77179fb30bf07073783f6ef08bfb1a8c096e9bd60bb57aead3b0f3663d\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"00b4750401a83c37a01ad5e0a9404faacc1356e0b553d17f31e16e8b8570e86c\", \n \"view_tag\": \"ea\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"befd3d4f80d897085c68417d4af03a4c2520b5ff75c400d1d018ec0d4617e677\", \n \"view_tag\": \"0a\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7b2e69e0187086e600aa2ee921c61ff6847ebf80512e5f86c0500a889da2e820\", \n \"view_tag\": \"5c\"\n }\n }\n }\n ], \n \"extra\": [ 1, 71, 170, 127, 48, 221, 171, 108, 129, 143, 0, 143, 7, 74, 74, 32, 82, 141, 82, 47, 190, 208, 190, 63, 88, 26, 143, 87, 78, 105, 120, 118, 160\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 56160000, \n \"ecdhInfo\": [ {\n \"amount\": \"0a24de88aa0fcb7e\"\n }, {\n \"amount\": \"567bad3c08c3a724\"\n }, {\n \"amount\": \"7b06d68e46efce99\"\n }], \n \"outPk\": [ \"40d6036d4b2b7e03c383c26989ac5176cef29aa6592d688747a7bc1989d88311\", \"aaad63d60db25d64d04ed7d40ebbbd4b6adeff521ddc60c9b00594381c89b9d9\", \"e4afabcff8a906fe5120b70bd38328753d4cee997f92552087ba220aa427758f\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"0c8c7c9eb2e43443be439372d50e3cad3141de924fa118bad635f8105a086eb7\", \n \"A1\": \"41d4b609c9a7fca073984dd6b9561b8a5dd0bc1fefd32a839ba25fa34b2c3d50\", \n \"B\": \"21cd4b2157a936ecd28cbd4ad243876c84a0c09447b0ecfcf216c7c9f7ae6ec8\", \n \"r1\": \"c58d694f185dc9c4d4115e5d8d58dbffec407c39cd455c5842410c92ff9c0107\", \n \"s1\": \"359836b8b462f81e20852f2b14ad81c2931a6bc41097d824d175310f15d7890a\", \n \"d1\": \"99ac8ab0cf3e9cf0048da193a3a7706c824dbc98d3be3c7aee69d296c519380d\", \n \"L\": [ \"5c64adc25186867f568afbd0ab351aeaebda94ff0832ea71771c255ff1656694\", \"95211b42b692e6f6f745365f45baa6af535b6bf4868eb9699d7fd7b5d8a00a4c\", \"35bb4a24827cfc8d6f77f56a907a20dc80437a3f9cd3a4a889ad30eae65bfe34\", \"81af3a7f398933896be7bb06bce96c64113c3da8732c4f28f0a83b12ebc7ec32\", \"9627128e8f6332b0c89fc1f18850786f594a7ef566a2970a4c308d5a0a7649dd\", \"81890916da41de7d8f74349fdea603f529d6aa77747ef2bda70d02688b4f1213\", \"99f25ddbed7f3d2fe2cb136daa069b3548c9f0736057b68e249165662f8782f6\", \"cdbfe3e6a9eee2623dcb25eb492ad6ad825ae9917453c2772643e8bcb44b8122\"\n ], \n \"R\": [ \"5661c1ea7185fdabc273bd62dffd938e1bee1b95c6a5940900331254bdb7f036\", \"1d66af8aa68615444bc98631b9cbee7ad6fb2c64fa92e42b472b7098acf25642\", \"06e152560f919105aee65e4116fd730f6201e639b585a0886262aef0e65db50b\", \"3413e399527b6a031081a272c0bdc89516b55107d186038fd8b2641690dbed61\", \"48e3084de6599e25bda1c719290dc31742b6863d3fcc57980ccb808215dd17a4\", \"b949f725231f2c29eabf7487e7c93bdf92cc5786c5614e77b2d2d10d9b9ccd47\", \"5a2bf8992c52219480706e5676b40c203ee6799bca4636aeb7011875eb13dc2d\", \"a7e6a777c7ccf246a19195e7aa4d7a617f0e5b7797a13228abfa6d288799b177\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"02e263aa90ab2883591877afd734279ccf3442e40437401e4a6875b6603bb10d\", \"b2503ff62c27a6ee89ba54efff4a15c149aae234cbdd165b6acd87f8f68d0408\", \"c3357e22b6ac83ea40186091d41ee82a467e6eedfb81065e1a6261493572820b\", \"b3a02732c48afe03a04344e108a16c6ed9b57f8bc70c0373c14e02aa58c39d08\", \"1b9d63df16411a24065419e149dd5cf0d16c361d52077e372668658217ae0008\", \"052f944bf07460dc2650b271abbd3470f9d9f1151915b47f4e843c2cb66e8104\", \"b954b9b11711f668d700a7e8e73bba0e1f3325c7ee168c2e52fbbc42a9a37c0a\", \"5acfbd247117d89d3dec678cd2f1e5a358adba45cd96eb83bdefa574fe7ee704\", \"62e723e878f558b4ff133353015d12039d88ff4e4a42542274a29eec9d48f606\", \"f819b255f796a383db4db031d966f0512c0be65bcb570be40c5afeb301ced60b\", \"bd96cb11101317b582c5a4938f4b42aa4e4764923917fe03a849783821052701\", \"b601b95768bf304145239a0dcad54850a49431706b58139f92ab1aff4083ce02\", \"1da8fbb5e19ef43bdcb40a61b67d615fdcc7d1ef9937efe0f64c65a6fc7d3a0f\", \"656bc19201f87d279c7dec96160b8cd0001f5064609b3561259728e19b5da20e\", \"57181f2d5e36ab9edc5fe93b71fde30f262d4e60525df697dcd66d45f079da0e\", \"5d95974c8bff7e89d06a64c25714eaa1b65522218c996c7ddb6cf902519f3405\"], \n \"c1\": \"24e084e6b195f994bc9be4cbfd8e6e9c5a1e00551148e1f8b6c154b2115b1f08\", \n \"D\": \"8c5ab9747f5d6f49a819f0eb06a4277090292c3b14f219196d91a3d8333b1127\"\n }, {\n \"s\": [ \"8b5ca04440eb42f46cca567d8a2c3e9fd2880078544e47a6e932a828b494a705\", \"6038560559de1bdefeda1e9da672b6e931f22d530a7dd7ff8586184b5d593d0e\", \"bed9c5263818225fb9db1e64cbfc0174f872a3ea5e92f314c87b96daa8e7a400\", \"742ea90a1c357194d610b998a0c07dbad2bd2541158439be096b8f59ad7dcc07\", \"3345072ecdb181af27eecaa21642d23abca25c1cd6a45daec5d423c8c002f60a\", \"99e4218d23ae7bd8055f9d473a8de7a36d5c00c64eea4be99c751655ab904604\", \"992a602c49416d56179835e3c6c5d0e89aca53a372dc61cfd9d105c7c0bdeb02\", \"765e41608cec6275ba3b7c19f2dcecd8cf7c02eed764b953066535c9395ba50e\", \"a8e8223b185992e646d89d4ab71009b3244dd2c2949d98557b21ad8cd2f03d0f\", \"140613a8e1366fe9d73e2282e471274b7304b94dc5a99a84411c5504b8617604\", \"2351e695642aa3608fb3c413e7221b153d2ba3c0f8adca0d9636b3f95922ed04\", \"0c19b92c35505041a39b8c9492c08577c9689bebbf1955827ab7298779d9d50b\", \"861f800ae0baa6761569a92e7b74d2d9dde4d8c98ffb37737edba691f4c94a0c\", \"78ef2c281ffe9c1d7f290cc7c60efd992f2b436c73b04f920984a1e8b8256a05\", \"3d938f9f385b823aeee4f760e7c4346a3be6921700c433664d6806deaa3a6c03\", \"9a1dc227d0fb1d07b26156b4b998053b2d07e037c024eb305212852edea36401\"], \n \"c1\": \"0936723eed3db539fd792c5acfe56bf2444998a107cff83eb7e37504b05c990f\", \n \"D\": \"78111416918e784305b77030f0e2a14124afce9bf4d285c09a77264b2cf85e7f\"\n }], \n \"pseudoOuts\": [ \"e99253575d3a7dbca07223afc25c085bc2e45aea9b399d58339b4ac1edfcd5e9\", \"ef14d6f64ab94d4f94eca4765dfabac9c91a423283387f51301a31d35de5b61d\"]\n }\n}", + "weight": 2808 + },{ + "blob_size": 2387, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 116500000, + "id_hash": "dbabb82a5f97d9da58d587c179b5861576144ea0cd14b96bef62b83d8838f363", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010df97d533d1c51185e031f69e08adce05b0aa06fb8a03b137895afb14c08e02b0549c1af60da305cb1f6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf0b0003e205de62fdbf4ff1b6554e3180bfd09051e368d70a5739bc039d864eea9d355da60003dacd0606c42ee249c304393e3169de7baacc28a9ff94240b603c54b0238d542d11000324dffd0f9dfb98b459a463f1c62df0d5ec76ad3781c41d5a65bdce94dc43aa5e5b0003631aff9f6291c9890f02a667c976b7e13bce27d443d00bda47d72d32ca2eacff40000360ba12b274ebbfe8f74abfe132a02a4a91e96f2f5db3b5772e5bab2e1066015959000348e42fd446643b5ab32024d193050ad976173b0ce43cc33add718167b0d99b33ac000356a51fe30876666e834c9741ee4e4fcbd08372005319f58cdc1a46bd98db5dec4b0003421637f078091694b19f1d2d3691a2ba8fe38198a808d0bd38f4202270d5b47dbb0003b42ff611a9b780b8e4afc2958d059013c55512102b3f6d7b6f30f4da75c5bbbb340003c3253ebf1a9125a52b9a7d4fca7d29cc41c701aefd7b8d5caa6ca02a128a0433cf00030fbe7d06b657e84ffa29bcb937a649a647cc1e967fdeb3a1c7a00f2e72faddbbaf21016b391fa654bc1e4141eec1d8a55bfbe363d9632b56dd4c7393b7b1f8934932e506a0ccc6376dad44ee6f7e9686a70f600a18341bd60dcbe99a71bc8a05eb8f5ad01274c1590bb0e0848ccd0872e311b24115067be37fa3244c7785ce295d3eaff88e7bd50450bcfe9aebd902dbf3fd4d3d5f5deaa8f5517e89834007556c99f923978cb868d155d761085d03d11684d6d75b79e461da935fe8b8ccff02aaba98f24626e21abd6b7cee23ff35c800e99b7b8719b7adcf2987f61a51c7e81728cfab8ac5ec5fa22a10dbd58ae64976b188860a88ea9a7ea3ce49cc5460168c355de58788139c74011a95e49b0d91e6e033be4f212573cae4ae8b77c7f59e727f4ff9ef5045de7a733c53eb98c57bf4c643a1183da886f66d7984e4559192c1de69533b7777e5da4290431063f42b9f900f82564deffb967e214eb22c6835fda6a27569c7211b2ccf9602046049c5b72ed13a70193cec07dc1336b609c575b4dd58693376a102246f13508d44c88dec158b3a9b263bf8557818979f7a833f758602ffc62037d78994082ea998cd1b4944ebed5a3cba2f3e19dcc7f9bfc96901a60c2293b2949b0e9d3b84260d2f6085c628ba1a0fbfaab1611bdad0ec39ba599342f1de56d2beb0930aea3ef3f446ebf52bf5c0cc89566a68ca68ad05f9cb012acf619b5da2f005a99b98d6ab1244a4458435fdd4ba5aabab8884eede69ee25d063c5e054a0763bd8e6cad5859a1f54d5d280af664cd2b3ab6a8ba944e2858bc9979f63b12e75b3a647fcb31e8796502947ada7d65526c74f70e2ff4820e599a03200a8bc6475cde069c7e90a9fda27d204a8c01c10706f1fec6c791d6e6d05707f92d85119d8b54b5818e894ddc4e5ce45612e601094aca90fac825b8f13060b9cf42dc96310520835ddff845dd782508fd8b73d99613d425323b206d3ae060a6ae9c2bf3c383f11392df4b0cdb537c62e618cd97de259135abc4de9fc3fa1e9d64eaa642cdd7d6a6e3f2208ed76771b2be4a2416e53c15f1e3bd7eee3ec3e92f6ceb61873e03d5ac5f2bf583fb5f86453310e232a2620a6bfa4cfb4981b533b8625b08e0938291ba52a9850629e3097bfef1b3764c73643ade61a8d425d3f2da30df56d9fd7c736efe223b158ed49114e1eb8e616a2ef47d673c8ed52b2d94c5c25ebab35e7e0512aa8d9a48c01ece39a0dd29125cafa904e1a33cca56d64ea06551895fa1f1691674216e56eca2ac8ba84da58eac436746fe39e028815dd812f19d8236dadccbc23378bece2a78a8cf6ce42a40d7e97ac095f2fbe3c769eb5674359a42c69eba31b8952c54fcc90ef3f141acb32d9f4f7d88c0d416d541aa2300560059f8f8ed209d935b3e26185f387ebc5f0ac4a279b661a97f13da023010afcc6f7d3542ee9511acf4ff6782d5f516ebcb08f76ef089cfbca6012001893bb8ef584c963ec50d1249f063d3a138a836e98fd3c845b15d97e819e60fc8b7298bb52ac2e31690570e777e3a18b0d2d30e9219d4c53ad018c05f9150963045dfacd472e1d89929f4df50429794c4214bd02cf7ef7a1dbac9bd7d882748c36ade1c99a664e1ebf3b88c9f2af6b0facadce56c11ee6e7f9bb642d612952642a2e584bc487bae947cac5301fb28e8635b282d5aeb999ac21fe3060a351649c00ed98646e663edab73c1d545771f1e85919b2d89ba44149a086e188ecaec21f5ecebe04198c506cae6da01a2413e414d2aaa0309f16c8afb17b146577f41fc383957d8ab02506d67795f41c6709c5cba79a14fa32842c4e38e9d97b0c69500f9dffcf78e6aa8232f46e7f6b2e402d0a5ee548a0e503be9b789a08803e0970f2d745249fbc34dc464b3ae4d5ba8b98946e3201fb1d9185507aae2a77e129394c8b64071d658e5eff25510bd2becae400f2db0a865a4a6fd55fbe6ea016f1370eab2d0090eddd7545194e0b3fce7cab06c968a6bd17cc848f5d3d20a26f5dccd004e803839d366680832ed1290ed6469f0a75a5ef9a35c426381a9372d4f0cddd77370d506ec1d7573950192844f4f3fe1089077dc6faa84d6513eb75621f5ade8eff0bceba99fa0b988daa13fe0c7fd15d85f050a339d447581c8459db9833952e710ae6c97b50495765c8d034d821da26bb950c6d379b923ba2482ae700098579400f3337c5898f002ab7832e258e3ead08da9c71bcde6449a5cacd506b6977549b08ffcdd6d90ce4f0b06e36f0b1c61055211284292df52d88532ade8962e3679f0e313298b6a931e790e641d5176247e5dcfa715134624e4bca5dfd92259cacd907014bf9138373942890180ea291a80e2594d0c062d1826dce12d4167bb10cd30a90cd211a857b80f7c1887c1ce14aa8124ede4b989f3520cce7011978b955cf030c76902a5b22867f9c3d47e8523c9cfd8a4d97ad2592f1debea8c30a7856bf04a435b617fd6b6a78ee5bd65e354f1b6ca5a21fc66d399e4debb8c2a97acc07048967d4c0fa15746f102312f0fd1f8ea851ee133282cba0f1f439408f2742df0af040c7e3ec8e5a0deeb6e952316fa300d986c29986cfb6293b90f5dac8a9760e2c8178c818aa7a44292f3129578d6bead21638d57b020709f39379f3b579cc0ae6aa3044eb12eb2487b34119b5f3ed92fc020406eaa991cf0a51c60cb1b7789afa8610e6d02955b7063068ebc5b481d70f39e570e880cd8551eaa333670fb213", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 108350431, 287441, 815109, 135030, 91949, 103728, 50555, 7089, 11529, 2683, 34624, 10800, 3356, 1782, 675, 4043\n ], \n \"k_image\": \"6d80d9c12f1439b0a994f767d71d98d2d2cde1a54c6a6134a00c2f07135d98cf\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e205de62fdbf4ff1b6554e3180bfd09051e368d70a5739bc039d864eea9d355d\", \n \"view_tag\": \"a6\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"dacd0606c42ee249c304393e3169de7baacc28a9ff94240b603c54b0238d542d\", \n \"view_tag\": \"11\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"24dffd0f9dfb98b459a463f1c62df0d5ec76ad3781c41d5a65bdce94dc43aa5e\", \n \"view_tag\": \"5b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"631aff9f6291c9890f02a667c976b7e13bce27d443d00bda47d72d32ca2eacff\", \n \"view_tag\": \"40\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"60ba12b274ebbfe8f74abfe132a02a4a91e96f2f5db3b5772e5bab2e10660159\", \n \"view_tag\": \"59\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"48e42fd446643b5ab32024d193050ad976173b0ce43cc33add718167b0d99b33\", \n \"view_tag\": \"ac\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"56a51fe30876666e834c9741ee4e4fcbd08372005319f58cdc1a46bd98db5dec\", \n \"view_tag\": \"4b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"421637f078091694b19f1d2d3691a2ba8fe38198a808d0bd38f4202270d5b47d\", \n \"view_tag\": \"bb\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b42ff611a9b780b8e4afc2958d059013c55512102b3f6d7b6f30f4da75c5bbbb\", \n \"view_tag\": \"34\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"c3253ebf1a9125a52b9a7d4fca7d29cc41c701aefd7b8d5caa6ca02a128a0433\", \n \"view_tag\": \"cf\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"0fbe7d06b657e84ffa29bcb937a649a647cc1e967fdeb3a1c7a00f2e72faddbb\", \n \"view_tag\": \"af\"\n }\n }\n }\n ], \n \"extra\": [ 1, 107, 57, 31, 166, 84, 188, 30, 65, 65, 238, 193, 216, 165, 91, 251, 227, 99, 217, 99, 43, 86, 221, 76, 115, 147, 183, 177, 248, 147, 73, 50, 229\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 116500000, \n \"ecdhInfo\": [ {\n \"amount\": \"6dad44ee6f7e9686\"\n }, {\n \"amount\": \"a70f600a18341bd6\"\n }, {\n \"amount\": \"0dcbe99a71bc8a05\"\n }, {\n \"amount\": \"eb8f5ad01274c159\"\n }, {\n \"amount\": \"0bb0e0848ccd0872\"\n }, {\n \"amount\": \"e311b24115067be3\"\n }, {\n \"amount\": \"7fa3244c7785ce29\"\n }, {\n \"amount\": \"5d3eaff88e7bd504\"\n }, {\n \"amount\": \"50bcfe9aebd902db\"\n }, {\n \"amount\": \"f3fd4d3d5f5deaa8\"\n }, {\n \"amount\": \"f5517e8983400755\"\n }], \n \"outPk\": [ \"6c99f923978cb868d155d761085d03d11684d6d75b79e461da935fe8b8ccff02\", \"aaba98f24626e21abd6b7cee23ff35c800e99b7b8719b7adcf2987f61a51c7e8\", \"1728cfab8ac5ec5fa22a10dbd58ae64976b188860a88ea9a7ea3ce49cc546016\", \"8c355de58788139c74011a95e49b0d91e6e033be4f212573cae4ae8b77c7f59e\", \"727f4ff9ef5045de7a733c53eb98c57bf4c643a1183da886f66d7984e4559192\", \"c1de69533b7777e5da4290431063f42b9f900f82564deffb967e214eb22c6835\", \"fda6a27569c7211b2ccf9602046049c5b72ed13a70193cec07dc1336b609c575\", \"b4dd58693376a102246f13508d44c88dec158b3a9b263bf8557818979f7a833f\", \"758602ffc62037d78994082ea998cd1b4944ebed5a3cba2f3e19dcc7f9bfc969\", \"01a60c2293b2949b0e9d3b84260d2f6085c628ba1a0fbfaab1611bdad0ec39ba\", \"599342f1de56d2beb0930aea3ef3f446ebf52bf5c0cc89566a68ca68ad05f9cb\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"2acf619b5da2f005a99b98d6ab1244a4458435fdd4ba5aabab8884eede69ee25\", \n \"A1\": \"d063c5e054a0763bd8e6cad5859a1f54d5d280af664cd2b3ab6a8ba944e2858b\", \n \"B\": \"c9979f63b12e75b3a647fcb31e8796502947ada7d65526c74f70e2ff4820e599\", \n \"r1\": \"a03200a8bc6475cde069c7e90a9fda27d204a8c01c10706f1fec6c791d6e6d05\", \n \"s1\": \"707f92d85119d8b54b5818e894ddc4e5ce45612e601094aca90fac825b8f1306\", \n \"d1\": \"0b9cf42dc96310520835ddff845dd782508fd8b73d99613d425323b206d3ae06\", \n \"L\": [ \"6ae9c2bf3c383f11392df4b0cdb537c62e618cd97de259135abc4de9fc3fa1e9\", \"d64eaa642cdd7d6a6e3f2208ed76771b2be4a2416e53c15f1e3bd7eee3ec3e92\", \"f6ceb61873e03d5ac5f2bf583fb5f86453310e232a2620a6bfa4cfb4981b533b\", \"8625b08e0938291ba52a9850629e3097bfef1b3764c73643ade61a8d425d3f2d\", \"a30df56d9fd7c736efe223b158ed49114e1eb8e616a2ef47d673c8ed52b2d94c\", \"5c25ebab35e7e0512aa8d9a48c01ece39a0dd29125cafa904e1a33cca56d64ea\", \"06551895fa1f1691674216e56eca2ac8ba84da58eac436746fe39e028815dd81\", \"2f19d8236dadccbc23378bece2a78a8cf6ce42a40d7e97ac095f2fbe3c769eb5\", \"674359a42c69eba31b8952c54fcc90ef3f141acb32d9f4f7d88c0d416d541aa2\", \"300560059f8f8ed209d935b3e26185f387ebc5f0ac4a279b661a97f13da02301\"\n ], \n \"R\": [ \"fcc6f7d3542ee9511acf4ff6782d5f516ebcb08f76ef089cfbca6012001893bb\", \"8ef584c963ec50d1249f063d3a138a836e98fd3c845b15d97e819e60fc8b7298\", \"bb52ac2e31690570e777e3a18b0d2d30e9219d4c53ad018c05f9150963045dfa\", \"cd472e1d89929f4df50429794c4214bd02cf7ef7a1dbac9bd7d882748c36ade1\", \"c99a664e1ebf3b88c9f2af6b0facadce56c11ee6e7f9bb642d612952642a2e58\", \"4bc487bae947cac5301fb28e8635b282d5aeb999ac21fe3060a351649c00ed98\", \"646e663edab73c1d545771f1e85919b2d89ba44149a086e188ecaec21f5ecebe\", \"04198c506cae6da01a2413e414d2aaa0309f16c8afb17b146577f41fc383957d\", \"8ab02506d67795f41c6709c5cba79a14fa32842c4e38e9d97b0c69500f9dffcf\", \"78e6aa8232f46e7f6b2e402d0a5ee548a0e503be9b789a08803e0970f2d74524\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"9fbc34dc464b3ae4d5ba8b98946e3201fb1d9185507aae2a77e129394c8b6407\", \"1d658e5eff25510bd2becae400f2db0a865a4a6fd55fbe6ea016f1370eab2d00\", \"90eddd7545194e0b3fce7cab06c968a6bd17cc848f5d3d20a26f5dccd004e803\", \"839d366680832ed1290ed6469f0a75a5ef9a35c426381a9372d4f0cddd77370d\", \"506ec1d7573950192844f4f3fe1089077dc6faa84d6513eb75621f5ade8eff0b\", \"ceba99fa0b988daa13fe0c7fd15d85f050a339d447581c8459db9833952e710a\", \"e6c97b50495765c8d034d821da26bb950c6d379b923ba2482ae700098579400f\", \"3337c5898f002ab7832e258e3ead08da9c71bcde6449a5cacd506b6977549b08\", \"ffcdd6d90ce4f0b06e36f0b1c61055211284292df52d88532ade8962e3679f0e\", \"313298b6a931e790e641d5176247e5dcfa715134624e4bca5dfd92259cacd907\", \"014bf9138373942890180ea291a80e2594d0c062d1826dce12d4167bb10cd30a\", \"90cd211a857b80f7c1887c1ce14aa8124ede4b989f3520cce7011978b955cf03\", \"0c76902a5b22867f9c3d47e8523c9cfd8a4d97ad2592f1debea8c30a7856bf04\", \"a435b617fd6b6a78ee5bd65e354f1b6ca5a21fc66d399e4debb8c2a97acc0704\", \"8967d4c0fa15746f102312f0fd1f8ea851ee133282cba0f1f439408f2742df0a\", \"f040c7e3ec8e5a0deeb6e952316fa300d986c29986cfb6293b90f5dac8a9760e\"], \n \"c1\": \"2c8178c818aa7a44292f3129578d6bead21638d57b020709f39379f3b579cc0a\", \n \"D\": \"e6aa3044eb12eb2487b34119b5f3ed92fc020406eaa991cf0a51c60cb1b7789a\"\n }], \n \"pseudoOuts\": [ \"fa8610e6d02955b7063068ebc5b481d70f39e570e880cd8551eaa333670fb213\"]\n }\n}", + "weight": 5817 + },{ + "blob_size": 1664, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 42480000, + "id_hash": "3fd963b931b1ac20e3709ba0249143fe8cff4856200055336ba9330970e6306a", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261660, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261660, + "relayed": true, + "tx_blob": "020001020010cfc19c31d9c65acfdb1bcfb91dd6e00aa7c2b601e2b51489e01ab3cd038e99039aa705c0f806ba2ffc1da40ba002913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b030003ad696cce55ac392f061b7e0f1acccbd17cbdfc11d20805df2ee8efb087476ed59c000357483f9fa7379f9b72e6149fd2b319c4e60653a627d0107d73f5604159d597ebf000034360e4f9cfb1109148e0b8428c36fa62270e4aa68e6aabd768270363b53a195acc210173c64bf5cd3e9f401a3800c9624c45f8e9408163348c4e2c473b88f610874eff0680e3a01479bce24f3573dbafaae47cc85d8723551119c984e226cab7daa8193188e5b32952ea0a464812c5f1189db475fe78ad4a6351e7d335a79163d9bd1c7b680e62a0ef36e83b122965bf6042bef9cf535da9286ea586681c4bef225c49f8f54b6b1a92a4a2d53c699fd021e1d2cf9ae75729ba7cf244a0264cf901fd2a5b8b2a2016880735d531b55fa6e206cc0c0d5177c93d10e2f60fadde3bebebf7ff3558e9949869a8401f4174a6e2e6092074d44b2665008391bcef3be3b38dcbb6f45f57379ba40acdffb81ffc8a3d312c6b728eff17a8c8f15d49f1517aca615e2f8bc5257f7b7a27cd455c633a400b6ff63f58d7c66c1172e1e56ef30e4edf1f17a43bd06754bcc657391acef09cbc15d6b827e61cfa8f6e33a4f7ce0837c0edf5a192c739ca6c8da976af9a1fa2ca1649024639f6c26e09dd01489b0a08e64e75e0dd89bd999cdaf72f1334263b2daa2b27b593951c8ea7781d7f4479fa8b24c4e3e7d02de85eb0144aa0e26be30c4a283db27c5b488ee4895345f81a51b54741caf457eef595c9753aa58d82d45182325aea6d77198090e8e14fd413620fcb3a7750dcb87178431ed8b4b23f39fd893c1e500dce31de0bf1861540e212e1263d567951d7c73e3f082a9fd7086aba9b58fbbbf61783fda9d037afa96250fe234734c186422495f5d5db92690958e43e5fd5ec5e302537d3353950f5a8e2a65a13ea58137481ebc02768f12ba06d4c3a58cc3f2725567a544434acc7934c87eff281268d9495670dc73032cab96056aeeda57fb16e153074dbe5b490ee2608d4ee6684886fec29e4f03741b6cfeb7bf468eca760fdf20085df50309f6942585ded84a963d18448029d3990a43ad3ba730b70193b7195961375390863ef0e721f26495ea684c129a7bfe0b58837269c2f3b9ed5697b4e5f50205e1699086db0d89e441c287e7aff567135d73a6724f206a15909cf4505c99a1ece8d2d44197ec9c2d28ca55008ba1819f7092e694e0048502dd937674b825e8325ccf06d2257e50dccf6d450f07a040def2b0af67d7a9ca8d54179bf474b11f3e0342b92e6f0d5c2c5b01dd7769f3651c5a5b7e4af34428cfd9429ecf373cc409db223a3f81075b8d10b0e409336f9922b0acc60780ed8c7e6916eafa813e486f678133c7c287040847e31fae2ed636fc2830394ecfef0c93ae4a380394907018dfdcf9814083efcaee92919e11f36ec098917436902e5752d62d43e054178b9cb6a66e7d4087b234f9f3fff46cdec0fd531ab50b82ffbbcf22171250fd8ce8015e6ccb940022ee8c5d7a0d10186b416c51c3370b0300f54651accfbebd3ac3e4a22ebbf2409b5e7ebc40dae1be188c5208668d6216d1cdb0552541c27fc6b9f770aa6fa2e0f50eedef2110162cd67b027348beb3865d40b71dfcd4bb02caa5d85454868f8071d6fda35f3729b3fdf7041ae02302733ed00dafdbb91e81d304daa7a99e0350ed8967cea2637fddda63bb273ed406be7ee694bff1fb5a0cdbf792fe25da78d0be68bff0add3f8868ece66332a446415da39994ed247606a7951ccbe639006008a2796c33bb8d53f64de030b7618deac7e15fc6b53bea8f04b30d244007e6b805a5c6a79cfa5e75c89eb96b298fe5f3722c9ab5219b839a6a88999d288913d9025d07125c47cc210f19bf42dc7edd84d62fd4d6ebfba9d865c8909477d5aa98047b12e6430d55c9f19d9ee5fe6020cb0fd4604ff5999ef3e8904d6016f99334035c1a93efd452b75925209e0eabbefade7d92f8ed581135d87f791170b3dd7b024eb818dba8ccfb23e897c6edc986ac47cd95292156e50c06797f9d9e552e3e01ac75a709909f7650bb41125bda22a768d3673bb7a28cf418514fbde82c3cc7035c483ea223a5c017b42873aca5381b7d4926c47d6ac62e8289a4bfe1f72751049b04cbacc98c9796300170897711eab28348eb4c73fc31ba3fc82d0043a67d628e9f3dc440a410d0dbe2c8c3c46caf95165cc82f3716f56fd0a6c3b187e1f892", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 103227599, 1483609, 454095, 482511, 176214, 2990375, 334562, 438281, 59059, 52366, 86938, 113728, 6074, 3836, 1444, 288\n ], \n \"k_image\": \"913f889441c829e62c741c27614cdbb6278555b768fbd583424e1bb45c65e43b\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"ad696cce55ac392f061b7e0f1acccbd17cbdfc11d20805df2ee8efb087476ed5\", \n \"view_tag\": \"9c\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"57483f9fa7379f9b72e6149fd2b319c4e60653a627d0107d73f5604159d597eb\", \n \"view_tag\": \"f0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"4360e4f9cfb1109148e0b8428c36fa62270e4aa68e6aabd768270363b53a195a\", \n \"view_tag\": \"cc\"\n }\n }\n }\n ], \n \"extra\": [ 1, 115, 198, 75, 245, 205, 62, 159, 64, 26, 56, 0, 201, 98, 76, 69, 248, 233, 64, 129, 99, 52, 140, 78, 44, 71, 59, 136, 246, 16, 135, 78, 255\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 42480000, \n \"ecdhInfo\": [ {\n \"amount\": \"79bce24f3573dbaf\"\n }, {\n \"amount\": \"aae47cc85d872355\"\n }, {\n \"amount\": \"1119c984e226cab7\"\n }], \n \"outPk\": [ \"daa8193188e5b32952ea0a464812c5f1189db475fe78ad4a6351e7d335a79163\", \"d9bd1c7b680e62a0ef36e83b122965bf6042bef9cf535da9286ea586681c4bef\", \"225c49f8f54b6b1a92a4a2d53c699fd021e1d2cf9ae75729ba7cf244a0264cf9\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fd2a5b8b2a2016880735d531b55fa6e206cc0c0d5177c93d10e2f60fadde3beb\", \n \"A1\": \"ebf7ff3558e9949869a8401f4174a6e2e6092074d44b2665008391bcef3be3b3\", \n \"B\": \"8dcbb6f45f57379ba40acdffb81ffc8a3d312c6b728eff17a8c8f15d49f1517a\", \n \"r1\": \"ca615e2f8bc5257f7b7a27cd455c633a400b6ff63f58d7c66c1172e1e56ef30e\", \n \"s1\": \"4edf1f17a43bd06754bcc657391acef09cbc15d6b827e61cfa8f6e33a4f7ce08\", \n \"d1\": \"37c0edf5a192c739ca6c8da976af9a1fa2ca1649024639f6c26e09dd01489b0a\", \n \"L\": [ \"e64e75e0dd89bd999cdaf72f1334263b2daa2b27b593951c8ea7781d7f4479fa\", \"8b24c4e3e7d02de85eb0144aa0e26be30c4a283db27c5b488ee4895345f81a51\", \"b54741caf457eef595c9753aa58d82d45182325aea6d77198090e8e14fd41362\", \"0fcb3a7750dcb87178431ed8b4b23f39fd893c1e500dce31de0bf1861540e212\", \"e1263d567951d7c73e3f082a9fd7086aba9b58fbbbf61783fda9d037afa96250\", \"fe234734c186422495f5d5db92690958e43e5fd5ec5e302537d3353950f5a8e2\", \"a65a13ea58137481ebc02768f12ba06d4c3a58cc3f2725567a544434acc7934c\", \"87eff281268d9495670dc73032cab96056aeeda57fb16e153074dbe5b490ee26\"\n ], \n \"R\": [ \"d4ee6684886fec29e4f03741b6cfeb7bf468eca760fdf20085df50309f694258\", \"5ded84a963d18448029d3990a43ad3ba730b70193b7195961375390863ef0e72\", \"1f26495ea684c129a7bfe0b58837269c2f3b9ed5697b4e5f50205e1699086db0\", \"d89e441c287e7aff567135d73a6724f206a15909cf4505c99a1ece8d2d44197e\", \"c9c2d28ca55008ba1819f7092e694e0048502dd937674b825e8325ccf06d2257\", \"e50dccf6d450f07a040def2b0af67d7a9ca8d54179bf474b11f3e0342b92e6f0\", \"d5c2c5b01dd7769f3651c5a5b7e4af34428cfd9429ecf373cc409db223a3f810\", \"75b8d10b0e409336f9922b0acc60780ed8c7e6916eafa813e486f678133c7c28\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"7040847e31fae2ed636fc2830394ecfef0c93ae4a380394907018dfdcf981408\", \"3efcaee92919e11f36ec098917436902e5752d62d43e054178b9cb6a66e7d408\", \"7b234f9f3fff46cdec0fd531ab50b82ffbbcf22171250fd8ce8015e6ccb94002\", \"2ee8c5d7a0d10186b416c51c3370b0300f54651accfbebd3ac3e4a22ebbf2409\", \"b5e7ebc40dae1be188c5208668d6216d1cdb0552541c27fc6b9f770aa6fa2e0f\", \"50eedef2110162cd67b027348beb3865d40b71dfcd4bb02caa5d85454868f807\", \"1d6fda35f3729b3fdf7041ae02302733ed00dafdbb91e81d304daa7a99e0350e\", \"d8967cea2637fddda63bb273ed406be7ee694bff1fb5a0cdbf792fe25da78d0b\", \"e68bff0add3f8868ece66332a446415da39994ed247606a7951ccbe639006008\", \"a2796c33bb8d53f64de030b7618deac7e15fc6b53bea8f04b30d244007e6b805\", \"a5c6a79cfa5e75c89eb96b298fe5f3722c9ab5219b839a6a88999d288913d902\", \"5d07125c47cc210f19bf42dc7edd84d62fd4d6ebfba9d865c8909477d5aa9804\", \"7b12e6430d55c9f19d9ee5fe6020cb0fd4604ff5999ef3e8904d6016f9933403\", \"5c1a93efd452b75925209e0eabbefade7d92f8ed581135d87f791170b3dd7b02\", \"4eb818dba8ccfb23e897c6edc986ac47cd95292156e50c06797f9d9e552e3e01\", \"ac75a709909f7650bb41125bda22a768d3673bb7a28cf418514fbde82c3cc703\"], \n \"c1\": \"5c483ea223a5c017b42873aca5381b7d4926c47d6ac62e8289a4bfe1f7275104\", \n \"D\": \"9b04cbacc98c9796300170897711eab28348eb4c73fc31ba3fc82d0043a67d62\"\n }], \n \"pseudoOuts\": [ \"8e9f3dc440a410d0dbe2c8c3c46caf95165cc82f3716f56fd0a6c3b187e1f892\"]\n }\n}", + "weight": 2124 + },{ + "blob_size": 1537, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491840000, + "id_hash": "8a6ebea82ede84b743c256c050a40ae7999e67b443c06ccb2322db626d62d970", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261651, + "max_used_block_height": 3195153, + "max_used_block_id_hash": "8d15c2bf99e9a1c5a0513dca2106c5bcd81d94aa07c1c2d17d7ea96883cd1158", + "receive_time": 1721261651, + "relayed": true, + "tx_blob": "020001020010f783dd2ee9ec9804c7ca2ed7dd3aea990fd3fa0ae8c00edcfe1bee950493de03ba8302b494048114ab6ac7b70110a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4020003aa62b55146dd60777113d2c62c8bc41c9339b63e758513d57d5ada453786995ab00003b71531f0476ff381570af6bff1ca3bc8bf8286a098a939e32eaee36450895bef412c019ee82c35546584a78c1713407439e107e98c4da5e6735d77f5d61a369bbf4e2b020901c8c9a284894d6ce90680c4c3ea011d42ea4108a9eb4c47d3233193a71c74ed6c841d99c6eaad2a605daac8f0cdcb6b75c30f2a4aec0e955b2fd93b2cf65bb62d9b9ae29de54a88a5157484d03bd04b300ad7516fb0dc7c647b3ce7f2b2c301e106566731515046dbcf50b5e8f6edeea6bbd2e945b4eb49bc121da137e276481e0265fb162e11730623ead0848e5abfb5007e24282d4a5dc95ad1332e383bfd3d403de9cc283ef16eac799ea9ecfffd24b0a0faed955a5ce82f2a717f5b30bba3cc22335eb0edcfc3b2af623db3309981e8f39722accfc7fd215bd3f1f6a60f476ca5eb56798e464d5791a1cd5d57fbe2d2fbf1c6824a3e80e48d3fa807240fa78029fdf2795b8bc4f0b5d5336ebaa65c179534ec8038dec26c8b5cc103230607d277a8b482900c60791c03c6a072eb576cdf8532b2da3bb493872c264559a7832f46f076e6b541c665df3baaf4486d9466655a26e3aea8f9d6e4d3f5613f4dbcb5c1cc78c9698551ed28ebaccebba9ffe10b38c4791afa73c620cf433342e45733b26f192642f6498a0d482df12e605b91957614d4a6707d0c103883104210508812038852ef55bf0ceed35382c508d19b82bacb5891bf9070c4634bbdb229c22c4e1296d55ccad33aac94342e8f75acee853fa05e69fe1f545c3b4ef839b401f7dd53846c9a9d8b1b0b04282f802fa451475b86d39edc01368a86cf9382a037075bba459244e0dc6488730bc628330b776bf8c6ababb3046ee3b103fcc2d9d544cd92ea335770c68c0e6f385105299bffd1ae17a12fe4b40e6880a9c6f5038daed1464b0fee68ecdfcdc712405936422918197dfec8f4351b82e7da04f6bcb9c765924afbd077c168ee0306cf456e1f4606086e2155260b3f5e9d8443533de0bb2fa410fc5465603b16c0137a236f761fb254e7b53c8667d8ba931e41bf6ed5cd131a4171a265158843415ca2f5c1690c9ec431091f3312b7b308420878e7e9118bed3c3f7df4e5acc3bd7d209d3539ce182e551af5f5e6369d29367dfa97c8951cfa0ba11a23b5c5e1bf6cd59db4b658b1437652f6e3f25717c9f4f522b37a0767c193fdbbdb9dada9d5d46e38a7d8a42460c0bf37c7adc0a317fd3e4a29390de895b526f0368b4737af0746647e2025a10cae52d452400710c9709e2b375c09c73b2529c97d87b8e62f15d0c776dbb0f5564cde6fb5872c124193d47d92ac007bfab088ebd9a44f194597802213475438f21112a50cb4f02c7f650ecfd3290f1c9bffdd70877dbc77cd5485cfbec143687cf605938787c1426ed42646a1f0080757ba370ba2b36b492a71708e36736db7857405cae3b128879d8e9c7dff030b3fb734e6faf49c85ed5d7940b400375b12674b3858d31b7ab56222e82ab03e00ce265d7d694571d3db08a7f23baf68007c4aa67f31185aeaba276eb3688b26018d8c904b9deea0fd68ceeebdf1aac9d967aec1ebaa4d3c6df2c3ce7c874ace05a3cb5af380be76fd67a7fc64d7da5a3ad94350b295a86cf7943c05c12f8267017e2beada8abf3e0fbb104492c222bbd8a6058dbdb05042213ed077c0d36cbf0cc6f1333a49e977ef452cfba812ba428b19cca4ea461681abce4f707e8f691b06fa6c56667fae356bcfa47f8f74f1db6bb470ce8fb96815e3fa2aaf2d22d1b00f4487e8e83e98b7d879c1699fd6f1092b1a9b5827effc22f14e3c7bf59ef2fc0b9eaf15677934da8c39016b3f771dd9227e76f541818cd4ca03d3bc78bd18cc0ee9dea19ceb194f06dc893c192932468ef107461f70a03b4d9137b72958ef68031ae7ae8bfa3fcc4d0f335959b7ac162dd86aae3da1580817d5fa6f932fe3c5511d1585d0f1383e6c1035c5ace104f8b03f27958c9160375a80498f8b73f861c4", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 97993207, 8795753, 763207, 962263, 249066, 179539, 237672, 458588, 68334, 61203, 33210, 68148, 2561, 13611, 23495, 16\n ], \n \"k_image\": \"a7f204f932169b1b056fc63be06db8ec91a436f7188a30545bcd6a8bae817ca4\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"aa62b55146dd60777113d2c62c8bc41c9339b63e758513d57d5ada453786995a\", \n \"view_tag\": \"b0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b71531f0476ff381570af6bff1ca3bc8bf8286a098a939e32eaee36450895bef\", \n \"view_tag\": \"41\"\n }\n }\n }\n ], \n \"extra\": [ 1, 158, 232, 44, 53, 84, 101, 132, 167, 140, 23, 19, 64, 116, 57, 225, 7, 233, 140, 77, 165, 230, 115, 93, 119, 245, 214, 26, 54, 155, 191, 78, 43, 2, 9, 1, 200, 201, 162, 132, 137, 77, 108, 233\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491840000, \n \"ecdhInfo\": [ {\n \"amount\": \"1d42ea4108a9eb4c\"\n }, {\n \"amount\": \"47d3233193a71c74\"\n }], \n \"outPk\": [ \"ed6c841d99c6eaad2a605daac8f0cdcb6b75c30f2a4aec0e955b2fd93b2cf65b\", \"b62d9b9ae29de54a88a5157484d03bd04b300ad7516fb0dc7c647b3ce7f2b2c3\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"e106566731515046dbcf50b5e8f6edeea6bbd2e945b4eb49bc121da137e27648\", \n \"A1\": \"1e0265fb162e11730623ead0848e5abfb5007e24282d4a5dc95ad1332e383bfd\", \n \"B\": \"3d403de9cc283ef16eac799ea9ecfffd24b0a0faed955a5ce82f2a717f5b30bb\", \n \"r1\": \"a3cc22335eb0edcfc3b2af623db3309981e8f39722accfc7fd215bd3f1f6a60f\", \n \"s1\": \"476ca5eb56798e464d5791a1cd5d57fbe2d2fbf1c6824a3e80e48d3fa807240f\", \n \"d1\": \"a78029fdf2795b8bc4f0b5d5336ebaa65c179534ec8038dec26c8b5cc1032306\", \n \"L\": [ \"d277a8b482900c60791c03c6a072eb576cdf8532b2da3bb493872c264559a783\", \"2f46f076e6b541c665df3baaf4486d9466655a26e3aea8f9d6e4d3f5613f4dbc\", \"b5c1cc78c9698551ed28ebaccebba9ffe10b38c4791afa73c620cf433342e457\", \"33b26f192642f6498a0d482df12e605b91957614d4a6707d0c10388310421050\", \"8812038852ef55bf0ceed35382c508d19b82bacb5891bf9070c4634bbdb229c2\", \"2c4e1296d55ccad33aac94342e8f75acee853fa05e69fe1f545c3b4ef839b401\", \"f7dd53846c9a9d8b1b0b04282f802fa451475b86d39edc01368a86cf9382a037\"\n ], \n \"R\": [ \"5bba459244e0dc6488730bc628330b776bf8c6ababb3046ee3b103fcc2d9d544\", \"cd92ea335770c68c0e6f385105299bffd1ae17a12fe4b40e6880a9c6f5038dae\", \"d1464b0fee68ecdfcdc712405936422918197dfec8f4351b82e7da04f6bcb9c7\", \"65924afbd077c168ee0306cf456e1f4606086e2155260b3f5e9d8443533de0bb\", \"2fa410fc5465603b16c0137a236f761fb254e7b53c8667d8ba931e41bf6ed5cd\", \"131a4171a265158843415ca2f5c1690c9ec431091f3312b7b308420878e7e911\", \"8bed3c3f7df4e5acc3bd7d209d3539ce182e551af5f5e6369d29367dfa97c895\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"1cfa0ba11a23b5c5e1bf6cd59db4b658b1437652f6e3f25717c9f4f522b37a07\", \"67c193fdbbdb9dada9d5d46e38a7d8a42460c0bf37c7adc0a317fd3e4a29390d\", \"e895b526f0368b4737af0746647e2025a10cae52d452400710c9709e2b375c09\", \"c73b2529c97d87b8e62f15d0c776dbb0f5564cde6fb5872c124193d47d92ac00\", \"7bfab088ebd9a44f194597802213475438f21112a50cb4f02c7f650ecfd3290f\", \"1c9bffdd70877dbc77cd5485cfbec143687cf605938787c1426ed42646a1f008\", \"0757ba370ba2b36b492a71708e36736db7857405cae3b128879d8e9c7dff030b\", \"3fb734e6faf49c85ed5d7940b400375b12674b3858d31b7ab56222e82ab03e00\", \"ce265d7d694571d3db08a7f23baf68007c4aa67f31185aeaba276eb3688b2601\", \"8d8c904b9deea0fd68ceeebdf1aac9d967aec1ebaa4d3c6df2c3ce7c874ace05\", \"a3cb5af380be76fd67a7fc64d7da5a3ad94350b295a86cf7943c05c12f826701\", \"7e2beada8abf3e0fbb104492c222bbd8a6058dbdb05042213ed077c0d36cbf0c\", \"c6f1333a49e977ef452cfba812ba428b19cca4ea461681abce4f707e8f691b06\", \"fa6c56667fae356bcfa47f8f74f1db6bb470ce8fb96815e3fa2aaf2d22d1b00f\", \"4487e8e83e98b7d879c1699fd6f1092b1a9b5827effc22f14e3c7bf59ef2fc0b\", \"9eaf15677934da8c39016b3f771dd9227e76f541818cd4ca03d3bc78bd18cc0e\"], \n \"c1\": \"e9dea19ceb194f06dc893c192932468ef107461f70a03b4d9137b72958ef6803\", \n \"D\": \"1ae7ae8bfa3fcc4d0f335959b7ac162dd86aae3da1580817d5fa6f932fe3c551\"\n }], \n \"pseudoOuts\": [ \"1d1585d0f1383e6c1035c5ace104f8b03f27958c9160375a80498f8b73f861c4\"]\n }\n}", + "weight": 1537 + },{ + "blob_size": 1534, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 122720000, + "id_hash": "7c32ac906393a55797b17efef623ca9577ba5e3d26c1cf54231dcf06459eff81", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261673, + "max_used_block_height": 3195144, + "max_used_block_id_hash": "464cb0e47663a64ee8eaf483c46d6584e9a7945a0c792b19cdbde426ec3a5034", + "receive_time": 1721261673, + "relayed": true, + "tx_blob": "0200010200108088a024a3deeb0ef5939001e7eb0fc19401cab201aeaf01e08001cd16f4a601f52a8c8d01a106d811c83e5e987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3020003507533a540f57548b44e011305bd39d6a6a64e5ccc82bac1833b6ededb9121f3690003b32a2175124a8e0a0fd95a37d51a290a397dc74637a8cdd73ba4dfdec91b2c0ec42c0159ef5288bf4ea3ba0f6780416feedec329416b7fd78af51f494f3eb89db900a7020901810cdc8e9ce7315906809ec23ab7e164d1d01bfc65b2f30e77c390a5fd68d73b4565628faee8a1f9bd7cffe31a3bdb37ef7fc670e18f6190cc20459a32ac6e15f56e257ed3471f4bbf5394501db831fa2d05b784f3e5091f322c1df20601326379bcc24fdcb0e68dbca4cbfac5fc5238e31b4ea8619c5eff8776a42e9c9a88287c0c5fc24c5a1872237e971762f9ec12853ca27eb3b1e611d3713bdd4778bc8585ef0adb3e8f144518a22d2eb4e77c526335c5ad9f5c4754740eaba4623f59cce4ea9eab890ec0976f3e03bc08f3218192c56d82c36ddc4692f5a96f8b056f11c7f15635ba12a22274a0a171999444f21079496b016867b7dac9f05f5f0141b181816c004f0f3033d7e0a9819f5a8623a637dc7d949b34c0da6eba6a7d07073c7219d079608c6ff8d04b0d4bb425b73142e84750e7e5548ef86cc02ac0b91132c3e4df24b11a32f8c30a483bcd5b903b2cd87197d8172bd4bd190c4034322dabd82600f852c346716518c93c439a799a2763ff9d2457c47f96e22371440cc1db354184fddabde2d51512556b7d05ad6be9f44fbf1671834f6fd45a6f8f1f09edf96551160fe83c207fb8eabab30ed294aca1287ea196f1640af3a183cc7891a680264c41b34d56ab4219e8175b1d847de3174d4c298cb5ac40e1c84169b0a5fbee401b237ad92093cb34b6752229e03cc7ceb2546102360ca5fe823192024b0775b50b22fe9e7cfb29fec3400ea8a03b047ca1c92af76d14da40589415116b922b080f61446f1e5a11aaaa84acd4bee2e67f25c8a0c77db1e3cf8d9a2f57a1f6a622a4c76475ecb20fb1b6221caa4be32876414e6d6b0375582fbf9c1d50403981d303b53d877af580443431499ed7a030d01a618c37139ac4fec11b3a2afa1aee51a3605abd36c3cc05c02348430bdec52a481e04249a7adde0a4b718d459ee1aa67e4ce05980a451753b0e9b7dd543b373a137cd900f81929699bfa4fd5bd51d096c93673bc035031bf18d1e153a5b62e5965f4865827d7c871403fcda46a7e38ec6b2c7c7de6f6e88e7e32514fdbcf04bfdc851a1a052ad32052ca4a74f039ca030a488b3f3160043add8d6b6f7f5275c49ca6c5f3e6556641b5d08c80f053adcb2928105c91712723592a72f37c0da5ec12f62325cf8ce9a9d98244c830715d805c79b3e0d09ff094563869a28864beaf3d2e4a257f2ac7e05b253f84801fa53fc02c27fb618a582eeb68261458ff5a24f19db62368984e35c13a9b3b10de92a20b1f757574390e8a9e6cf58a33e6f2d7425dd5cff36f15b5992cca1690cedb97159b128e4e509bb7735bbfab47d0e925103bd69585fd60a772b9fbf300bde8d80256510c267a7fba688969b4e89e06d8faaa7b1369cdbba78b19520fd0a8e3e0858e4ca6457e9444053ef08c62efdf1eb2176e81261c5f2b1febf72f4047ef2981ea380eea8a4429fc107fa6f96bb9a60feb70db7cda063be92d3f650098f85e14b1a45dab1687f1e2d4e3b11099286945c997c1cc2f9f908367ce5af0d7fc4f2f770cce3184f98be164dfcd15b896c5290e46dbc2e1458683022d9980d87afe5ff98453b33f1ce50617b33ebe0565bfe0166c1af28cee2ff788e623c0614b3db3ca8b04ae0ca9fc6a036ba40269a3da9cbfd2cd1ce2c8690a6df13ed00cc5527e60edd183603ffc5fb3fec14ee3e6c24bd708192756a2f10d6c3d54102ba4b38a06f9ca37fe3e10c68a4a6bd3858015f2b18f82e7e446a347c488890046b48a02d406ce2f4ae39ab70fa019f4c5086aa861f73f8b343eb433873c52e0901d1794138c64ea8943d6763d334920c5d57014a4ff9b2e163abcd9af5da269cb8dd672e123f737a896c99551b4c610bc68bb68b30ba5206881e18fd2288a42f", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 76022784, 31125283, 2361845, 259559, 19009, 22858, 22446, 16480, 2893, 21364, 5493, 18060, 801, 2264, 8008, 94\n ], \n \"k_image\": \"987605d678e8bfb17e8d2651e8dd5c69c73c705d003c82e4e35d2b5b89c9ebe3\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"507533a540f57548b44e011305bd39d6a6a64e5ccc82bac1833b6ededb9121f3\", \n \"view_tag\": \"69\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"b32a2175124a8e0a0fd95a37d51a290a397dc74637a8cdd73ba4dfdec91b2c0e\", \n \"view_tag\": \"c4\"\n }\n }\n }\n ], \n \"extra\": [ 1, 89, 239, 82, 136, 191, 78, 163, 186, 15, 103, 128, 65, 111, 238, 222, 195, 41, 65, 107, 127, 215, 138, 245, 31, 73, 79, 62, 184, 157, 185, 0, 167, 2, 9, 1, 129, 12, 220, 142, 156, 231, 49, 89\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 122720000, \n \"ecdhInfo\": [ {\n \"amount\": \"b7e164d1d01bfc65\"\n }, {\n \"amount\": \"b2f30e77c390a5fd\"\n }], \n \"outPk\": [ \"68d73b4565628faee8a1f9bd7cffe31a3bdb37ef7fc670e18f6190cc20459a32\", \"ac6e15f56e257ed3471f4bbf5394501db831fa2d05b784f3e5091f322c1df206\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"326379bcc24fdcb0e68dbca4cbfac5fc5238e31b4ea8619c5eff8776a42e9c9a\", \n \"A1\": \"88287c0c5fc24c5a1872237e971762f9ec12853ca27eb3b1e611d3713bdd4778\", \n \"B\": \"bc8585ef0adb3e8f144518a22d2eb4e77c526335c5ad9f5c4754740eaba4623f\", \n \"r1\": \"59cce4ea9eab890ec0976f3e03bc08f3218192c56d82c36ddc4692f5a96f8b05\", \n \"s1\": \"6f11c7f15635ba12a22274a0a171999444f21079496b016867b7dac9f05f5f01\", \n \"d1\": \"41b181816c004f0f3033d7e0a9819f5a8623a637dc7d949b34c0da6eba6a7d07\", \n \"L\": [ \"3c7219d079608c6ff8d04b0d4bb425b73142e84750e7e5548ef86cc02ac0b911\", \"32c3e4df24b11a32f8c30a483bcd5b903b2cd87197d8172bd4bd190c4034322d\", \"abd82600f852c346716518c93c439a799a2763ff9d2457c47f96e22371440cc1\", \"db354184fddabde2d51512556b7d05ad6be9f44fbf1671834f6fd45a6f8f1f09\", \"edf96551160fe83c207fb8eabab30ed294aca1287ea196f1640af3a183cc7891\", \"a680264c41b34d56ab4219e8175b1d847de3174d4c298cb5ac40e1c84169b0a5\", \"fbee401b237ad92093cb34b6752229e03cc7ceb2546102360ca5fe823192024b\"\n ], \n \"R\": [ \"75b50b22fe9e7cfb29fec3400ea8a03b047ca1c92af76d14da40589415116b92\", \"2b080f61446f1e5a11aaaa84acd4bee2e67f25c8a0c77db1e3cf8d9a2f57a1f6\", \"a622a4c76475ecb20fb1b6221caa4be32876414e6d6b0375582fbf9c1d504039\", \"81d303b53d877af580443431499ed7a030d01a618c37139ac4fec11b3a2afa1a\", \"ee51a3605abd36c3cc05c02348430bdec52a481e04249a7adde0a4b718d459ee\", \"1aa67e4ce05980a451753b0e9b7dd543b373a137cd900f81929699bfa4fd5bd5\", \"1d096c93673bc035031bf18d1e153a5b62e5965f4865827d7c871403fcda46a7\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"e38ec6b2c7c7de6f6e88e7e32514fdbcf04bfdc851a1a052ad32052ca4a74f03\", \"9ca030a488b3f3160043add8d6b6f7f5275c49ca6c5f3e6556641b5d08c80f05\", \"3adcb2928105c91712723592a72f37c0da5ec12f62325cf8ce9a9d98244c8307\", \"15d805c79b3e0d09ff094563869a28864beaf3d2e4a257f2ac7e05b253f84801\", \"fa53fc02c27fb618a582eeb68261458ff5a24f19db62368984e35c13a9b3b10d\", \"e92a20b1f757574390e8a9e6cf58a33e6f2d7425dd5cff36f15b5992cca1690c\", \"edb97159b128e4e509bb7735bbfab47d0e925103bd69585fd60a772b9fbf300b\", \"de8d80256510c267a7fba688969b4e89e06d8faaa7b1369cdbba78b19520fd0a\", \"8e3e0858e4ca6457e9444053ef08c62efdf1eb2176e81261c5f2b1febf72f404\", \"7ef2981ea380eea8a4429fc107fa6f96bb9a60feb70db7cda063be92d3f65009\", \"8f85e14b1a45dab1687f1e2d4e3b11099286945c997c1cc2f9f908367ce5af0d\", \"7fc4f2f770cce3184f98be164dfcd15b896c5290e46dbc2e1458683022d9980d\", \"87afe5ff98453b33f1ce50617b33ebe0565bfe0166c1af28cee2ff788e623c06\", \"14b3db3ca8b04ae0ca9fc6a036ba40269a3da9cbfd2cd1ce2c8690a6df13ed00\", \"cc5527e60edd183603ffc5fb3fec14ee3e6c24bd708192756a2f10d6c3d54102\", \"ba4b38a06f9ca37fe3e10c68a4a6bd3858015f2b18f82e7e446a347c48889004\"], \n \"c1\": \"6b48a02d406ce2f4ae39ab70fa019f4c5086aa861f73f8b343eb433873c52e09\", \n \"D\": \"01d1794138c64ea8943d6763d334920c5d57014a4ff9b2e163abcd9af5da269c\"\n }], \n \"pseudoOuts\": [ \"b8dd672e123f737a896c99551b4c610bc68bb68b30ba5206881e18fd2288a42f\"]\n }\n}", + "weight": 1534 + },{ + "blob_size": 1535, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491200000, + "id_hash": "63b7d903d41ab2605043be9df08eb45b752727bf7a02d0d686c823d5863d7d83", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261665, + "max_used_block_height": 3195155, + "max_used_block_id_hash": "c8ad671ebe68cc5244ea7aed3a70f13682c3e93fc21321abcb20609d42a5b6e7", + "receive_time": 1721261665, + "relayed": true, + "tx_blob": "020001020010d6e18c2ffcc1e40483f90be9b30b9af70af8ac0dc3bc099313ccbb01c79f04aa25946bc7a102ac37c30a8d1d563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393020003416ee9d85c13be6a1ad2a0b9a5c3fad790bc3c266cd0eb55f1d38959a2eef8a49b0003d6292302f486945eb9bab6beaa2564a4dbe09cf8e92a107eb8734f0f8a09a1c9052c017ce27d3675d2db5af9968b93733350b10749e9fa4a0c1bfcfdc86c550088fdd1020901c6821af937903f5f0680bc9cea01e240eebed50fd3fd162b7b3b54185d5ecb8253de6123d50449a2746c6a82023347509df7efd5f8106b4e3d60dd0d7f86958d05ee5bc8a6963af900849d2a4118e031bcaeb2788aa2a1e56b036ebadab201d92af1585cb9cb3db78d3da41c66221180e1c8128a35b304ae96de328ce538d160dd66e00a9b57988589eb48c4e131b24bb266de41540a164b34008c591871cd93fe7c3fa4aacd67f35315c0927e7b4add9c63e94a95732b7f12cf1346b7b26e1d1d893d582b2b0787f00f331e787749b14a3e0cb5363537cb4654d90a4c90001e08066b76e5b7cb3d4a9a58b88c613f5b4ca2fb0a875becee6a26f287d14906f3be11a310ff9a3bdcb579818bb958229c1b0ab3d4ce9b935723bdf8c888190d071089e5724f75ce14fe58360ba419f7e7bda58a0175b9ce9b12627b599ae896d060e7a21f3b9d65cf4d386aa6ac044e2283c64b92f4b3f4234ad7e1036fa96f53f187ed53d0c753805df8de748339f72e901ca157aeabff55f14c451f0084e725a55ffc85d7ba5d26a7f678bbbcd1e40e8a3a5400620ea4eee86796b7ec2266b20f8fd361a94759654d7d39ba8cdd6b3a002f140dcefe0f498a6b33655bfb2981ac38db4dd0cc6b7c25d408bbe69fb7cb86114fd7aea4136aacfe15475f20f3d4f7cd51eedec4e4a1890fcc0d29f18d6270a84d9c4aa6ba6bf9ec3900016057240713ad161ed5d9dd3a852ad64f7183a57b83015ab461f36e5ac1ed0bb0d2f7e1ff6c37cb4ef43c6e65f11858490d29b6fff50e91acfb2799fa5b8c0c9e9f913f2941ffa2414ca459db6de293f9e8a231152f6fbdb1dcb2a79bebb5687870f69ec96254f56d963bc8e283a7fdf1fbdf4f60d5d97d4224d9f1a9d4c52c14ba4ffabf5dd9955c0e075da49a8f8d84686d4213331b64a5f770fa35260ff1e1e13ec9da2b7aa35b728810febb734410cb117e37040d2a3c3198b816272e8e76fca9199064b01511b86f7578718590b6ffedb78a5ae175e4efeb3ed71028913deb2e52e5f17d3792990cec53ff4b834616d77cd50e32c84b95422b8436645c304b22c2018c6308ab20f4f7e06c5f67bd59c6f106fabe2b14dc1713a4b13f6522aa74f40410fd14ef905febb4e95ccd80265803c37e285ed3939d43e368b20b9e49b955041dd3d895f4885e8264d99f574d4da3e94b785e5679e300b817e15f1c60228f0745d6faf106d0d36d000be6d5779d6d83165dec7ae167ed7fe6ef7a688f391b0e93c15448214aa121b110d4573e9f386b432451f5689b29a81cec1497ba9c17022f3c310407533b549210dc47186bf117beb821a3ac0d255a31bc57c795b4bd02982a42b4ce5d5a86dade14e37ef9a9a4870687800d2d8ae637cad458e3c7a80ed3e47cc7f6af7523f1ed6bdbbd3f4eaeb6fb9cc9ae13a905618821ca41f13902430b77134bc29f5319b96b17c9fda24cc72af61a890b422bbcd10fd323001605dd25ae17c9c1492fb180e3ac206e780db485c3f8940e1d80301e962fb3384d05d187fdf65cf5b1e2ad3e379f5a765773a3f196bc0b835ad305946328ace26502dc43c5790a5ca6c3076206b56bb1c4bc53bfb4b0fba11dfa24809087e6a6760d7475a3c2465b02f2068d356289d4f17ee35fc0956deb061a737fb4bde998320cfd3e85d3e5113062b0b49f860318d909c9cb714758e6203a2af2ee69e5bf58032014aafdf920276f5a50ca4c3a1b4b7c7f7099ea414e0982bc806beeb68bd303b51723220ce63175d0ba8577400c59109d97357692b9ace8ec0a57992585ed07bea256456a8600ec85716f9bf7dae9cd0cf88ccc2c542600e12c41c7403cf4056db57e4fe2422adcc28c3ec0a02dbb6b1b5926d4012ec3f27ad757faeae60cad", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 98775254, 10035452, 195715, 186857, 179098, 218744, 155203, 2451, 24012, 69575, 4778, 13716, 37063, 7084, 1347, 3725\n ], \n \"k_image\": \"563cd0f22a17177353e494beb070af0f53ed6d003ada32123c7ec3c23f681393\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"416ee9d85c13be6a1ad2a0b9a5c3fad790bc3c266cd0eb55f1d38959a2eef8a4\", \n \"view_tag\": \"9b\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d6292302f486945eb9bab6beaa2564a4dbe09cf8e92a107eb8734f0f8a09a1c9\", \n \"view_tag\": \"05\"\n }\n }\n }\n ], \n \"extra\": [ 1, 124, 226, 125, 54, 117, 210, 219, 90, 249, 150, 139, 147, 115, 51, 80, 177, 7, 73, 233, 250, 74, 12, 27, 252, 253, 200, 108, 85, 0, 136, 253, 209, 2, 9, 1, 198, 130, 26, 249, 55, 144, 63, 95\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491200000, \n \"ecdhInfo\": [ {\n \"amount\": \"e240eebed50fd3fd\"\n }, {\n \"amount\": \"162b7b3b54185d5e\"\n }], \n \"outPk\": [ \"cb8253de6123d50449a2746c6a82023347509df7efd5f8106b4e3d60dd0d7f86\", \"958d05ee5bc8a6963af900849d2a4118e031bcaeb2788aa2a1e56b036ebadab2\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"d92af1585cb9cb3db78d3da41c66221180e1c8128a35b304ae96de328ce538d1\", \n \"A1\": \"60dd66e00a9b57988589eb48c4e131b24bb266de41540a164b34008c591871cd\", \n \"B\": \"93fe7c3fa4aacd67f35315c0927e7b4add9c63e94a95732b7f12cf1346b7b26e\", \n \"r1\": \"1d1d893d582b2b0787f00f331e787749b14a3e0cb5363537cb4654d90a4c9000\", \n \"s1\": \"1e08066b76e5b7cb3d4a9a58b88c613f5b4ca2fb0a875becee6a26f287d14906\", \n \"d1\": \"f3be11a310ff9a3bdcb579818bb958229c1b0ab3d4ce9b935723bdf8c888190d\", \n \"L\": [ \"1089e5724f75ce14fe58360ba419f7e7bda58a0175b9ce9b12627b599ae896d0\", \"60e7a21f3b9d65cf4d386aa6ac044e2283c64b92f4b3f4234ad7e1036fa96f53\", \"f187ed53d0c753805df8de748339f72e901ca157aeabff55f14c451f0084e725\", \"a55ffc85d7ba5d26a7f678bbbcd1e40e8a3a5400620ea4eee86796b7ec2266b2\", \"0f8fd361a94759654d7d39ba8cdd6b3a002f140dcefe0f498a6b33655bfb2981\", \"ac38db4dd0cc6b7c25d408bbe69fb7cb86114fd7aea4136aacfe15475f20f3d4\", \"f7cd51eedec4e4a1890fcc0d29f18d6270a84d9c4aa6ba6bf9ec390001605724\"\n ], \n \"R\": [ \"13ad161ed5d9dd3a852ad64f7183a57b83015ab461f36e5ac1ed0bb0d2f7e1ff\", \"6c37cb4ef43c6e65f11858490d29b6fff50e91acfb2799fa5b8c0c9e9f913f29\", \"41ffa2414ca459db6de293f9e8a231152f6fbdb1dcb2a79bebb5687870f69ec9\", \"6254f56d963bc8e283a7fdf1fbdf4f60d5d97d4224d9f1a9d4c52c14ba4ffabf\", \"5dd9955c0e075da49a8f8d84686d4213331b64a5f770fa35260ff1e1e13ec9da\", \"2b7aa35b728810febb734410cb117e37040d2a3c3198b816272e8e76fca91990\", \"64b01511b86f7578718590b6ffedb78a5ae175e4efeb3ed71028913deb2e52e5\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"f17d3792990cec53ff4b834616d77cd50e32c84b95422b8436645c304b22c201\", \"8c6308ab20f4f7e06c5f67bd59c6f106fabe2b14dc1713a4b13f6522aa74f404\", \"10fd14ef905febb4e95ccd80265803c37e285ed3939d43e368b20b9e49b95504\", \"1dd3d895f4885e8264d99f574d4da3e94b785e5679e300b817e15f1c60228f07\", \"45d6faf106d0d36d000be6d5779d6d83165dec7ae167ed7fe6ef7a688f391b0e\", \"93c15448214aa121b110d4573e9f386b432451f5689b29a81cec1497ba9c1702\", \"2f3c310407533b549210dc47186bf117beb821a3ac0d255a31bc57c795b4bd02\", \"982a42b4ce5d5a86dade14e37ef9a9a4870687800d2d8ae637cad458e3c7a80e\", \"d3e47cc7f6af7523f1ed6bdbbd3f4eaeb6fb9cc9ae13a905618821ca41f13902\", \"430b77134bc29f5319b96b17c9fda24cc72af61a890b422bbcd10fd323001605\", \"dd25ae17c9c1492fb180e3ac206e780db485c3f8940e1d80301e962fb3384d05\", \"d187fdf65cf5b1e2ad3e379f5a765773a3f196bc0b835ad305946328ace26502\", \"dc43c5790a5ca6c3076206b56bb1c4bc53bfb4b0fba11dfa24809087e6a6760d\", \"7475a3c2465b02f2068d356289d4f17ee35fc0956deb061a737fb4bde998320c\", \"fd3e85d3e5113062b0b49f860318d909c9cb714758e6203a2af2ee69e5bf5803\", \"2014aafdf920276f5a50ca4c3a1b4b7c7f7099ea414e0982bc806beeb68bd303\"], \n \"c1\": \"b51723220ce63175d0ba8577400c59109d97357692b9ace8ec0a57992585ed07\", \n \"D\": \"bea256456a8600ec85716f9bf7dae9cd0cf88ccc2c542600e12c41c7403cf405\"\n }], \n \"pseudoOuts\": [ \"6db57e4fe2422adcc28c3ec0a02dbb6b1b5926d4012ec3f27ad757faeae60cad\"]\n }\n}", + "weight": 1535 + },{ + "blob_size": 11843, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 236860000, + "id_hash": "b8a15acb832330b5070c7615fa1bb5142e8a45ecca022c4136f61dcbcc493986", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261659, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261659, + "relayed": true, + "tx_blob": "020010020010f081a521c5a1d41186eb5698f516b5e818bbba1bedb705a5d00df89f039357c73b890bf54f9217b344cb19dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9020010f3eab531b38b98029ff116d1ec1faad00187d521d51bc7e80487c304913cbaac01629510e861df07da0fd656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600020010e6cf81308cc3e702cbd661ecd64b8650a7bd029cdb05b7e5019ae20cd8f801e87ca479ba3c8a24d08c01b667ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f020010d5989d32d582698beb2499c931dcb2379ae705fcad02ac9e0e85a902f2f30193ed019c0796d302a85ec50e872bc5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da02001084f7ef27b2c2e20acbd767c49a16a092298fe60a8a880286d903aa8d02b2bb1ecb8d02d4d306a814d612f25ba605a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22020010ea98ad1ad2e88618ddf413bbbc9b01feba1d908508938e15eca804fbd803f4f50bc154c661fd60a706be1dae03a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a0200108acfff31e1c28e02bfd8139bd603e09604e26387af0586f501c79401a059800a9e24e10d9629fb010288f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b85530200109c83ed30a19a25d3a9bf01a08c1cbccd14fbc90cccb59701b953c0e403df41e69608bc1dae33c40edc2482037d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5020010d4a2f828fda6f308b19f8101eacab90199cc02ffae01eff701a4c005d320b32cc10bca9001b101ff59a906cd0365bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500020010b8b18c2f80a3c704dfa34e808b02d0a704daa501ecb2039a29eef503a19102ef07669343c20ed525a10452418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba480200108cf3cc3296f472e2e75184ee0aa3cf0483bb01fec70a96c602c562cb59f6cf01da07d861c14ac411ea0c40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349020010b9a4e12cfa958d02b4e18705e6831da49807c8a007b7c202d3ba04b40ba7ca02edb402b4b803d38c01b431ecdb01e50438d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d60200109ccba132acadc401c98731a0ef0ce461b9ad0895f702a24ec955c68401f975da32f616bf07e530c81c1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d020010cf888128c1f8fb0ac0f221efe24fd7bb14e3a40fddca0bfdcc0fb78f02d7ed02b5a0018e09be10cd1eb10d89051cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec020010db90af22e0eaf40bcbd19b04b6d0b401aec905f48402e5bc0f94cb08bf9808b9db13809701b817d49902fe629903db1b05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53020010a7bbf4299ff7fa05ff9bd201b1fee801e1d529b38341e8900f81a908c78b03b0e101b89201fe6faa5aa701d501ce050007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c00200038c2ba531d06e4ac990213d765751e89981303d4714d81229ac09e385c8ac1cd3c900030c9d3c41171e04e42f6ed61a932c2ecdb5c0103d7cdd909fa0ab15881469c66af52c01b848c2cbf8e0ee4a984bf645c0e6118450971072a18d23b6cf716f0bf681fd40020901d0fecbabcf50dc5706e0e4f870dc9c59cdde692b10e76b3405d76aec5c479df6fa3357cbddcb22e20d2b458011dd31dfc1c78316bad7ed9ffd39ba12b86a8ad571f7f5aa9668806d10d2b03c2c84c90690e4ddeb3dc6f870fbd5df5b0901c55abb99bdda211773d14dcf7cd68ca2c2e1df64896976c36da0b72b9b0b5abbf68499752558d3b7b342a554f91ea3171cba93075d35968565e37020bd60f6bf0086548c6fd600cc04f704607fd008bdd1ef83ed341109114d9301c30a86d3d87b9a9a3a6cf62d4aedea2cbe4f48af3a830c33486b3e3c7942ad7b4dfbc322076cbaf3b5e0afffc36549482dc4e5220ad7be9284470405e2a889dc4b0eda950b9eacce92f4b6fe9bbb9f853cd5a68b1ea882a66fc1f86df6b7f21ed49e21fb07070c2330673beaf7961285d721e00c55e6d97b77efb12cfcdac429cbe92fe7ed641ae9878d713edc784573be96c47e80dec59c487590f721dda777066a66a388a8cbd007967d90e323d91cae997f9449518ca71dd5e0473cd88d52a1b63f4994cb355fc68976cdff244295ea25e9d579eeee92211f52ed942307827480bf639e9b945e1f12d5a0369f990945dc143dcbb3e7bb7eada7b4e10ccf65128ab3a203d27441035465a96cac073e12db24bb696f38587021576582596bd0351ea8dcd30123af3e518560a843188e19aa666d9b70d7c55b2072c3813f05084de714988a9a07e21b543d2d50a2b7949e9c6ee3638731aa1f19909ac0978ef10ebe83e52558f6e96814d9b64fb0344d8a37e43e9933a9bcb087173ad895400a680f3f7919a585572b4c0639855b4a146492b00c82eb9d7bd9566aa94387e79f80561676a045e4be44d3a42061a710a8c5be6ac2af7531504911a4e17d27249f7395c40ca90ac64e8a0eaee615e0a23be18c2a2b74e5afea867b60acf8ddf7301055d2e9b6556e2b8750caaddea43764d08b5dbe1497cc7db2be2ccee30629fb313078fdb9fc4f9f01b9d7e2e9cb90fdf2654411be9914ac32fe797c427f2a3f5e0ab974226f243363e401c6214a87d6d33bd5d66a6f74f273c9eaeba1210b64089c7ca7418d0ae9ea96a6e3608b004341be48b3fb658836e80e9c3c2786764db9e239d115c206e2c5d846b20c33614aa533f068a9363f9e78b728faedd5fd5ac6c2b427dd0b00278d9000c2872ff0d2506b842847a65eaf93d1f5ed3267d63cf9128158e1d307aa6a0c69a73c3a1b9914c285981e40de53329e666a98e065354dae1c92765d0af2a1b19b7a3f102fe8a7ba53487879d77c8df704ee4785bd27a6083ab60ec00681a849c4a7064e6265c60e06924fe32cec542a40b1c89d81356cdb200e8429033f75ee3e3749acce3d130cca4e7892f68cd7cdfa8e122bc78530b2e8d0176d07ebae1b0281338c2b2ec14d71d211f1e5500ea3bf298e88b2bb39edc1a4614d0d5048bbe60650d351ec2ff4cafb7f29ed646885572bab7ca50503bd7cb1a6e40775276f4e3914a473acc0a6520ea22947173052c525ff7f69b4161bab3f4e55097adc053156eda0d8d255298da99b7612756be610b5d4c2d4c5622c2e3d45a00a2847e2e122a38f797072440f3b2860dc4a184d42f64b1a3db4c26d43cc3c6a040a1df4fe0cb81346469ee48b4c64ca3a163a234dd107cfa155d610e859d04405d90e1d6a6c4f5b5cd93fa65fc80abff6607d8dc1dd932abb1a760e5a05b8d90003f7429d83df3df24cad2012bd01134cf6ac445145c54434b6e12aea99ed030e66902fd5bbbefe4ec9d59d9e38da2e3e182da804b9cd69fc6516e5c42d12e409807e7e4661a8300cc35c9a5aaf05c74f315d301d3b58af8423e8b3a9dab2a5f47cb7bac961689da9c8480fdcd7699abfc7b1846b5406095930e305225408a80fff283d2ff7b05766873ea25e37a51af6e80fe42c040c09e1254fec0c561d7005078faf699880bad1bff0adcaf0fbbed6a6b708e993738fcf3f51307f49427b0dca0a31c36ab9453b909cc184d4c63908b94427deeaac5521a838ce0e7c6547082dac768455bb669ab5297535a143999b247d8999b9c8d041a2eb3bc3d152c50ae586639c731aa945cc0ee851bcef18a27d942354cb8d66ea356d8c2c04eb490e54f4fd74727b0237fee689ea8802a8d8c885884c216702e32c8e8769430e8709954ea828466f98d09e756e5ed6c12ef5eff3a4ee36c990542f935a460da469037bb52fb8e07f50801ab6a9b81b8eab75b73a7fe93b9d65b75679b3103d5e880aa1d7c47088f50c690def8612384bfd2f01dc17e184a9d0529735d34bba8e0704c1d20036078d9394a593de43e28afeed8e9f9e37a283de8bfa383f333281b502baab61d08ee55dc83a7a7bf7b9be015aa90cfaa3cf2c7976eb1616b6a53277079c48c0cd37af84fd9804839055052758c47dcbf861d6f481edd26426abec9b0f23396a7e14bd9f6ce9168c88eb5cc00c16e92c7133ef6cf66c16ab51d6209401f49e4a023ecaac0ec7ffac5f168d4e7d4ccf098bf7c59a769430b9131bad6500fe9c562cafea11ce248b40a51faf4a9a380b9abf6407f1184c05ab6f1c2caf0d83d399b9afb3f50c7d1b305bb321c14df0787e226ecd2def7bfc6b7c312dca0b255e606d60022c07faf74422223d45772072dc8a7452802ec6e0a208deee8d7da374add905f27f162b82bd779d88d0f533b9cb550de4b7a51679ba3469bf5b0d003038393e91408af3ab39972995190a27c377fd568695b06cf0a2c0f374680c7e6b2a71d81eb28f336edf96840978b03a9c42bc72c9004dea1ed37d1208670da75ed7d6209b278e9362c880ad85e32253589c0ba3fb6a613df14128040e4d0b37dcdb26edc3ed3d88b1c2608439d2b930cfff4eea3c5386d27928ad0f8af70b6e81677aa7c2ceb9280628cd810e5b7e175393342d5cd560ca14ead9574bc20cbf0d7268a249e56526af3029493eb31fd3d20160aa32d30a0f53c44f9d82970105a30031d96eb6efa0bca4d61a6739c37cde501a33ed7de3b8f1fc3ea696830c52fa1350698901ad198fa276f73406da4a2d4cac670860fd0da77c42d9c67305e4044076d2fb8bf2765ecb825e468e698130674102dc58f3c7fcb8194a6ad60546555d48c63f1354f34db8b6c6a381a4052cf4fd6a30ee2274af2da5684e3d0d32f2ad376e21aed1ffd8b3c0e48032a16283c3ce6f12ac2cc90b389e81f59c053fc9310d8c947f78ea37f66d2df64ec8c4e04b8bc58801a6a362802ad8c26106743aa699d067999feb437d92b438ca45bf1a8d1ca5106f6c8609f277a360f70b9237b6b9a393af4c7af6da7647c4bcb7b50da483f82478af3d99bcdbe4ef5e00464e5e541e187854dc98a69aa48fc444019a803cb71719f44b175e968330850f4cc9c96f6c2c24075edcb03b79a8d82d83b49db4a33b7eb993d48e4ece3d2a0f524c847d7f15c03e03708b96b6ea8eaca6a4657c3af0244e6401779a0ee21221fa92f13efd7721c5cc4500e7cdb8254997d8b73a7b2243f580a5e91cc246910e7fa7d13567b4164e6911f6722ed13aadd2a76f127a3435a4bd919836c363cb09ebf110b68ed56b28b4e8bd5d3e4568adfa509e914d98eb21aeaafb6734e42f0710aaa746fe4ea17c423f38ac951dd1007dc17486d2b823ecebf9fb9dcb62dd0de523f315c266de82c74f0378fcbfd2b030c0e1e30b034ec8f7d27946b8c86403863350520c89e34975e27b05df44cbcb017e0abbb6a756faf0b34f43f27529079fa6b98c8b7a26493a215c954448e1958f6f1aa558cee11352fceac48273440c199f152d34e7d3c805b35e61a2ce914b5e03dc5d7858ec15c44413e5b2569c00f31567df8324d01565d4e4e9e04d86f75f24dea9d9d24568cc0ff02740c50c06e0c1712ec5b711aa78a21e7433da47430ed76a9e5a20748ff5df632a9cfdce0d1e54ab98f2bf59db008b5c6a03c2d47fa12540d1c927fde4da851325e28afc0efc1fdd8a0cb2544e57306a0b5f486299a713a8cd4aad16b38f3c1b789937690b35287cdb337f33ce203d6303892c64e8be17016436717ff0c5c1af6ad7082e0b0bb3ca7bdf4a6369598e25d28c42189fc879daef22144424854ba28fef20680b5d32e9ae7872d9c134d6d9fe5b0ffe9352ceabd3805a6af5ac2b7b6234af960a77523d6e80b27849bd7f784a98d2422ff79c902f9dcbe4609c429e5b4a9fc90b15bac05d97ba07fe3734d3f7491c6a962ae942f382a7d28ea1fa5302c51bb40ccfdbd190f3286cf798bb345b51723f6e8ea8515534773a340613d8612db8f9adfff3278d190f7894a6ae534e2ef7a0a8b7b2fae577aaf438b62ff0e4d79b8a0856a76827f3e29042f3cb444e67dc16633c30d8db17c541ab4db7d81fc5a32f00b3609aa0b8d2544843206117a826e456a41aa87d80d320c09f1ac0f93ed8e30a22b70ab8b6be5dd1288e09f9c20906d6bca5f0eff1e2ed57da9465ce60ecc00faeebd450c3c3ad2453b75c18bdb725cca06d8cee657c77564ef03cda16d06f0d8e5ac1cce961abddbb5879738d04997263622263d9f27ee8d5868814afc7a2024e8807bfbf6943fd0792d13b9bef4ff83ed82f0c34df5f9c42aa472cc2c1ac023e152321855eb77e8f762d5b6937d9e7cb3a9f3c2419b806203aa2deb57ae90680a3c83cdcee63d0bf296027ef22cfac0938f9b115359e6c6d91306dadb2fb0441107ab8a88b18f0bde29fd68432767c7ef193cb6815c4456eb0d917b9a9e80f4cf02dee189d4b8accb839002f8b6eba365c9f3f693e156932c09941bfc64600fa9a1ed117e99068c3220c9f0e94b9d7c122c6c391e8f6dc0ddd0ec8097f4d0da7e3987fc86c5523fc788c68b25f86ff5e347849e71171ff0f5c6b15a0b501076b1dcd3e4ba22639beba069d6c813548c99db963077c1f2bcdd173f38e79bb05b450a4748c5959889f22d53f62476626e22587a023dfc4517d49396b0c2efc05817d8187aee15d20ef0f623615bf4da108d2429e596dce7d8e9fe6e8596c06009824b7bc0d7dc58a097658babc3701f19a7210f2c197eea886c5d00dfc50c108570002d437713df562fa1e3d67f70450db016570e1119532ed0e3e1bdcfd3155792e33a1110e87d24c8eedbd1b5d731c4fdea67a7d31168432e7d7cfd6564706d6a5c45ffc307daa0cbc600a5e6e3c9c82d493388ada75deffd3c83906dba60d6de0af97e952ddad74e59b92013a6e55d63a0289e09feb33641ae2bc1b2fd60e33ebb741b522f6c0d47a331846edd4f4d82223d193e0e5aae4b88750280b5702afb63b883a67e6a442ebaf9c7c60be8037a040c0a6e1814696ca2238717ecc0ef670db23a3063db45d74557d715d643255f5cc2b02ce024a42a2c4f48d0a020c4fe82d187b3b6edb7e2e9444cb2cb3b8862523febcd7bd5e7549b5031eb8c90c27907eadeef4328d325dfa862735dc9ba8ae26df2fbf9712ac4fc813122ea10c0ffd43cf70c217fbfed3edd012d66d959d03aba43873f87634a3ec91efdc920986321b3ae6152c4e1688b81775c069fd5a41e71e2dae304bd5c479e3247ee20a94266393a0761debae592b659f32e71c39343636ac1ab2d3732af1fa9778d701fceb0b71bad10c860e51e637430e6520d5639c6a1c430aaad3802c30a92b500fe55616cfb92ae1ce65f3eba6285da428c7849d24918f6d3bf5bcd8b37db6a403a3220064e82e94bc8a3df93e39332b133bda28c866f30e45c824039e448ef70459086810004d7613201583e9497b5c17e878238bc1280c87ed2381b8fd30860656df5be83e02f18e2308f2546a553299ca285b53c7f66ad0297c907e01dccb021d0e21631768d826068eeb457b877f4009977de0fda3d981dc3887515060c50b6cbeb0704498abea125f623107f97b33daf3ba02d619b6edb8223167fcc1df4c1909cb506805d1f464b1ebeaa69a1b1a0cf02387504296c7ce63c61e0385f00036dde496366c9988b48d904c4111379548fa829d55787ed9c629ca873862a40648f6f8e24387826471f8e6047baf0babff8f21cdfb81d4930749638856f2340d306f636d277ae1792f84a293e4f66350c75023a1b5967eb40a43c440a246c60342d8e7ebaa4ee68b8551b9807d7b308574a90a7c54134112d6fdb3151af2c90a2463bad24e336dd34bffcb34713b6802e711a168e4afd30bfc3dba82550fba036a8d9f5070875b622865ce43fa773e77ebcca52dd7fb846aca9c133eb93bfd08030bf7d2f95f541cee1f8ede2d0099177bc447d8c67a6d3b05ee3183498b1b0a9e8c271f40e36941df4924502e31d8122d28fef01063e9692152371ffbd44d0d54b527ba4a550b6191a40192e1735f9e9634df5647c3580179ed7f11d893890e968845f97aa642bccfb6b9e84a4d68189d59fc729f23059dead12b5bce438403aa7a1ef89da4ac0e4617a978acd99ce57213509702e31941dcab7530c902690a7da1e1c0ae121c31fa19cf8889ecd8b61cf086be7cfe695b172a36cdc326d504e55918e88a21efc120a54ffb6f852d5b97ccd7cba3ed1e44b6fda91be7b5de0589ee42794cc797c25249a9a6d40f873e52b491389065a950c8aed5b6675f5c066d1dce8ae2cee39a8f7b1c4adf5fba44fbb41a81b06a5ca9b597ff6081d89a0edf9d832bc866de4095119e7fb0eaeb8930b705d0712aa17e11754ee27da80105befbd6dfc2d3e6e8a040c080a7d4508621474e11a3ba7fee3a2d6ed428324c22d723f61c46b0dedf57ed8a1f5b4588f9922fa5bcbfe9cb9d97d558a4030e7809c1057838edc60be81f357bf466bc5b9fe05d58ce8009fa509db7d24e0404f609b7ade4df5611ef00761069d18aec275e33941db96feb81abcf87171b7d54f0013d88f8d39dbcb57df1b0a3a3463bc75995e8193afb08815de576c1de957b700aba945788360754f9575a3d9c3909e56b171e193da4dec2789cc41ce933c82904e5c4c01c218680c44c46ff1d501ffef20d6a5b067c523ea161e115e7b0179007835c82d226c6d3f7615d13ee729f7c926d429a33302cc61febb9ec53d24dcc00322740613d4714cdf380dec0673929b91f0c173455c1d5f605b3c2a2998e970309c59130a49a6dfe6b9f56d4ae04e21e9b14034e54e0b8745ba2c918f39d2f0f95e48323f664da395a8e2d6e294c202a51e5cb513a664f947deb2ce6c270000632cf0c9decc4857d801b939cb2e97498832d469e9487978a447e40b25904f80ce5d6e7d6a32ea5fdbaa470d134b318b41c8ab7e8cdffec513ad4cc2abe280406081827c389a2c247d0ee40235dd62f1ffecbf6aa3389abda43298ab752f9480f898f59005b4d8a2fdc4631bf7f2cb10a6cb789dd028e5ce71f0cb3512b3ed30a497bedd241b6bbef8bc099340e859f20d47d928fd1bd804eed2b41e266dcb00e3ba7ab8b1d442ef9200458654d613fa08ced2b8d86cbc27260e099bea23e4904e5ce096899cee734b4a524e714c1b98e9acf571563328ef71d45e3bf6fca2d0bafe7b32ad8919db80210eaef8c2c89bcbbafabcd6d5292cdfe439c1a9f1fae66090411640c6ccf905547102b49f7a025a3c7e095e9f7eadd457c147d96210208962f737018ec057b427e2d31ca7eb58b25e9fc4cf7cd9550129035d6b98da60ba7919d4fe9b99e4da7598fe85015c43be4eca45d1913458e9db9bb8a155c1a0eb64774939673b8914c6bfe98348182ec3a222da702175ebd2b8c970bb878d807858ebb7d35f6675bd2f4ab35af156defc38ca3fe0ec5b305f2d13481bdfaf40b09d0076aa6d6e2de0a284fc8027f7ef58df0eba11a524764b4e939371566a50a2f1c2171207626099ea70cbb62112001ce82353e7640b91370b2a277047b9903e6aac7bdc67db5d1e316b7ecd9d26d9efa52a1427aef62ffe91d19e5549f9106059a55d8085e937920beceb69b6233a950913aedb6c9913853257d80a7f2df09240a0b0e2265f861f25f3b3f020295e4c3a557d8f210a19713fbea955e2fbc00aee13bb44e9d935d547cf7b071b05d94292a4d753018e4b2058fe919e98fca0209f9e3815440b6b34990f49af2210cb6074f74dc7bbf5804a6a2986c2a1fe60c70493fb69d8a0a256d3ad34313bb2d88cd9e3dbee169bd656369c2ad0b598e03e2d43843317dcd53af92eaf7de73f89d0aae108da35e9639a951cc4e6bb6bc08478d9108aa862add6731184656515718c3bbe4c666bd3730ecbb150447fb2a0f6a1cd0c4c26f836163f5bd045d30243b49e10bedad9c3a974faed2469b128d02025202ee0e8e262e50081c08bf278b9de30afe9fa80ed99f0b50915bb83752051ce7b914a5b6c8eb0e4a2de2f412a377ebfbe8e3f409ef8580b4878a380c11e476588b8804b369130947a7b805aaf563125b08d79fc2a7a6dd59edbfcac7aa08de4ec98ad49fa4c88de7702238c883b2790c56b5752326843113fc7a3e860e058ac662acfecfe2f803960414b6c5a3f0e16ba8bc45d2e9442b74d13f7774f30af7f4cbb7eb1a2545914dd2562cd1cc69ca12a46b76e4b3d0fdaf520e4a4a7f0dd8ce412d8b0567f994a8c1684e5549402078b59200b8b697c776e6e58d34cd0b797938f4eba967963055852af7194e1d776f455e2e84402598e1ce415f8720000531b2935021bad2348c4b5976d3cf2d89626138a5f204102559c592a33be507dd596db1cbc03ab91f1f5a80729619bfb1bc830d88188e3f9593fb430d4050092f9064ce03ebf328aa662c7adf612240056e0418f531fe086f19509d44cd8e0c83393059bff678437c3d61acafab248c7b41119bb777354b836e9752849c80054fcad749c5660627ac7a24adce564b62426563b0caeb16769ffd5afae42643071d211d06f860445b0a384ea4275a1f6f2b0caf11ec2fead1b8a8abd71215fb0ba65cee1f28aeaf4fb54685c4b9c1d4583d05be800fb3b392c9c9b16d45b4740589c45a1d7adc9fbc4a8a91a6d95a256954408f1e1794b9b73c7fa92d9d1d3d0d62e035bd6a3c91f354608ac7d002f1ec1481b92fe1efb47dd717c58388432e0821b4642894dc1d95bbcb4bea812edd2be509e3723eb27dab02f0aa5174bfda011bac757ac05198bfae729198b21ad83ed77d7099933ca8d438223eb5172e29005f8d4bb439e9f1fd7d9319bc235a800ad8cea46cf72aa161403411fcd239c2ccc1caf86b559d263ec57ba80793cb6045195b2306dc2d3d1545b4f9f628733f0150a751cc8468d64e4fd87d8f6808c387112ea6cbd2747347a2702a1c53d036061c256f19897dced055dedc2f6000444b891b2f0f103ec52169bef5620ecc8802b9c40573c76e1a18907d9aaa82cfde4f69fbe29a031ae39e9d47161b52561104b9c4272fd23a758596329701d313cf2a2a5a0783b9f39b0d9d376b1a53d9aa02008e31c110ea76d7f3dd3b6119d18436d7d87638aaa30c633eef1fcd5923ef0f702f504ae3e3157382c50c022725f50e0de515d1bef117ca08d832e098b0240e6c2be19c7e25a8f32f876f4ea74809a659e02b8f2ec908dbd119ccf0edcd790994de8034f11691ac2cd6500c32e20287e968196dfd93de98d31a5733b19e7a0735c541ce2da123a622e10b79abd68ed242218fc198d3fd9bbe3dced2aabf070750593ef0ddbcf3da82d63d30faf24289e054434fe9b2547de07056c2ae927f00ddf5820c7620d2b9cc0f5047fe4a74d163cc3813f5465bcad682ea0be2d0070d34b1b382dc98e343b261a1a605ce916498f476b2131ff2353e1f9a3ce3e71902f2820f6f514b72f32b2a3e2d50fad8c47129849e2643ffd978f3d6a8893a6a0c1dfb540d9405f41c1b62bba4b0893273535b223deb4bfbf64c2a8b6ac040f00610d5d9beda68f687ebd4035c39c45cd80cef2c15ab5f35f24af9c0560ae08f07b50751c330e15f3ee3a8e07783dc00ca6b8f78d465d7dea2c8c83de74d5f000e240a2046c4d4346fde8802305e568074e94ff89cf0a3d5346eb77564c35b15b6b5746f3412f0a78c40323c98a985c255d63656bf1115c4c826622d462b26f608130852be14f740b8570f5f4a5511b86a784c51a5e4fb8e945e31c8040628da04fcadb8cc0c23e4b24bf2222932c7893ca6b9904dca2ff8a4968d35a0d368ac017496afccec6a815cb767f0b779ae95e3ba5260724a34f18cbc9204840414e60344dedaec24d73ccdbe4807b8f1555c074c94f53d94177fd1698997c431bd100d9a501dbae938247f5b77eb42505ef9cefefd4e7f42287903b316f036dacf80089174621ca177d5a71c9aed771de721522b0f72c815e97b130f1cbcb83877010f56c9926851bccc1f48abeb9972a7cd765435ed59666c643e8bf9f6f0dbf91e0c32da0d9416c4c27b002fe1547ad2406532ecf44c89e6245ac041ac0ce4e4eb0d7ca53b3bab439481930d4e214b3b8460354614c9fc4a2c5035671c4fbc522d0b0ea983f506dcead626b4f7932bc9b7f497f8233a1556478c0786218bac666e0ea9e7ac6a29eba7c27edc4f25dbf6906883bbf4ec47d1ebf38a3fb70c0df46f0b0541a8b5e21a9ebfa0a08a660daf10a2dac0d7c8bba437f3b931c96455b58805b0d522d58f81354805dc073ae2dfd82bc827ae72807fd533687f78a450eba70ef15be34213fc5ca3b3762944f1d018c46e7bb0b9829132633e54528c7178b7000c3852eff49d00338308b216cf77fa36f67130ca7424138560ff9adbbec1ac0f3c300aa9725662614ee790040e3668b7b512e4953b5a68a3df04f881978dac0e77be03f07de3ea414ab5de8b4148df7b2c7548de2392fed2d26dcb61deeb329a434e34961ba8fc0a8533731fbf13923c60ecaece5ac0633100fd18b1a47ab40a9c3d10e14fdd5f1c448479b933db813f16c87d74e925e91a370be9d5cc5f550f3bec745edc71c836a48d90e51777f2c480f5338917fa9ab9ce258bcf6eae6708542e1d2ac11c524ee41c307b7deac63d13dcea39ca2a3fe38f4fe0ba59a6f4052485619c52efba1b68fb0f0ef5aa921cc57fd1d341f3556a1d6b73d17f6db60b496621f542826cddad8de2e517258ab046842f696b9c4f2835506522f9d2b50093ed535605c4815979bac5627c103f51e4ad001e6a600f0d0b4ca4b81f06390b14ad02ea0c3b0349824c1e0573549bd853d20cad9fa1edcca9bc566014f0af06528ffe2ecbaa066240c9bff71fd5ddeff863b27cb00a74fccfb1db5499a8bc06eb01cdeb3b01b261e61f3fdf0f3973a4e1104fb997c8b6e3ae436f1ce0353c028c292daef3187740a653da65cf9610bc22e0fdd7dd5ae16293b057f31e8db002edb9e8b76d70aeb7d1aafa772a757c790590260f51c7671871f0df38f6cb230d84ab59ad81a924c2c8e28a7537ada86ea383b9476311c1ba4fc3ab2cdfec0205637c21cdb13444bc4b3a74e18f399b62fdabea074a3fbf866486b4ed86a6a4068b85c656cca6ed3e68b35175e1924973e82d462e4c2704e06609d552f5e81203d04fbbdf96911fd3fef07f359a8ea935d5a3657bb773783cdc060395d0081102f50063354fae3f6e43ab851dc5c29fc36d48b1e654b09260aa5e62c743cc6b0861f9bfa62752561e2a80f8275dc0e2abaf8f65572fe4fe8e8ccc2d91bdd7c6468783932798cf483e2b6793f0fbc08495d135ae16abd9d334a58bc97de071d10bf13f1dd379ef24f8e41f63787166417e18e5960f38163fb0b92a7127fa22ee03db3562c139a0887a8820e0f85f1714fd29a59d500474f92e1187bf9729c7210cf85a5b1f13d581dd7b8b25a709888f147420a493cc6e036ab9c6366c0e4ebb07026b0e3fd5b34eee445669452cf92bbab5fad18b9b164179b2bd489155c017091a448388d6ca40cea2523618c4a992c37d3c20a7139b940417cdda9c7852bd082bc30446d38f51d4d3ef9b4a8d2482dc05a1efe359d65e9fac6d673fc3a9570b123f5a609851be3b90461ccdd602d85a251164e1c757ed256c4d92b10bec9302a5e9f89069c73bbd2266ab755a4575254a43604b69965de8d131cb812e6a6e0c56a983915850fe148d329c29d15eec67464427da9d763b89c52995b32c1c500ff81d35fa348ec90c2f4680c1e073fb259df6a4dd145b8951e017948c2976050784ed0e7207997ffc619ef2ee4e53fa88af9aea63cd2f1a761151bcab58025a07e3a2b2ce40f9c25e0a88d82efd67c0fefa4cab7cfaa290865508e2e17edee80898d4eed281cd7a8d0e4090a97ff3cbc0ac62712528b367a32bea105840701e0c6588cd1a118bc1b8c4625a3e48f69b779f0f575cd2e505b362051021df8c1b04fb273a52cfe42e7fb16164d3218777622f89580bea14ed586073df23c6c9da01574ae621f62af3728281a4c3cffcfd6f192324ea9eece0755366bde98b5daf0e2b9c58587c15f7b76023125dfbd5fadb374d71481f4427e8a15eb189ee2bc4cbe3bb3b0fdcac5fce8f46a31e9a33a1ce34b8dcf22990f9e15218f67d8ce5470ae819fbd3781424adda93c5a443cbb6c209b9ca424c0eee28ab168f05331b610606f7a9f33ed3ea477451373c0f3578017beacc7b15fae66c0b11abace6d3860bc48977b4d2a9ecaca3b0e3f43b4583f86d560ae1ff0e2cb77edb5419deea3f0cacba39780fc58f3ce0c29c66a3591892c176e1a7d3bb54fbcec8f79fa2bbc004229afd6fdc18c4cb89edf1459ee314763cafe1e75287615c3dbb7f5b05fbab0a9adf444c01a3227b51509dc5bcbf4e6a2eec548d68d1fc6d1bb52673c7efe303c80b9b1cacaac0cfd73dcca1618f30116410c8e6a1475fc191bd6a080b0166087aa6531e59f9d96dc30101580f2cac5efda8fcb7996e085fac2ae03f7b0a460d5cab710600c77c4d3f7f6af775a422b9af2b687d6cab4ddff8e6190e0d9985035f6529e1a277a089ad1b5894cd8f1261918587b89e66dd9bf5891943a319b607ebf127991ac2145696f4b8489d14fe076983381b0ceb983014bea17aed90d90a79d2c9c6e58453483488bf5c892cce23273e98ba4847f39250b126885643ed00540fcb83defd5588e0a342a4d5f73b815e8b18c7d0c7a9aa32b86e65783d2b07e2465870e9a8e1364e6baa88dea32a47f215f661a692d52c0ddada551bbc8d0db7a4e1e987f9f330b0abfcab7318c30fcb4d801f008bbadad93172b8d8f0ce085648a5b033e7f5921900756f12d59d70334cfe24a848e1b85d83e316d303a100add604567b2f5703238410cb63177a22dc934864af6763498346f6039ff360352519cca87dbad1d3f8003bb46a9072b8b396114731d0e1f0df03fcdfee3a0401435fcd82fb448d96064ca438928ab3f2dbc705ef16e9b9dfd65ae8d800a49c0162c35f8ab595be1da87c6c3a8e10886b16519589f22a260d1ef3c2ab11ee9204bda92882ad266b29b1eb64f959c3cf828b27afe849a61195870c65d34ea70706c0944f8091ea483dbc06745a8658dcf5749cbb4887a079399af13cfa69a306088e5bbc7389fd33bbedc0f8b2bcd730b8fa0fb7d5c957a4551e9dce29c4b15f04e057cd99771aab75a7887b82cb12453cea07f785b4ef560e654eac26a079e90a9d3a60c39c1a3a301b9b3a2a0bb7c7071c96ce37bf59b1b847632ceba30b5d044c532e896c29fb2b3769aa0f7495f97a3475a718ce17479f805ede3c893c8d0c0e362207bf9a4c5a48ca988550185e3d010a52b4ad65bce64419142d7238c80020e43b99cb80ceb18bc6a2b045333f8ec169211dd26dc8dfa398691f1d76d30f1b4204a9113a1e353594033e50c908cae266ccd449bae6e030a0acb6619b360f92833140fe243c5be4bd1fda19455a96818f3cbee2232383fbb909c615d1050b687f4451524d507cc9130149899e040927a553b90dd324d57db73c08a5fd0e013f08be740ff23243f87b2c0f4fab5d65edf4e7297ed774852e423e9412e7be0f99a677719928d879cd56bcabfd4dce3ff96a7c5ce34f23471268f42d4b73990295b03026c77645eccf963218535168fd08ab0969ba97b812b6bf3a598e710f09450dd5123579658951b21eb143e4b76ee660a32654a0723e48d2069dcb52edde0690dbbb2bd0c2de2536eb40c89238127c48085de1eb89c8f5bd3208e2c2292422bf268ade80cb5331cbf74cb7338f5d8c99b106b837d0b7679b40907ce4fc4a054bf6418951192254108b6765270f70cecbc01f3e145ca05f1123ad79f8f09bbf7320af0e1802202b1fccb190fa8750d845bd8ea324bcbf558233ae7b65835aa84a144c11883a3a19c084b9c9a9e4a4fbc4a722294a0bd35a8374fa6910fe4b635c8ab7bc1a7951f0f16cfea09a52ab31e5c85848273df135cfacf630e2e8db9728ef733e2adde7dc65b29d6816743ee4b57cdbcc507ae8156edc3f5548e616439cc02daa04a7e0edfc5755b0463072cd9f1480c33e7730e098e6201dd68c35574847b37652d8dfc7ab8eb88788fcc3a54304f69287396bd6783c33dd2f9d92e6bf19bd48cd062b5a5b222e8cc9a84883593261ee7308701453e7250749a7b0f26a28be17dbcbe096e717476b12c1093da5718771a43de20d9b51b76c3a206946f097408973c8441e5a94cd160192ef7e6aac57b8b82ca809f7e504fb6a48dc58a18f5fa5952de6df1f3af19d3041b908946d480ad2328876bc22fc174762e475a5502d85e878e536a78f06574df2119e5c514f8e78910659bf079b189725d9ee0918829b2772828ea6691664868283974656827c44687c47b6164aca68817a888859e80063da37ac442386181fa238fb2db123b49d3f158c2b8a444c80c9c9", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 69812464, 37032133, 1422726, 375448, 406581, 449851, 89069, 223269, 53240, 11155, 7623, 1417, 10229, 2962, 8755, 3275\n ], \n \"k_image\": \"dc006e92fc1e623298b3415ddccfc96a8cae64cb7c9199505a767a16ddd39bb9\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 103642483, 4588979, 374943, 521809, 26666, 551559, 3541, 78919, 74119, 7697, 22074, 98, 2069, 12520, 991, 2010\n ], \n \"k_image\": \"d656ac13a64576e7af5ca416d99b899b0bafef5e71d50e349e467fa463b13600\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 100689894, 5890444, 1600331, 1239916, 10246, 40615, 93596, 29367, 209178, 31832, 15976, 15524, 7738, 4618, 18000, 13238\n ], \n \"k_image\": \"ca559feaf79de4445ca4d2bcc05883b25ecff2f6dd8fd02a9a14adea4849f06f\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 105335893, 1720661, 603531, 812185, 907612, 95130, 38652, 233260, 38021, 31218, 30355, 924, 43414, 12072, 1861, 5511\n ], \n \"k_image\": \"c5b7d94e661c5eb09714b243f3854cc06531b1085442834c9e870501031b73da\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83622788, 22585650, 1698763, 363844, 674080, 176911, 33802, 60550, 34474, 499122, 34507, 109012, 2600, 2390, 11762, 678\n ], \n \"k_image\": \"a5ebf4914f887ecdfde8e7ef303a7f2cc20521a2a305ba9a618e63d95debfb22\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 55266410, 50443346, 326237, 2547259, 482686, 131728, 345875, 70764, 60539, 195316, 10817, 12486, 12413, 807, 3774, 430\n ], \n \"k_image\": \"a2b08a090f611ea1097622cc63a49256a2d94a90b8dbaaa5e53a85001c86d55a\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 104851338, 4432225, 322623, 60187, 68448, 12770, 87943, 31366, 19015, 11424, 1280, 4638, 1761, 5270, 251, 2\n ], \n \"k_image\": \"88f7594b26dcbaff22f7e7569473462c49d8fb845aa916d7a7663be8b85b8553\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 102449564, 609569, 3134675, 460320, 337596, 206075, 2480844, 10681, 62016, 8415, 133990, 3772, 6574, 1860, 4700, 386\n ], \n \"k_image\": \"7d805459f05d89c92443f43863fa5a4d17241d936fc042cc9847a33a461090c5\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 85856596, 18666365, 2117553, 3040618, 42521, 22399, 31727, 90148, 4179, 5683, 1473, 18506, 177, 11519, 809, 461\n ], \n \"k_image\": \"65bb760c9a31da39911fa6d0e918e884538f0a218d479f84a1c9cca2f9a5f500\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 98769080, 9556352, 1282527, 34176, 70608, 21210, 55660, 5274, 64238, 34977, 1007, 102, 8595, 1858, 4821, 545\n ], \n \"k_image\": \"52418ac25be58fbfcc8bd35c9833532d0fa911c875fa34b53118df5be0b3ba48\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 106117516, 1882646, 1340386, 177924, 75683, 23939, 173054, 41750, 12613, 11467, 26614, 986, 12504, 9537, 2244, 1642\n ], \n \"k_image\": \"40e57cb9a9f313f864eef7bf70dea07c2636952f3cbff30385ac26ee244a4349\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 93868601, 4410106, 10612916, 475622, 117796, 118856, 41271, 73043, 1460, 42279, 39533, 56372, 18003, 6324, 28140, 613\n ], \n \"k_image\": \"38d739cfb68aba73f0f451c7d8d8e51ae8821e17b275d03214054cc1fe4f72d6\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 105407900, 3217068, 803785, 210848, 12516, 136889, 48021, 10018, 10953, 16966, 15097, 6490, 2934, 959, 6245, 3656\n ], \n \"k_image\": \"1eda8e08b1024028064450019b924eca2e3b3e3446d1ac58d0b8e89dc4ba980d\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 83903567, 23002177, 555328, 1306991, 335319, 250467, 189789, 255613, 34743, 46807, 20533, 1166, 2110, 3917, 1713, 649\n ], \n \"k_image\": \"1cccfcece29fbd7a28052821fdd7aac6548212cab0d679dd779a37799111f9ec\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 72075355, 24982880, 8841419, 2959414, 91310, 33396, 253541, 140692, 134207, 323001, 19328, 3000, 36052, 12670, 409, 3547\n ], \n \"k_image\": \"05138378dedfae3adbd844cf76c060226aaeddcd4450c67178e41085d0ae9e53\"\n }\n }, {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 87891367, 12499871, 3444223, 3817265, 682721, 1065395, 247912, 136321, 50631, 28848, 18744, 14334, 11562, 167, 213, 718\n ], \n \"k_image\": \"0007a41ed49aa2f094518d30db5442accaa7d3632381474d649644678b6d23c0\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"8c2ba531d06e4ac990213d765751e89981303d4714d81229ac09e385c8ac1cd3\", \n \"view_tag\": \"c9\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"0c9d3c41171e04e42f6ed61a932c2ecdb5c0103d7cdd909fa0ab15881469c66a\", \n \"view_tag\": \"f5\"\n }\n }\n }\n ], \n \"extra\": [ 1, 184, 72, 194, 203, 248, 224, 238, 74, 152, 75, 246, 69, 192, 230, 17, 132, 80, 151, 16, 114, 161, 141, 35, 182, 207, 113, 111, 11, 246, 129, 253, 64, 2, 9, 1, 208, 254, 203, 171, 207, 80, 220, 87\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 236860000, \n \"ecdhInfo\": [ {\n \"amount\": \"dc9c59cdde692b10\"\n }, {\n \"amount\": \"e76b3405d76aec5c\"\n }], \n \"outPk\": [ \"479df6fa3357cbddcb22e20d2b458011dd31dfc1c78316bad7ed9ffd39ba12b8\", \"6a8ad571f7f5aa9668806d10d2b03c2c84c90690e4ddeb3dc6f870fbd5df5b09\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"c55abb99bdda211773d14dcf7cd68ca2c2e1df64896976c36da0b72b9b0b5abb\", \n \"A1\": \"f68499752558d3b7b342a554f91ea3171cba93075d35968565e37020bd60f6bf\", \n \"B\": \"0086548c6fd600cc04f704607fd008bdd1ef83ed341109114d9301c30a86d3d8\", \n \"r1\": \"7b9a9a3a6cf62d4aedea2cbe4f48af3a830c33486b3e3c7942ad7b4dfbc32207\", \n \"s1\": \"6cbaf3b5e0afffc36549482dc4e5220ad7be9284470405e2a889dc4b0eda950b\", \n \"d1\": \"9eacce92f4b6fe9bbb9f853cd5a68b1ea882a66fc1f86df6b7f21ed49e21fb07\", \n \"L\": [ \"0c2330673beaf7961285d721e00c55e6d97b77efb12cfcdac429cbe92fe7ed64\", \"1ae9878d713edc784573be96c47e80dec59c487590f721dda777066a66a388a8\", \"cbd007967d90e323d91cae997f9449518ca71dd5e0473cd88d52a1b63f4994cb\", \"355fc68976cdff244295ea25e9d579eeee92211f52ed942307827480bf639e9b\", \"945e1f12d5a0369f990945dc143dcbb3e7bb7eada7b4e10ccf65128ab3a203d2\", \"7441035465a96cac073e12db24bb696f38587021576582596bd0351ea8dcd301\", \"23af3e518560a843188e19aa666d9b70d7c55b2072c3813f05084de714988a9a\"\n ], \n \"R\": [ \"e21b543d2d50a2b7949e9c6ee3638731aa1f19909ac0978ef10ebe83e52558f6\", \"e96814d9b64fb0344d8a37e43e9933a9bcb087173ad895400a680f3f7919a585\", \"572b4c0639855b4a146492b00c82eb9d7bd9566aa94387e79f80561676a045e4\", \"be44d3a42061a710a8c5be6ac2af7531504911a4e17d27249f7395c40ca90ac6\", \"4e8a0eaee615e0a23be18c2a2b74e5afea867b60acf8ddf7301055d2e9b6556e\", \"2b8750caaddea43764d08b5dbe1497cc7db2be2ccee30629fb313078fdb9fc4f\", \"9f01b9d7e2e9cb90fdf2654411be9914ac32fe797c427f2a3f5e0ab974226f24\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"3363e401c6214a87d6d33bd5d66a6f74f273c9eaeba1210b64089c7ca7418d0a\", \"e9ea96a6e3608b004341be48b3fb658836e80e9c3c2786764db9e239d115c206\", \"e2c5d846b20c33614aa533f068a9363f9e78b728faedd5fd5ac6c2b427dd0b00\", \"278d9000c2872ff0d2506b842847a65eaf93d1f5ed3267d63cf9128158e1d307\", \"aa6a0c69a73c3a1b9914c285981e40de53329e666a98e065354dae1c92765d0a\", \"f2a1b19b7a3f102fe8a7ba53487879d77c8df704ee4785bd27a6083ab60ec006\", \"81a849c4a7064e6265c60e06924fe32cec542a40b1c89d81356cdb200e842903\", \"3f75ee3e3749acce3d130cca4e7892f68cd7cdfa8e122bc78530b2e8d0176d07\", \"ebae1b0281338c2b2ec14d71d211f1e5500ea3bf298e88b2bb39edc1a4614d0d\", \"5048bbe60650d351ec2ff4cafb7f29ed646885572bab7ca50503bd7cb1a6e407\", \"75276f4e3914a473acc0a6520ea22947173052c525ff7f69b4161bab3f4e5509\", \"7adc053156eda0d8d255298da99b7612756be610b5d4c2d4c5622c2e3d45a00a\", \"2847e2e122a38f797072440f3b2860dc4a184d42f64b1a3db4c26d43cc3c6a04\", \"0a1df4fe0cb81346469ee48b4c64ca3a163a234dd107cfa155d610e859d04405\", \"d90e1d6a6c4f5b5cd93fa65fc80abff6607d8dc1dd932abb1a760e5a05b8d900\", \"03f7429d83df3df24cad2012bd01134cf6ac445145c54434b6e12aea99ed030e\"], \n \"c1\": \"66902fd5bbbefe4ec9d59d9e38da2e3e182da804b9cd69fc6516e5c42d12e409\", \n \"D\": \"807e7e4661a8300cc35c9a5aaf05c74f315d301d3b58af8423e8b3a9dab2a5f4\"\n }, {\n \"s\": [ \"7cb7bac961689da9c8480fdcd7699abfc7b1846b5406095930e305225408a80f\", \"ff283d2ff7b05766873ea25e37a51af6e80fe42c040c09e1254fec0c561d7005\", \"078faf699880bad1bff0adcaf0fbbed6a6b708e993738fcf3f51307f49427b0d\", \"ca0a31c36ab9453b909cc184d4c63908b94427deeaac5521a838ce0e7c654708\", \"2dac768455bb669ab5297535a143999b247d8999b9c8d041a2eb3bc3d152c50a\", \"e586639c731aa945cc0ee851bcef18a27d942354cb8d66ea356d8c2c04eb490e\", \"54f4fd74727b0237fee689ea8802a8d8c885884c216702e32c8e8769430e8709\", \"954ea828466f98d09e756e5ed6c12ef5eff3a4ee36c990542f935a460da46903\", \"7bb52fb8e07f50801ab6a9b81b8eab75b73a7fe93b9d65b75679b3103d5e880a\", \"a1d7c47088f50c690def8612384bfd2f01dc17e184a9d0529735d34bba8e0704\", \"c1d20036078d9394a593de43e28afeed8e9f9e37a283de8bfa383f333281b502\", \"baab61d08ee55dc83a7a7bf7b9be015aa90cfaa3cf2c7976eb1616b6a5327707\", \"9c48c0cd37af84fd9804839055052758c47dcbf861d6f481edd26426abec9b0f\", \"23396a7e14bd9f6ce9168c88eb5cc00c16e92c7133ef6cf66c16ab51d6209401\", \"f49e4a023ecaac0ec7ffac5f168d4e7d4ccf098bf7c59a769430b9131bad6500\", \"fe9c562cafea11ce248b40a51faf4a9a380b9abf6407f1184c05ab6f1c2caf0d\"], \n \"c1\": \"83d399b9afb3f50c7d1b305bb321c14df0787e226ecd2def7bfc6b7c312dca0b\", \n \"D\": \"255e606d60022c07faf74422223d45772072dc8a7452802ec6e0a208deee8d7d\"\n }, {\n \"s\": [ \"a374add905f27f162b82bd779d88d0f533b9cb550de4b7a51679ba3469bf5b0d\", \"003038393e91408af3ab39972995190a27c377fd568695b06cf0a2c0f374680c\", \"7e6b2a71d81eb28f336edf96840978b03a9c42bc72c9004dea1ed37d1208670d\", \"a75ed7d6209b278e9362c880ad85e32253589c0ba3fb6a613df14128040e4d0b\", \"37dcdb26edc3ed3d88b1c2608439d2b930cfff4eea3c5386d27928ad0f8af70b\", \"6e81677aa7c2ceb9280628cd810e5b7e175393342d5cd560ca14ead9574bc20c\", \"bf0d7268a249e56526af3029493eb31fd3d20160aa32d30a0f53c44f9d829701\", \"05a30031d96eb6efa0bca4d61a6739c37cde501a33ed7de3b8f1fc3ea696830c\", \"52fa1350698901ad198fa276f73406da4a2d4cac670860fd0da77c42d9c67305\", \"e4044076d2fb8bf2765ecb825e468e698130674102dc58f3c7fcb8194a6ad605\", \"46555d48c63f1354f34db8b6c6a381a4052cf4fd6a30ee2274af2da5684e3d0d\", \"32f2ad376e21aed1ffd8b3c0e48032a16283c3ce6f12ac2cc90b389e81f59c05\", \"3fc9310d8c947f78ea37f66d2df64ec8c4e04b8bc58801a6a362802ad8c26106\", \"743aa699d067999feb437d92b438ca45bf1a8d1ca5106f6c8609f277a360f70b\", \"9237b6b9a393af4c7af6da7647c4bcb7b50da483f82478af3d99bcdbe4ef5e00\", \"464e5e541e187854dc98a69aa48fc444019a803cb71719f44b175e968330850f\"], \n \"c1\": \"4cc9c96f6c2c24075edcb03b79a8d82d83b49db4a33b7eb993d48e4ece3d2a0f\", \n \"D\": \"524c847d7f15c03e03708b96b6ea8eaca6a4657c3af0244e6401779a0ee21221\"\n }, {\n \"s\": [ \"fa92f13efd7721c5cc4500e7cdb8254997d8b73a7b2243f580a5e91cc246910e\", \"7fa7d13567b4164e6911f6722ed13aadd2a76f127a3435a4bd919836c363cb09\", \"ebf110b68ed56b28b4e8bd5d3e4568adfa509e914d98eb21aeaafb6734e42f07\", \"10aaa746fe4ea17c423f38ac951dd1007dc17486d2b823ecebf9fb9dcb62dd0d\", \"e523f315c266de82c74f0378fcbfd2b030c0e1e30b034ec8f7d27946b8c86403\", \"863350520c89e34975e27b05df44cbcb017e0abbb6a756faf0b34f43f2752907\", \"9fa6b98c8b7a26493a215c954448e1958f6f1aa558cee11352fceac48273440c\", \"199f152d34e7d3c805b35e61a2ce914b5e03dc5d7858ec15c44413e5b2569c00\", \"f31567df8324d01565d4e4e9e04d86f75f24dea9d9d24568cc0ff02740c50c06\", \"e0c1712ec5b711aa78a21e7433da47430ed76a9e5a20748ff5df632a9cfdce0d\", \"1e54ab98f2bf59db008b5c6a03c2d47fa12540d1c927fde4da851325e28afc0e\", \"fc1fdd8a0cb2544e57306a0b5f486299a713a8cd4aad16b38f3c1b789937690b\", \"35287cdb337f33ce203d6303892c64e8be17016436717ff0c5c1af6ad7082e0b\", \"0bb3ca7bdf4a6369598e25d28c42189fc879daef22144424854ba28fef20680b\", \"5d32e9ae7872d9c134d6d9fe5b0ffe9352ceabd3805a6af5ac2b7b6234af960a\", \"77523d6e80b27849bd7f784a98d2422ff79c902f9dcbe4609c429e5b4a9fc90b\"], \n \"c1\": \"15bac05d97ba07fe3734d3f7491c6a962ae942f382a7d28ea1fa5302c51bb40c\", \n \"D\": \"cfdbd190f3286cf798bb345b51723f6e8ea8515534773a340613d8612db8f9ad\"\n }, {\n \"s\": [ \"fff3278d190f7894a6ae534e2ef7a0a8b7b2fae577aaf438b62ff0e4d79b8a08\", \"56a76827f3e29042f3cb444e67dc16633c30d8db17c541ab4db7d81fc5a32f00\", \"b3609aa0b8d2544843206117a826e456a41aa87d80d320c09f1ac0f93ed8e30a\", \"22b70ab8b6be5dd1288e09f9c20906d6bca5f0eff1e2ed57da9465ce60ecc00f\", \"aeebd450c3c3ad2453b75c18bdb725cca06d8cee657c77564ef03cda16d06f0d\", \"8e5ac1cce961abddbb5879738d04997263622263d9f27ee8d5868814afc7a202\", \"4e8807bfbf6943fd0792d13b9bef4ff83ed82f0c34df5f9c42aa472cc2c1ac02\", \"3e152321855eb77e8f762d5b6937d9e7cb3a9f3c2419b806203aa2deb57ae906\", \"80a3c83cdcee63d0bf296027ef22cfac0938f9b115359e6c6d91306dadb2fb04\", \"41107ab8a88b18f0bde29fd68432767c7ef193cb6815c4456eb0d917b9a9e80f\", \"4cf02dee189d4b8accb839002f8b6eba365c9f3f693e156932c09941bfc64600\", \"fa9a1ed117e99068c3220c9f0e94b9d7c122c6c391e8f6dc0ddd0ec8097f4d0d\", \"a7e3987fc86c5523fc788c68b25f86ff5e347849e71171ff0f5c6b15a0b50107\", \"6b1dcd3e4ba22639beba069d6c813548c99db963077c1f2bcdd173f38e79bb05\", \"b450a4748c5959889f22d53f62476626e22587a023dfc4517d49396b0c2efc05\", \"817d8187aee15d20ef0f623615bf4da108d2429e596dce7d8e9fe6e8596c0600\"], \n \"c1\": \"9824b7bc0d7dc58a097658babc3701f19a7210f2c197eea886c5d00dfc50c108\", \n \"D\": \"570002d437713df562fa1e3d67f70450db016570e1119532ed0e3e1bdcfd3155\"\n }, {\n \"s\": [ \"792e33a1110e87d24c8eedbd1b5d731c4fdea67a7d31168432e7d7cfd6564706\", \"d6a5c45ffc307daa0cbc600a5e6e3c9c82d493388ada75deffd3c83906dba60d\", \"6de0af97e952ddad74e59b92013a6e55d63a0289e09feb33641ae2bc1b2fd60e\", \"33ebb741b522f6c0d47a331846edd4f4d82223d193e0e5aae4b88750280b5702\", \"afb63b883a67e6a442ebaf9c7c60be8037a040c0a6e1814696ca2238717ecc0e\", \"f670db23a3063db45d74557d715d643255f5cc2b02ce024a42a2c4f48d0a020c\", \"4fe82d187b3b6edb7e2e9444cb2cb3b8862523febcd7bd5e7549b5031eb8c90c\", \"27907eadeef4328d325dfa862735dc9ba8ae26df2fbf9712ac4fc813122ea10c\", \"0ffd43cf70c217fbfed3edd012d66d959d03aba43873f87634a3ec91efdc9209\", \"86321b3ae6152c4e1688b81775c069fd5a41e71e2dae304bd5c479e3247ee20a\", \"94266393a0761debae592b659f32e71c39343636ac1ab2d3732af1fa9778d701\", \"fceb0b71bad10c860e51e637430e6520d5639c6a1c430aaad3802c30a92b500f\", \"e55616cfb92ae1ce65f3eba6285da428c7849d24918f6d3bf5bcd8b37db6a403\", \"a3220064e82e94bc8a3df93e39332b133bda28c866f30e45c824039e448ef704\", \"59086810004d7613201583e9497b5c17e878238bc1280c87ed2381b8fd308606\", \"56df5be83e02f18e2308f2546a553299ca285b53c7f66ad0297c907e01dccb02\"], \n \"c1\": \"1d0e21631768d826068eeb457b877f4009977de0fda3d981dc3887515060c50b\", \n \"D\": \"6cbeb0704498abea125f623107f97b33daf3ba02d619b6edb8223167fcc1df4c\"\n }, {\n \"s\": [ \"1909cb506805d1f464b1ebeaa69a1b1a0cf02387504296c7ce63c61e0385f000\", \"36dde496366c9988b48d904c4111379548fa829d55787ed9c629ca873862a406\", \"48f6f8e24387826471f8e6047baf0babff8f21cdfb81d4930749638856f2340d\", \"306f636d277ae1792f84a293e4f66350c75023a1b5967eb40a43c440a246c603\", \"42d8e7ebaa4ee68b8551b9807d7b308574a90a7c54134112d6fdb3151af2c90a\", \"2463bad24e336dd34bffcb34713b6802e711a168e4afd30bfc3dba82550fba03\", \"6a8d9f5070875b622865ce43fa773e77ebcca52dd7fb846aca9c133eb93bfd08\", \"030bf7d2f95f541cee1f8ede2d0099177bc447d8c67a6d3b05ee3183498b1b0a\", \"9e8c271f40e36941df4924502e31d8122d28fef01063e9692152371ffbd44d0d\", \"54b527ba4a550b6191a40192e1735f9e9634df5647c3580179ed7f11d893890e\", \"968845f97aa642bccfb6b9e84a4d68189d59fc729f23059dead12b5bce438403\", \"aa7a1ef89da4ac0e4617a978acd99ce57213509702e31941dcab7530c902690a\", \"7da1e1c0ae121c31fa19cf8889ecd8b61cf086be7cfe695b172a36cdc326d504\", \"e55918e88a21efc120a54ffb6f852d5b97ccd7cba3ed1e44b6fda91be7b5de05\", \"89ee42794cc797c25249a9a6d40f873e52b491389065a950c8aed5b6675f5c06\", \"6d1dce8ae2cee39a8f7b1c4adf5fba44fbb41a81b06a5ca9b597ff6081d89a0e\"], \n \"c1\": \"df9d832bc866de4095119e7fb0eaeb8930b705d0712aa17e11754ee27da80105\", \n \"D\": \"befbd6dfc2d3e6e8a040c080a7d4508621474e11a3ba7fee3a2d6ed428324c22\"\n }, {\n \"s\": [ \"d723f61c46b0dedf57ed8a1f5b4588f9922fa5bcbfe9cb9d97d558a4030e7809\", \"c1057838edc60be81f357bf466bc5b9fe05d58ce8009fa509db7d24e0404f609\", \"b7ade4df5611ef00761069d18aec275e33941db96feb81abcf87171b7d54f001\", \"3d88f8d39dbcb57df1b0a3a3463bc75995e8193afb08815de576c1de957b700a\", \"ba945788360754f9575a3d9c3909e56b171e193da4dec2789cc41ce933c82904\", \"e5c4c01c218680c44c46ff1d501ffef20d6a5b067c523ea161e115e7b0179007\", \"835c82d226c6d3f7615d13ee729f7c926d429a33302cc61febb9ec53d24dcc00\", \"322740613d4714cdf380dec0673929b91f0c173455c1d5f605b3c2a2998e9703\", \"09c59130a49a6dfe6b9f56d4ae04e21e9b14034e54e0b8745ba2c918f39d2f0f\", \"95e48323f664da395a8e2d6e294c202a51e5cb513a664f947deb2ce6c2700006\", \"32cf0c9decc4857d801b939cb2e97498832d469e9487978a447e40b25904f80c\", \"e5d6e7d6a32ea5fdbaa470d134b318b41c8ab7e8cdffec513ad4cc2abe280406\", \"081827c389a2c247d0ee40235dd62f1ffecbf6aa3389abda43298ab752f9480f\", \"898f59005b4d8a2fdc4631bf7f2cb10a6cb789dd028e5ce71f0cb3512b3ed30a\", \"497bedd241b6bbef8bc099340e859f20d47d928fd1bd804eed2b41e266dcb00e\", \"3ba7ab8b1d442ef9200458654d613fa08ced2b8d86cbc27260e099bea23e4904\"], \n \"c1\": \"e5ce096899cee734b4a524e714c1b98e9acf571563328ef71d45e3bf6fca2d0b\", \n \"D\": \"afe7b32ad8919db80210eaef8c2c89bcbbafabcd6d5292cdfe439c1a9f1fae66\"\n }, {\n \"s\": [ \"090411640c6ccf905547102b49f7a025a3c7e095e9f7eadd457c147d96210208\", \"962f737018ec057b427e2d31ca7eb58b25e9fc4cf7cd9550129035d6b98da60b\", \"a7919d4fe9b99e4da7598fe85015c43be4eca45d1913458e9db9bb8a155c1a0e\", \"b64774939673b8914c6bfe98348182ec3a222da702175ebd2b8c970bb878d807\", \"858ebb7d35f6675bd2f4ab35af156defc38ca3fe0ec5b305f2d13481bdfaf40b\", \"09d0076aa6d6e2de0a284fc8027f7ef58df0eba11a524764b4e939371566a50a\", \"2f1c2171207626099ea70cbb62112001ce82353e7640b91370b2a277047b9903\", \"e6aac7bdc67db5d1e316b7ecd9d26d9efa52a1427aef62ffe91d19e5549f9106\", \"059a55d8085e937920beceb69b6233a950913aedb6c9913853257d80a7f2df09\", \"240a0b0e2265f861f25f3b3f020295e4c3a557d8f210a19713fbea955e2fbc00\", \"aee13bb44e9d935d547cf7b071b05d94292a4d753018e4b2058fe919e98fca02\", \"09f9e3815440b6b34990f49af2210cb6074f74dc7bbf5804a6a2986c2a1fe60c\", \"70493fb69d8a0a256d3ad34313bb2d88cd9e3dbee169bd656369c2ad0b598e03\", \"e2d43843317dcd53af92eaf7de73f89d0aae108da35e9639a951cc4e6bb6bc08\", \"478d9108aa862add6731184656515718c3bbe4c666bd3730ecbb150447fb2a0f\", \"6a1cd0c4c26f836163f5bd045d30243b49e10bedad9c3a974faed2469b128d02\"], \n \"c1\": \"025202ee0e8e262e50081c08bf278b9de30afe9fa80ed99f0b50915bb8375205\", \n \"D\": \"1ce7b914a5b6c8eb0e4a2de2f412a377ebfbe8e3f409ef8580b4878a380c11e4\"\n }, {\n \"s\": [ \"76588b8804b369130947a7b805aaf563125b08d79fc2a7a6dd59edbfcac7aa08\", \"de4ec98ad49fa4c88de7702238c883b2790c56b5752326843113fc7a3e860e05\", \"8ac662acfecfe2f803960414b6c5a3f0e16ba8bc45d2e9442b74d13f7774f30a\", \"f7f4cbb7eb1a2545914dd2562cd1cc69ca12a46b76e4b3d0fdaf520e4a4a7f0d\", \"d8ce412d8b0567f994a8c1684e5549402078b59200b8b697c776e6e58d34cd0b\", \"797938f4eba967963055852af7194e1d776f455e2e84402598e1ce415f872000\", \"0531b2935021bad2348c4b5976d3cf2d89626138a5f204102559c592a33be507\", \"dd596db1cbc03ab91f1f5a80729619bfb1bc830d88188e3f9593fb430d405009\", \"2f9064ce03ebf328aa662c7adf612240056e0418f531fe086f19509d44cd8e0c\", \"83393059bff678437c3d61acafab248c7b41119bb777354b836e9752849c8005\", \"4fcad749c5660627ac7a24adce564b62426563b0caeb16769ffd5afae4264307\", \"1d211d06f860445b0a384ea4275a1f6f2b0caf11ec2fead1b8a8abd71215fb0b\", \"a65cee1f28aeaf4fb54685c4b9c1d4583d05be800fb3b392c9c9b16d45b47405\", \"89c45a1d7adc9fbc4a8a91a6d95a256954408f1e1794b9b73c7fa92d9d1d3d0d\", \"62e035bd6a3c91f354608ac7d002f1ec1481b92fe1efb47dd717c58388432e08\", \"21b4642894dc1d95bbcb4bea812edd2be509e3723eb27dab02f0aa5174bfda01\"], \n \"c1\": \"1bac757ac05198bfae729198b21ad83ed77d7099933ca8d438223eb5172e2900\", \n \"D\": \"5f8d4bb439e9f1fd7d9319bc235a800ad8cea46cf72aa161403411fcd239c2cc\"\n }, {\n \"s\": [ \"c1caf86b559d263ec57ba80793cb6045195b2306dc2d3d1545b4f9f628733f01\", \"50a751cc8468d64e4fd87d8f6808c387112ea6cbd2747347a2702a1c53d03606\", \"1c256f19897dced055dedc2f6000444b891b2f0f103ec52169bef5620ecc8802\", \"b9c40573c76e1a18907d9aaa82cfde4f69fbe29a031ae39e9d47161b52561104\", \"b9c4272fd23a758596329701d313cf2a2a5a0783b9f39b0d9d376b1a53d9aa02\", \"008e31c110ea76d7f3dd3b6119d18436d7d87638aaa30c633eef1fcd5923ef0f\", \"702f504ae3e3157382c50c022725f50e0de515d1bef117ca08d832e098b0240e\", \"6c2be19c7e25a8f32f876f4ea74809a659e02b8f2ec908dbd119ccf0edcd7909\", \"94de8034f11691ac2cd6500c32e20287e968196dfd93de98d31a5733b19e7a07\", \"35c541ce2da123a622e10b79abd68ed242218fc198d3fd9bbe3dced2aabf0707\", \"50593ef0ddbcf3da82d63d30faf24289e054434fe9b2547de07056c2ae927f00\", \"ddf5820c7620d2b9cc0f5047fe4a74d163cc3813f5465bcad682ea0be2d0070d\", \"34b1b382dc98e343b261a1a605ce916498f476b2131ff2353e1f9a3ce3e71902\", \"f2820f6f514b72f32b2a3e2d50fad8c47129849e2643ffd978f3d6a8893a6a0c\", \"1dfb540d9405f41c1b62bba4b0893273535b223deb4bfbf64c2a8b6ac040f006\", \"10d5d9beda68f687ebd4035c39c45cd80cef2c15ab5f35f24af9c0560ae08f07\"], \n \"c1\": \"b50751c330e15f3ee3a8e07783dc00ca6b8f78d465d7dea2c8c83de74d5f000e\", \n \"D\": \"240a2046c4d4346fde8802305e568074e94ff89cf0a3d5346eb77564c35b15b6\"\n }, {\n \"s\": [ \"b5746f3412f0a78c40323c98a985c255d63656bf1115c4c826622d462b26f608\", \"130852be14f740b8570f5f4a5511b86a784c51a5e4fb8e945e31c8040628da04\", \"fcadb8cc0c23e4b24bf2222932c7893ca6b9904dca2ff8a4968d35a0d368ac01\", \"7496afccec6a815cb767f0b779ae95e3ba5260724a34f18cbc9204840414e603\", \"44dedaec24d73ccdbe4807b8f1555c074c94f53d94177fd1698997c431bd100d\", \"9a501dbae938247f5b77eb42505ef9cefefd4e7f42287903b316f036dacf8008\", \"9174621ca177d5a71c9aed771de721522b0f72c815e97b130f1cbcb83877010f\", \"56c9926851bccc1f48abeb9972a7cd765435ed59666c643e8bf9f6f0dbf91e0c\", \"32da0d9416c4c27b002fe1547ad2406532ecf44c89e6245ac041ac0ce4e4eb0d\", \"7ca53b3bab439481930d4e214b3b8460354614c9fc4a2c5035671c4fbc522d0b\", \"0ea983f506dcead626b4f7932bc9b7f497f8233a1556478c0786218bac666e0e\", \"a9e7ac6a29eba7c27edc4f25dbf6906883bbf4ec47d1ebf38a3fb70c0df46f0b\", \"0541a8b5e21a9ebfa0a08a660daf10a2dac0d7c8bba437f3b931c96455b58805\", \"b0d522d58f81354805dc073ae2dfd82bc827ae72807fd533687f78a450eba70e\", \"f15be34213fc5ca3b3762944f1d018c46e7bb0b9829132633e54528c7178b700\", \"0c3852eff49d00338308b216cf77fa36f67130ca7424138560ff9adbbec1ac0f\"], \n \"c1\": \"3c300aa9725662614ee790040e3668b7b512e4953b5a68a3df04f881978dac0e\", \n \"D\": \"77be03f07de3ea414ab5de8b4148df7b2c7548de2392fed2d26dcb61deeb329a\"\n }, {\n \"s\": [ \"434e34961ba8fc0a8533731fbf13923c60ecaece5ac0633100fd18b1a47ab40a\", \"9c3d10e14fdd5f1c448479b933db813f16c87d74e925e91a370be9d5cc5f550f\", \"3bec745edc71c836a48d90e51777f2c480f5338917fa9ab9ce258bcf6eae6708\", \"542e1d2ac11c524ee41c307b7deac63d13dcea39ca2a3fe38f4fe0ba59a6f405\", \"2485619c52efba1b68fb0f0ef5aa921cc57fd1d341f3556a1d6b73d17f6db60b\", \"496621f542826cddad8de2e517258ab046842f696b9c4f2835506522f9d2b500\", \"93ed535605c4815979bac5627c103f51e4ad001e6a600f0d0b4ca4b81f06390b\", \"14ad02ea0c3b0349824c1e0573549bd853d20cad9fa1edcca9bc566014f0af06\", \"528ffe2ecbaa066240c9bff71fd5ddeff863b27cb00a74fccfb1db5499a8bc06\", \"eb01cdeb3b01b261e61f3fdf0f3973a4e1104fb997c8b6e3ae436f1ce0353c02\", \"8c292daef3187740a653da65cf9610bc22e0fdd7dd5ae16293b057f31e8db002\", \"edb9e8b76d70aeb7d1aafa772a757c790590260f51c7671871f0df38f6cb230d\", \"84ab59ad81a924c2c8e28a7537ada86ea383b9476311c1ba4fc3ab2cdfec0205\", \"637c21cdb13444bc4b3a74e18f399b62fdabea074a3fbf866486b4ed86a6a406\", \"8b85c656cca6ed3e68b35175e1924973e82d462e4c2704e06609d552f5e81203\", \"d04fbbdf96911fd3fef07f359a8ea935d5a3657bb773783cdc060395d0081102\"], \n \"c1\": \"f50063354fae3f6e43ab851dc5c29fc36d48b1e654b09260aa5e62c743cc6b08\", \n \"D\": \"61f9bfa62752561e2a80f8275dc0e2abaf8f65572fe4fe8e8ccc2d91bdd7c646\"\n }, {\n \"s\": [ \"8783932798cf483e2b6793f0fbc08495d135ae16abd9d334a58bc97de071d10b\", \"f13f1dd379ef24f8e41f63787166417e18e5960f38163fb0b92a7127fa22ee03\", \"db3562c139a0887a8820e0f85f1714fd29a59d500474f92e1187bf9729c7210c\", \"f85a5b1f13d581dd7b8b25a709888f147420a493cc6e036ab9c6366c0e4ebb07\", \"026b0e3fd5b34eee445669452cf92bbab5fad18b9b164179b2bd489155c01709\", \"1a448388d6ca40cea2523618c4a992c37d3c20a7139b940417cdda9c7852bd08\", \"2bc30446d38f51d4d3ef9b4a8d2482dc05a1efe359d65e9fac6d673fc3a9570b\", \"123f5a609851be3b90461ccdd602d85a251164e1c757ed256c4d92b10bec9302\", \"a5e9f89069c73bbd2266ab755a4575254a43604b69965de8d131cb812e6a6e0c\", \"56a983915850fe148d329c29d15eec67464427da9d763b89c52995b32c1c500f\", \"f81d35fa348ec90c2f4680c1e073fb259df6a4dd145b8951e017948c29760507\", \"84ed0e7207997ffc619ef2ee4e53fa88af9aea63cd2f1a761151bcab58025a07\", \"e3a2b2ce40f9c25e0a88d82efd67c0fefa4cab7cfaa290865508e2e17edee808\", \"98d4eed281cd7a8d0e4090a97ff3cbc0ac62712528b367a32bea105840701e0c\", \"6588cd1a118bc1b8c4625a3e48f69b779f0f575cd2e505b362051021df8c1b04\", \"fb273a52cfe42e7fb16164d3218777622f89580bea14ed586073df23c6c9da01\"], \n \"c1\": \"574ae621f62af3728281a4c3cffcfd6f192324ea9eece0755366bde98b5daf0e\", \n \"D\": \"2b9c58587c15f7b76023125dfbd5fadb374d71481f4427e8a15eb189ee2bc4cb\"\n }, {\n \"s\": [ \"e3bb3b0fdcac5fce8f46a31e9a33a1ce34b8dcf22990f9e15218f67d8ce5470a\", \"e819fbd3781424adda93c5a443cbb6c209b9ca424c0eee28ab168f05331b6106\", \"06f7a9f33ed3ea477451373c0f3578017beacc7b15fae66c0b11abace6d3860b\", \"c48977b4d2a9ecaca3b0e3f43b4583f86d560ae1ff0e2cb77edb5419deea3f0c\", \"acba39780fc58f3ce0c29c66a3591892c176e1a7d3bb54fbcec8f79fa2bbc004\", \"229afd6fdc18c4cb89edf1459ee314763cafe1e75287615c3dbb7f5b05fbab0a\", \"9adf444c01a3227b51509dc5bcbf4e6a2eec548d68d1fc6d1bb52673c7efe303\", \"c80b9b1cacaac0cfd73dcca1618f30116410c8e6a1475fc191bd6a080b016608\", \"7aa6531e59f9d96dc30101580f2cac5efda8fcb7996e085fac2ae03f7b0a460d\", \"5cab710600c77c4d3f7f6af775a422b9af2b687d6cab4ddff8e6190e0d998503\", \"5f6529e1a277a089ad1b5894cd8f1261918587b89e66dd9bf5891943a319b607\", \"ebf127991ac2145696f4b8489d14fe076983381b0ceb983014bea17aed90d90a\", \"79d2c9c6e58453483488bf5c892cce23273e98ba4847f39250b126885643ed00\", \"540fcb83defd5588e0a342a4d5f73b815e8b18c7d0c7a9aa32b86e65783d2b07\", \"e2465870e9a8e1364e6baa88dea32a47f215f661a692d52c0ddada551bbc8d0d\", \"b7a4e1e987f9f330b0abfcab7318c30fcb4d801f008bbadad93172b8d8f0ce08\"], \n \"c1\": \"5648a5b033e7f5921900756f12d59d70334cfe24a848e1b85d83e316d303a100\", \n \"D\": \"add604567b2f5703238410cb63177a22dc934864af6763498346f6039ff36035\"\n }, {\n \"s\": [ \"2519cca87dbad1d3f8003bb46a9072b8b396114731d0e1f0df03fcdfee3a0401\", \"435fcd82fb448d96064ca438928ab3f2dbc705ef16e9b9dfd65ae8d800a49c01\", \"62c35f8ab595be1da87c6c3a8e10886b16519589f22a260d1ef3c2ab11ee9204\", \"bda92882ad266b29b1eb64f959c3cf828b27afe849a61195870c65d34ea70706\", \"c0944f8091ea483dbc06745a8658dcf5749cbb4887a079399af13cfa69a30608\", \"8e5bbc7389fd33bbedc0f8b2bcd730b8fa0fb7d5c957a4551e9dce29c4b15f04\", \"e057cd99771aab75a7887b82cb12453cea07f785b4ef560e654eac26a079e90a\", \"9d3a60c39c1a3a301b9b3a2a0bb7c7071c96ce37bf59b1b847632ceba30b5d04\", \"4c532e896c29fb2b3769aa0f7495f97a3475a718ce17479f805ede3c893c8d0c\", \"0e362207bf9a4c5a48ca988550185e3d010a52b4ad65bce64419142d7238c800\", \"20e43b99cb80ceb18bc6a2b045333f8ec169211dd26dc8dfa398691f1d76d30f\", \"1b4204a9113a1e353594033e50c908cae266ccd449bae6e030a0acb6619b360f\", \"92833140fe243c5be4bd1fda19455a96818f3cbee2232383fbb909c615d1050b\", \"687f4451524d507cc9130149899e040927a553b90dd324d57db73c08a5fd0e01\", \"3f08be740ff23243f87b2c0f4fab5d65edf4e7297ed774852e423e9412e7be0f\", \"99a677719928d879cd56bcabfd4dce3ff96a7c5ce34f23471268f42d4b739902\"], \n \"c1\": \"95b03026c77645eccf963218535168fd08ab0969ba97b812b6bf3a598e710f09\", \n \"D\": \"450dd5123579658951b21eb143e4b76ee660a32654a0723e48d2069dcb52edde\"\n }], \n \"pseudoOuts\": [ \"0690dbbb2bd0c2de2536eb40c89238127c48085de1eb89c8f5bd3208e2c22924\", \"22bf268ade80cb5331cbf74cb7338f5d8c99b106b837d0b7679b40907ce4fc4a\", \"054bf6418951192254108b6765270f70cecbc01f3e145ca05f1123ad79f8f09b\", \"bf7320af0e1802202b1fccb190fa8750d845bd8ea324bcbf558233ae7b65835a\", \"a84a144c11883a3a19c084b9c9a9e4a4fbc4a722294a0bd35a8374fa6910fe4b\", \"635c8ab7bc1a7951f0f16cfea09a52ab31e5c85848273df135cfacf630e2e8db\", \"9728ef733e2adde7dc65b29d6816743ee4b57cdbcc507ae8156edc3f5548e616\", \"439cc02daa04a7e0edfc5755b0463072cd9f1480c33e7730e098e6201dd68c35\", \"574847b37652d8dfc7ab8eb88788fcc3a54304f69287396bd6783c33dd2f9d92\", \"e6bf19bd48cd062b5a5b222e8cc9a84883593261ee7308701453e7250749a7b0\", \"f26a28be17dbcbe096e717476b12c1093da5718771a43de20d9b51b76c3a2069\", \"46f097408973c8441e5a94cd160192ef7e6aac57b8b82ca809f7e504fb6a48dc\", \"58a18f5fa5952de6df1f3af19d3041b908946d480ad2328876bc22fc174762e4\", \"75a5502d85e878e536a78f06574df2119e5c514f8e78910659bf079b189725d9\", \"ee0918829b2772828ea6691664868283974656827c44687c47b6164aca68817a\", \"888859e80063da37ac442386181fa238fb2db123b49d3f158c2b8a444c80c9c9\"]\n }\n}", + "weight": 11843 + },{ + "blob_size": 2320, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 115000000, + "id_hash": "c072513a1e96497ad7a99c2cc39182bcb4f820e42cce0f04718048424713d9b1", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261657, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261657, + "relayed": true, + "tx_blob": "0200010200108fb0ad269cfd890bb8a5980195e69e0184fc06c7f705d0d21b8df305b7c601e5da08dc0197ed03e6cd06a632cc7680bf01fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d0a0003dcfa3a2800cdabeea3c3206f05408adb96d2d6fdbbf704a0372bfee0535ca036f00003992edd42ab4ef6f21ac1639c8515b5930f39788788671122993164515bedfe4c8a0003e255c00974b24fbfd3cd45fd4004d6dc76201813ac827295b1eb04e7d14d10dd9500037d42e7770c696359631ae7e566fedd1417077d2f3bdee93240f544703b299ef7e40003755335d298ae54a80e4f3fca0c1066fe744d5a2719d188c0ab10e45a94ada4d3850003e5ccb6fc793c664acfd057d7f6fcc77850032b96702ce4d7112bda338c7aa576d10003f7e699af9f0b9ff887845f0b0600816a58faf680ad32c61ee4465c643e30a340d200031de678a8a50613c5db143e7b3d5aef14be9f1f5ce2805b3a3f2fabd0af950acc380003d1af1d1d909b9d07e4cbd8d4792ce3c719280a2c4a7618d8b2be89d3e2472dab56000365159da725872bbe45cd5b5a8eb8b2082c52f161c66130ecd74aa1690e31e1cf2e2101669fe64b9088044833435038ee59827b0b3f93b042e2ea7a2de803e8889a473a06c085eb36a2d1d7927d22c62713d484cfcc32ce08d57d1588f967ef3dce69f0b186e9417afe28372e3011410c15d178d85cb7992d5489eeaa56ab735167278d301a060edacb9944a0d91f4d1890403da176509a5a8fe8b16032ba1abf24aa58fcf1f37233985203599a5196678b96873b000d7e7138e1a39ec14709f7e44ed3d5a8d9be17401d2d4d362ce2798383fc2954c8f79876471eb52ad4e7cbbd0a1647771d11aef73612f15ca15c13ac18ed477bef4492b0b229ae04b00ac4a144329a0ea2f86e4decd8ab76e8f76e604a39aa8350777c113b99f7c2808a50c8556079f6b4b1e857d75ccf4fa43429e27d1bb205c9bd8dd2ccd6151c8106eaeda4ac3f28771864870d2327e129acd4a88a16e143901bdf17e2bbba7e8934c8b05bd95a35a04a020beb1298b8ae94c2b2bf7911ad173f20a9dd9a2dfd3929a3300e8522e0db027f391f8b03ae58064b76218608c02ef5771f3058852719367c5c7cef36169357e3b9ee637eecba7f28df2ac097dad28fc995a01f42f19181d1d691f92e8e2737cac55974f4d3e0f86f674c3e84cee205f60143108ec2a3072162e9b3b82aed46df599bd192c8f923293a398281f0c67e8aae7a1a1878999827176bcfc32df00414cd6be57d8bbad907564d071e77c625005d6e497cd58d30d420d5cde92a7ef8ddcdc5e953d55eb223e95cc1e9edba4049e7728542ceacaa8bceb6dc82ce3b0294eb57476103f4669b1b446fc8b3ff970409ad02e606b08b273e39f50344351bbe3e7f1f09437982f3c9817698394df12004635da6726be57bc727d6cc2488e2d1f1a7f64cd9fffcad5281176cb43cfc36040ae73b14501d732dda57d80108176de3a885cd7ca322e7368c5d1ef63b74d38d4f94a7ea1436feead534e9bfdbe5fd186199a172462f2165026673c883f6093633257c2b940a6a5c7dc3561e7685017a7a612d59facd45ad3aedee4433d66d7f6194c0ca2b30c86c26c07ff69a8fc5c568980c3662598a19f5c63259258dacf50ee6b83395d52fba08cb0305746c9ff74c8e0431ac10b65fada1ab841921b5a70538a2a1792830106d045c51f95043ca1ee492169208ec52dd8ce307025f5297d7d40cd94668079ea06fb9b65b2ca7d8edf58062cde615fc324b921db1611f24228ff52d986087dc7c56247aff907f7434b094e2d1f6c69be514196acbf758a3ab3db25b0a80c0fc3e9076e0a934f4cf5d2bdddbd7d9af0c47fe3dfe5d8de4dc203dd4a862cde72b72f138109137de2481ea146e6758877e7bdf4d98db15e97b610acaca6ad74c33497b2ca9a3e4445dd211d89156337d0b2e6bbc5ad5f6833f36b239e79526a48fc7b86cf9c7882af011404cab69de4b9c4f2b7929e7d23763eaf5fc673c2c2755231bf1e41bf1dcd4ea549e0b610d7a449337c499817921ef2fce52795d3ac0410faea86d480fe37dfe169ec177d53a83532d27b3ccfa7663c6d727a093c62d3f7988584f579bd146fde10992155bcb47ffec0d5390e4190aaf24a8709c9954e65d370ec79a47eb973fdcee672afa283c986be21f59db0f1b914438ae83c19c43c6a0113400e6883c84aa64a788086d2e6827bf125db4f23612d7e92d8fe260b2e1e76a235ef3ab824a000713ab23f34661ccc596e2198239f29bfcc05d99a86c42fe802eed8da93490cffadfc7adc848568af250835d9e926a71c446422faa9b57cb1ee4c4b9251d8e99c43635e7ce796d0dec1918393562bb70463c67a254e58ae723dd8c2f4534ced8e8fd68c2b655a046756cd42c93b4660418b166ac1ca569cc5642462664b87d02a15cd8fcc81c335d4a131b34e880ee01241d90a51e88fbeaaeb3952f8e2c32376cb883b28445d345ccd3991632732f0b315388f9b8fc18599ac2d74bc0654e8e6f684c87a274889384dc7393b3301f04e94dc23c3c41d08ad7cebe96c4221b9c1b9f02d24ee707a28a91e7e9f9fa84073be8794c7c34beaa52086b541f9904607eb115f2111dc96b5bce5b75454214098279c030920e6d1bfbfd083421eba2365d671467b2a2a3fcba832c5a6d86a60173f228a07d33596edc8aa922a0c9b26996502b33eeec0038f74fe87485f7b6075a484e14e6c961d8417dd2cbc48029b272dd42b935f27fe7ca7afed71dceed0b5f3a4688c6ea8b0a444b442ac0396a20784f4f9f94d025e2098c482717cf4e05a1862e45b8d12b90b19773857752fe4721a49a97eedf189d25c8b0d659883d0d3e2ce49e18a2f85ecf736191b9058f16cb65d96bf12be8300642c31400e3df05a8d748a1c84d932aa94c1337ed2c6303e4e4e1e4360cb23e4879760e60501007e2d2e18200688a0d71e5fb79211614cdf88bd2cd96252cd8742a2d8ea5914f0946752e3c0ffca147d0b962337b4e8674966b3ef8ed0f7698ea50e6dfc43a8105989c046ed697d5317e81ab30add72eadaf7d5cc74e68fd4b23e1d70a3ba43d0bd64720bb8524bf732f9f5ea45fbaf38470c693834b48658c4961e4d4987e30068b15e598afbade29176f45c702a78f0af5576519e177d355d8c17e2f2a859261369cc31747f01d8cb57916b59a11ff4aba68e8ea5ead7fa431f5389f8620f759", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 80435215, 23232156, 2495160, 2601749, 114180, 97223, 452944, 96653, 25399, 142693, 220, 63127, 108262, 6438, 15180, 24448\n ], \n \"k_image\": \"fb3e7cc08761a6037ca29965f27d2a145f045da5a1018ca7e6a5a5a93dbbd33d\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"dcfa3a2800cdabeea3c3206f05408adb96d2d6fdbbf704a0372bfee0535ca036\", \n \"view_tag\": \"f0\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"992edd42ab4ef6f21ac1639c8515b5930f39788788671122993164515bedfe4c\", \n \"view_tag\": \"8a\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e255c00974b24fbfd3cd45fd4004d6dc76201813ac827295b1eb04e7d14d10dd\", \n \"view_tag\": \"95\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7d42e7770c696359631ae7e566fedd1417077d2f3bdee93240f544703b299ef7\", \n \"view_tag\": \"e4\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"755335d298ae54a80e4f3fca0c1066fe744d5a2719d188c0ab10e45a94ada4d3\", \n \"view_tag\": \"85\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"e5ccb6fc793c664acfd057d7f6fcc77850032b96702ce4d7112bda338c7aa576\", \n \"view_tag\": \"d1\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"f7e699af9f0b9ff887845f0b0600816a58faf680ad32c61ee4465c643e30a340\", \n \"view_tag\": \"d2\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"1de678a8a50613c5db143e7b3d5aef14be9f1f5ce2805b3a3f2fabd0af950acc\", \n \"view_tag\": \"38\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d1af1d1d909b9d07e4cbd8d4792ce3c719280a2c4a7618d8b2be89d3e2472dab\", \n \"view_tag\": \"56\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"65159da725872bbe45cd5b5a8eb8b2082c52f161c66130ecd74aa1690e31e1cf\", \n \"view_tag\": \"2e\"\n }\n }\n }\n ], \n \"extra\": [ 1, 102, 159, 230, 75, 144, 136, 4, 72, 51, 67, 80, 56, 238, 89, 130, 123, 11, 63, 147, 176, 66, 226, 234, 122, 45, 232, 3, 232, 136, 154, 71, 58\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 115000000, \n \"ecdhInfo\": [ {\n \"amount\": \"a2d1d7927d22c627\"\n }, {\n \"amount\": \"13d484cfcc32ce08\"\n }, {\n \"amount\": \"d57d1588f967ef3d\"\n }, {\n \"amount\": \"ce69f0b186e9417a\"\n }, {\n \"amount\": \"fe28372e3011410c\"\n }, {\n \"amount\": \"15d178d85cb7992d\"\n }, {\n \"amount\": \"5489eeaa56ab7351\"\n }, {\n \"amount\": \"67278d301a060eda\"\n }, {\n \"amount\": \"cb9944a0d91f4d18\"\n }, {\n \"amount\": \"90403da176509a5a\"\n }], \n \"outPk\": [ \"8fe8b16032ba1abf24aa58fcf1f37233985203599a5196678b96873b000d7e71\", \"38e1a39ec14709f7e44ed3d5a8d9be17401d2d4d362ce2798383fc2954c8f798\", \"76471eb52ad4e7cbbd0a1647771d11aef73612f15ca15c13ac18ed477bef4492\", \"b0b229ae04b00ac4a144329a0ea2f86e4decd8ab76e8f76e604a39aa8350777c\", \"113b99f7c2808a50c8556079f6b4b1e857d75ccf4fa43429e27d1bb205c9bd8d\", \"d2ccd6151c8106eaeda4ac3f28771864870d2327e129acd4a88a16e143901bdf\", \"17e2bbba7e8934c8b05bd95a35a04a020beb1298b8ae94c2b2bf7911ad173f20\", \"a9dd9a2dfd3929a3300e8522e0db027f391f8b03ae58064b76218608c02ef577\", \"1f3058852719367c5c7cef36169357e3b9ee637eecba7f28df2ac097dad28fc9\", \"95a01f42f19181d1d691f92e8e2737cac55974f4d3e0f86f674c3e84cee205f6\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"43108ec2a3072162e9b3b82aed46df599bd192c8f923293a398281f0c67e8aae\", \n \"A1\": \"7a1a1878999827176bcfc32df00414cd6be57d8bbad907564d071e77c625005d\", \n \"B\": \"6e497cd58d30d420d5cde92a7ef8ddcdc5e953d55eb223e95cc1e9edba4049e7\", \n \"r1\": \"728542ceacaa8bceb6dc82ce3b0294eb57476103f4669b1b446fc8b3ff970409\", \n \"s1\": \"ad02e606b08b273e39f50344351bbe3e7f1f09437982f3c9817698394df12004\", \n \"d1\": \"635da6726be57bc727d6cc2488e2d1f1a7f64cd9fffcad5281176cb43cfc3604\", \n \"L\": [ \"e73b14501d732dda57d80108176de3a885cd7ca322e7368c5d1ef63b74d38d4f\", \"94a7ea1436feead534e9bfdbe5fd186199a172462f2165026673c883f6093633\", \"257c2b940a6a5c7dc3561e7685017a7a612d59facd45ad3aedee4433d66d7f61\", \"94c0ca2b30c86c26c07ff69a8fc5c568980c3662598a19f5c63259258dacf50e\", \"e6b83395d52fba08cb0305746c9ff74c8e0431ac10b65fada1ab841921b5a705\", \"38a2a1792830106d045c51f95043ca1ee492169208ec52dd8ce307025f5297d7\", \"d40cd94668079ea06fb9b65b2ca7d8edf58062cde615fc324b921db1611f2422\", \"8ff52d986087dc7c56247aff907f7434b094e2d1f6c69be514196acbf758a3ab\", \"3db25b0a80c0fc3e9076e0a934f4cf5d2bdddbd7d9af0c47fe3dfe5d8de4dc20\", \"3dd4a862cde72b72f138109137de2481ea146e6758877e7bdf4d98db15e97b61\"\n ], \n \"R\": [ \"caca6ad74c33497b2ca9a3e4445dd211d89156337d0b2e6bbc5ad5f6833f36b2\", \"39e79526a48fc7b86cf9c7882af011404cab69de4b9c4f2b7929e7d23763eaf5\", \"fc673c2c2755231bf1e41bf1dcd4ea549e0b610d7a449337c499817921ef2fce\", \"52795d3ac0410faea86d480fe37dfe169ec177d53a83532d27b3ccfa7663c6d7\", \"27a093c62d3f7988584f579bd146fde10992155bcb47ffec0d5390e4190aaf24\", \"a8709c9954e65d370ec79a47eb973fdcee672afa283c986be21f59db0f1b9144\", \"38ae83c19c43c6a0113400e6883c84aa64a788086d2e6827bf125db4f23612d7\", \"e92d8fe260b2e1e76a235ef3ab824a000713ab23f34661ccc596e2198239f29b\", \"fcc05d99a86c42fe802eed8da93490cffadfc7adc848568af250835d9e926a71\", \"c446422faa9b57cb1ee4c4b9251d8e99c43635e7ce796d0dec1918393562bb70\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"463c67a254e58ae723dd8c2f4534ced8e8fd68c2b655a046756cd42c93b46604\", \"18b166ac1ca569cc5642462664b87d02a15cd8fcc81c335d4a131b34e880ee01\", \"241d90a51e88fbeaaeb3952f8e2c32376cb883b28445d345ccd3991632732f0b\", \"315388f9b8fc18599ac2d74bc0654e8e6f684c87a274889384dc7393b3301f04\", \"e94dc23c3c41d08ad7cebe96c4221b9c1b9f02d24ee707a28a91e7e9f9fa8407\", \"3be8794c7c34beaa52086b541f9904607eb115f2111dc96b5bce5b7545421409\", \"8279c030920e6d1bfbfd083421eba2365d671467b2a2a3fcba832c5a6d86a601\", \"73f228a07d33596edc8aa922a0c9b26996502b33eeec0038f74fe87485f7b607\", \"5a484e14e6c961d8417dd2cbc48029b272dd42b935f27fe7ca7afed71dceed0b\", \"5f3a4688c6ea8b0a444b442ac0396a20784f4f9f94d025e2098c482717cf4e05\", \"a1862e45b8d12b90b19773857752fe4721a49a97eedf189d25c8b0d659883d0d\", \"3e2ce49e18a2f85ecf736191b9058f16cb65d96bf12be8300642c31400e3df05\", \"a8d748a1c84d932aa94c1337ed2c6303e4e4e1e4360cb23e4879760e60501007\", \"e2d2e18200688a0d71e5fb79211614cdf88bd2cd96252cd8742a2d8ea5914f09\", \"46752e3c0ffca147d0b962337b4e8674966b3ef8ed0f7698ea50e6dfc43a8105\", \"989c046ed697d5317e81ab30add72eadaf7d5cc74e68fd4b23e1d70a3ba43d0b\"], \n \"c1\": \"d64720bb8524bf732f9f5ea45fbaf38470c693834b48658c4961e4d4987e3006\", \n \"D\": \"8b15e598afbade29176f45c702a78f0af5576519e177d355d8c17e2f2a859261\"\n }], \n \"pseudoOuts\": [ \"369cc31747f01d8cb57916b59a11ff4aba68e8ea5ead7fa431f5389f8620f759\"]\n }\n}", + "weight": 5750 + },{ + "blob_size": 1879, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 66240000, + "id_hash": "d696e0a07d4a5315239fda1d2fec3fa94c7f87148e254a2e6ce8a648bed86bb3", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261654, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010d4cbc025f888800aa9b3da02d483d801b4c3188ad105b38a0ee7c3089831b542af37b3f901e68006885282d2028701428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f050003f4c8ce3812f84c4dae9fab0e120faf7e9583c5bb024674a4ceb36550a58da368690003edcedc3b21577e2cb73380c7a829cc6707109e672cee030aa007618ff033ed9e8c00037d3b66c65d73e2c172c52ef1336987487da2507fc7c6ca0579ff7e8d34c47f4d170003d423aefc2536b8839995f569dc5e61385f8a537067f5c00aac957cccfb5a4790b5000397fcebd0877bdd71d2a90aed5a58180a0deecaed068f9e43ce44df011aecae0e382101430ad497db87046e33184d9617daa64add82a53dc16982a056b682b2f9a2da980680fcca1f6a94c40de9d6fb8935d837180bad6fc5bb224d8ed00140afb3123c36a4ff194f108db26877967b024bbd0cb51d34a7f5cb26262573b9a66cd70635204820f80d01f04297474b9ca965cdc9302126eebe75aa86a0a837f9dcface5fa38ad81bdcaa40e1fe942f2fc2768f267feece915df41d9864291b57885c1e0f8e4c93a7a4610aef477cf53b805dad5a1006071fde67ca8c47f545c2fdf3202120ef652e0173b4257b91811f7f39df6d0c9c1d2e8f00074c619e9e4322dd006c41c10b67c4ed21aae62ce8748701865c7cb3df6f168e9564b1c7f0f7ac8429f2ecc311a10557feec8395cd8e03be8f38908c2fee85f557cdb534d9775ae9583f7a73024d9a7efa1d75b74f4e62b0a40954b6ca4c0c2091beb3021f21cce994f5a679c0f45126a83d9cb431ab6babb1d3272ed9456ea6af5c35037b4a89a23640c6edf1a4576d592bbc5dd0c4f40aa3e918712d4812f5bc8a01e38e6dc1d4b5dc39ccb5e26eeaf5c88b85d968c800e10bfca42e320f6b03c3065856b58908555a266b7308321ba94e80aab0956b0c09e2f1c8c5da991672dea0c43de4c27d232f50c35e3fc836ac8eec7d6466d8af2ce9277a92aca9f4d4d4230d1dfbb042871dc92498339a6225d6ac016e0e9dde852364c31878a19a2820bd196c1f837b398a56152ae01928f9f2fb80aae7301795da93006123b6cea54f34f1232a9f1ddafa340764a5b0b305af112c72c76a6ed5d5513739fbbc56bc027b4251b23a8c7727a2abf5b3e9a803aa3d144022d129fede2abec4f787d574df639bd48d0de0d1e981d88de680a7d40033b0f93b51ed6e60b8199c2adbc7de0b8f76802d8e74d9e46d2145a61232920571bc1d23fce134c02c268b523f5d5d31876d2259b2292597c15937c3057e5c4350391327429428ac6069bdd5db4515113d046f8058506b6e04399016157964b7c7dd41b929e9fb09997f51fa6db7903465b3cab1093ad38a783b67542e75bde42bbf5c07e415c24241017f2365a3e05b1bb8eab7b4ef681d3208a98715fd84e60be86931800276ce667812e9602afa5baacb0caa1ef9fc37cbac120918564ca04d73ce130ca42c28577d504516e5f44b843a76b2c1558d142e64ce4a55c34bf197a0251022a94e973f59a4352fe5e086e79fc90319e29834ab75bb24c4438c383f19ff77463d0f322566c1b5c64f1dd5ab8472ae4e8b4ac20ab110f5281a848ceca73330fc45d3366b7bd1accb139fec1ed99f15d11ef3ec2e7207a5e7bdafa136a85ccb0a55c3e77c86a70c2496fbcd0373c8aac6067731183cf211bbefbc7d71269d6ba62c98aa791613cbd267b4424763141075bf53d6c4472d3f7685f73f726b16caff0b90183a71c42ca6a27bf6d818abea8c67eb90ce4aca1f9efcb43215214336da99b4074faba4a99c9cfb429e70759024f3d5ce150e9e97e77c4f70501efc575c485e0aea5ed0f246930d73be9d8662f41a408323beacd53135df2eeea93722a00bfc026f5827dccdb05f8e9fedbfc7632b36324d891eb8220978da3ad6e4ad1f3e3507f4b7b8f4914326b28fda56141b2be6350c9c8499afbe66da9417d03598c40f05ec043be6b64b2aa2a0a5da6ab9825d6ebc03578ef5e7370ea63d051cb9f87e01933cfe48a5b22c49d6aca2cf869f6e98bb89c51c6fc9a5cea61a03920214aa0a014a19d0f3fe97670e3990021dae0fdbb5ed4979b1e2144ec3d1ba4b68c39205a86c3350cf2315a25c4ddb21d24ff2a3bdd5e0c53556e2c917920059781700081bdc3fd3966568487382cdb556e8fce96995dc2fb2d598edd55abb423e1ab50b74fdd8568356d62c742394e7bd5e1869a8f28a8557525664b8b28ab14c3d1108e4bae38d05447408f71bdb233aab9cd9a740e242794b9c75760064fdc90c2e0b902b407f311b34d9a172731fe7aa10c9bd189071bdf6f24be900fd8711a08407052db276f4ec535ffb2341e486de7e4ef4b0bbd10de4409c80221a653455930b82edf3a40421c3c665bba621821945a05a7c6c7ca3946f0b673a7e8fa2023d0e2b05b2fc21ffbdb68b0ccb9e0aacb2623f1a843896e68eb7157f8d115a3d7e0e02fd894c154ea56eb1b0d8f296325d3a71f581b868cbade1bbb50b73bf52fa0883cf71eb20aae786f9b27637e3d19ae75ae5206c0a6f789241741bfee9ce05259fe40f2628445d7d741b152930c35ab1c40aba265817242be986971e83665677", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 78652884, 20972664, 5675433, 3539412, 401844, 92298, 230707, 139751, 6296, 8501, 7087, 31923, 98406, 10504, 43266, 135\n ], \n \"k_image\": \"428be79097b510e49fe5b25804029ac8bfa5e2a640a8b0e3e0a8199b1d26f22f\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"f4c8ce3812f84c4dae9fab0e120faf7e9583c5bb024674a4ceb36550a58da368\", \n \"view_tag\": \"69\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"edcedc3b21577e2cb73380c7a829cc6707109e672cee030aa007618ff033ed9e\", \n \"view_tag\": \"8c\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"7d3b66c65d73e2c172c52ef1336987487da2507fc7c6ca0579ff7e8d34c47f4d\", \n \"view_tag\": \"17\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d423aefc2536b8839995f569dc5e61385f8a537067f5c00aac957cccfb5a4790\", \n \"view_tag\": \"b5\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"97fcebd0877bdd71d2a90aed5a58180a0deecaed068f9e43ce44df011aecae0e\", \n \"view_tag\": \"38\"\n }\n }\n }\n ], \n \"extra\": [ 1, 67, 10, 212, 151, 219, 135, 4, 110, 51, 24, 77, 150, 23, 218, 166, 74, 221, 130, 165, 61, 193, 105, 130, 160, 86, 182, 130, 178, 249, 162, 218, 152\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 66240000, \n \"ecdhInfo\": [ {\n \"amount\": \"6a94c40de9d6fb89\"\n }, {\n \"amount\": \"35d837180bad6fc5\"\n }, {\n \"amount\": \"bb224d8ed00140af\"\n }, {\n \"amount\": \"b3123c36a4ff194f\"\n }, {\n \"amount\": \"108db26877967b02\"\n }], \n \"outPk\": [ \"4bbd0cb51d34a7f5cb26262573b9a66cd70635204820f80d01f04297474b9ca9\", \"65cdc9302126eebe75aa86a0a837f9dcface5fa38ad81bdcaa40e1fe942f2fc2\", \"768f267feece915df41d9864291b57885c1e0f8e4c93a7a4610aef477cf53b80\", \"5dad5a1006071fde67ca8c47f545c2fdf3202120ef652e0173b4257b91811f7f\", \"39df6d0c9c1d2e8f00074c619e9e4322dd006c41c10b67c4ed21aae62ce87487\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"865c7cb3df6f168e9564b1c7f0f7ac8429f2ecc311a10557feec8395cd8e03be\", \n \"A1\": \"8f38908c2fee85f557cdb534d9775ae9583f7a73024d9a7efa1d75b74f4e62b0\", \n \"B\": \"a40954b6ca4c0c2091beb3021f21cce994f5a679c0f45126a83d9cb431ab6bab\", \n \"r1\": \"b1d3272ed9456ea6af5c35037b4a89a23640c6edf1a4576d592bbc5dd0c4f40a\", \n \"s1\": \"a3e918712d4812f5bc8a01e38e6dc1d4b5dc39ccb5e26eeaf5c88b85d968c800\", \n \"d1\": \"e10bfca42e320f6b03c3065856b58908555a266b7308321ba94e80aab0956b0c\", \n \"L\": [ \"e2f1c8c5da991672dea0c43de4c27d232f50c35e3fc836ac8eec7d6466d8af2c\", \"e9277a92aca9f4d4d4230d1dfbb042871dc92498339a6225d6ac016e0e9dde85\", \"2364c31878a19a2820bd196c1f837b398a56152ae01928f9f2fb80aae7301795\", \"da93006123b6cea54f34f1232a9f1ddafa340764a5b0b305af112c72c76a6ed5\", \"d5513739fbbc56bc027b4251b23a8c7727a2abf5b3e9a803aa3d144022d129fe\", \"de2abec4f787d574df639bd48d0de0d1e981d88de680a7d40033b0f93b51ed6e\", \"60b8199c2adbc7de0b8f76802d8e74d9e46d2145a61232920571bc1d23fce134\", \"c02c268b523f5d5d31876d2259b2292597c15937c3057e5c4350391327429428\", \"ac6069bdd5db4515113d046f8058506b6e04399016157964b7c7dd41b929e9fb\"\n ], \n \"R\": [ \"997f51fa6db7903465b3cab1093ad38a783b67542e75bde42bbf5c07e415c242\", \"41017f2365a3e05b1bb8eab7b4ef681d3208a98715fd84e60be86931800276ce\", \"667812e9602afa5baacb0caa1ef9fc37cbac120918564ca04d73ce130ca42c28\", \"577d504516e5f44b843a76b2c1558d142e64ce4a55c34bf197a0251022a94e97\", \"3f59a4352fe5e086e79fc90319e29834ab75bb24c4438c383f19ff77463d0f32\", \"2566c1b5c64f1dd5ab8472ae4e8b4ac20ab110f5281a848ceca73330fc45d336\", \"6b7bd1accb139fec1ed99f15d11ef3ec2e7207a5e7bdafa136a85ccb0a55c3e7\", \"7c86a70c2496fbcd0373c8aac6067731183cf211bbefbc7d71269d6ba62c98aa\", \"791613cbd267b4424763141075bf53d6c4472d3f7685f73f726b16caff0b9018\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"3a71c42ca6a27bf6d818abea8c67eb90ce4aca1f9efcb43215214336da99b407\", \"4faba4a99c9cfb429e70759024f3d5ce150e9e97e77c4f70501efc575c485e0a\", \"ea5ed0f246930d73be9d8662f41a408323beacd53135df2eeea93722a00bfc02\", \"6f5827dccdb05f8e9fedbfc7632b36324d891eb8220978da3ad6e4ad1f3e3507\", \"f4b7b8f4914326b28fda56141b2be6350c9c8499afbe66da9417d03598c40f05\", \"ec043be6b64b2aa2a0a5da6ab9825d6ebc03578ef5e7370ea63d051cb9f87e01\", \"933cfe48a5b22c49d6aca2cf869f6e98bb89c51c6fc9a5cea61a03920214aa0a\", \"014a19d0f3fe97670e3990021dae0fdbb5ed4979b1e2144ec3d1ba4b68c39205\", \"a86c3350cf2315a25c4ddb21d24ff2a3bdd5e0c53556e2c91792005978170008\", \"1bdc3fd3966568487382cdb556e8fce96995dc2fb2d598edd55abb423e1ab50b\", \"74fdd8568356d62c742394e7bd5e1869a8f28a8557525664b8b28ab14c3d1108\", \"e4bae38d05447408f71bdb233aab9cd9a740e242794b9c75760064fdc90c2e0b\", \"902b407f311b34d9a172731fe7aa10c9bd189071bdf6f24be900fd8711a08407\", \"052db276f4ec535ffb2341e486de7e4ef4b0bbd10de4409c80221a653455930b\", \"82edf3a40421c3c665bba621821945a05a7c6c7ca3946f0b673a7e8fa2023d0e\", \"2b05b2fc21ffbdb68b0ccb9e0aacb2623f1a843896e68eb7157f8d115a3d7e0e\"], \n \"c1\": \"02fd894c154ea56eb1b0d8f296325d3a71f581b868cbade1bbb50b73bf52fa08\", \n \"D\": \"83cf71eb20aae786f9b27637e3d19ae75ae5206c0a6f789241741bfee9ce0525\"\n }], \n \"pseudoOuts\": [ \"9fe40f2628445d7d741b152930c35ab1c40aba265817242be986971e83665677\"]\n }\n}", + "weight": 3312 + },{ + "blob_size": 1539, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 492480000, + "id_hash": "a60834967cc6d22e61acbc298d5d2c725cbf5c8c492b999f3420da126f41b6c7", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261654, + "max_used_block_height": 3195144, + "max_used_block_id_hash": "464cb0e47663a64ee8eaf483c46d6584e9a7945a0c792b19cdbde426ec3a5034", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "020001020010dab5ae1feefaf606fea6c609aca1ec018fa0e70186de3df48307c5b72ab9a001dea302de3c9c17c2b701a923a801d302d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f020003c9558a2daba528058e11b62fb334f0c1324096575db177d3676f2f3a7287f154640003cfc87315c4bbbfc8053d6f4921465da881c2675a531141d272c7b4e085829909c82c01af5114379f90bb68c84743bedf90bd63aa394ea61191a0c315c9816d8e0b9efd020901d1c905878ab04b850680cceaea016f7857e7ade8bd0ee83ef9696bc9b3823e5b43d81c8f49c1f4c6e6ccdb93f7d3d500bcc38801eba594e5377f8339c5e2baed73a25fdcb6162c8cac13ab20adf8fa0652db145499fff83ca114f5b5c39b01d0531831fce743ab9dcdd3571f8a93c433dfa17e9885741b50e34ed92e5d3b41b43bd7a99784d3e95385b94d578222b3bd7e8f0d981a318c424c98eaeffb9cab22da9b9ff6d666a72f2e56751796015b36634f455d2b1f99874b6b30707a20961c12819b752f60ec321436fe0d3bfd681b14048578b14eef8bbedad3f672fc010518a1c63af4ed08d8e7b875641eff37acf9fe46ca0a1b428ea685131eab8003a3cbe82bb87769ab6c5763eb468d04c985a650dde6db581ed3926f44f68bd9040796c94d7d0655ace603e046b12e0267718d575ac32b6fbfe0b911158198d2c5bc08c54b7f62ed326d8965c90e9bb29ad63fcb6d1d4ca5f849aba2eccb5a51ed1d28f2eceda481bd1f3a0229a9cce6b8816831ff5ac7d129f8f3088cdcfe356d73cd4b07a4453a016b3cd65bcb3ac7f85bed185044ffced69b4e111af67582c85551a1fee0e370eb1360611653b571f6dd35f1ab90b1af509dd630b88e6851322e65f1189e46bde8175c4add525e5975c8de17b718155ba857dcba3695543ef6e6a71d548c4940850707eeaf3769839ec636f3e0088d92bbdd5eb2e508529cfb4c078cfa9204c35863256579aefad74fbc7e411174375840bc3070c74939a0de834131dabafa47de92c03d38384d870f3b65f73e7e411ca4130de1f0a706399b64f63dc809524dcdcc76b3a1c0eb963384c39779125ec10c7eeccf9190200a76c4df88dedc7a6b7661ef39be599844c88f5320a1288db2f6395f73907851f966fe67c584ec77c89b15e597254b6550c460f4607c9aa7287577c6c1fc3c3b0b01f0f30818dce251455e18138c4509100cf7c1c79e9b0d332a57a0e3734b8bdaddfa0a3233560a441f2bff47b98de49b8c87e5401e7dfe844b7ad4e3b669394e44816e4cb057b6afce2a9fe3915084deae4d047e698b6753ec6da7e4b3a34babe73206a5990f57933939c242e27891786882631d5fd264e26d2853aba16336ae15070f00602edfbfd9abf9702c7cf9607b52ad0a04d08518054c7f4cada448853092090164292412d8c4196fb4ddb842669ea9de50421b6f219cb05a0befe63f618f043ececbc47f1e3547e4e7abe935c972ed8f60258e1bb7b7b97a74109bfb5b6a03308939028b18f56c3128641e4bf7c7c5884fc05ee7ebdb76b85ad30cb9a1d90680dfdfecbfee9ea2e98bc50e3021f165dcf3890713572b0847f4767a244dc908c8931d54a79dbc34f35888485b977aa7d809b3513aec10d4a522175edfcc9e044a15f68a6e1f5b2095f9c7791984e794b75d3f9940b602b8984773443e4c760bf28dca4e93a036aa4be7771073d23aca7732aae7a364c628437064dc5e8dbf0dfb1fe2333a90b6950dc764e90840ebb74fb98803afe6ccd792ad637183747402c3218537f5b9416b9559b6cadd148c5a8bb52b4f92be7cf130ef729227acac092c13e6c8a9b5046688b81ded00f5ccfbd2f70bfb5aeec37de7d7ea4097feb5005590fab38475f3e37dcc9df3fa3a176d8f45e732acfd12e3f680bc4b511aa90e8c853f0d294f351f8a16a486dbd7bdbe582d48ff956a7c17d719220293d9b3088b24cbaa956ee96d662fe1f30b3d477c96dd3fab9902fcecf8692725440a380f5ffe1c8872ada9fcba8ad026cbb5e9d8324943cb5ebde77eefdcd5bb175c750a577032337113d6d1f45d488bfedbc3a25427633bb1873eb45a9a4ea726c33c0b297af95c01880e1f3bcb0ac86f4d7105e5c6cee032282fc50aa2d34c06b4c4bb", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 65772250, 14531950, 20026238, 3870892, 3788815, 1011462, 115188, 695237, 20537, 37342, 7774, 2972, 23490, 4521, 168, 339\n ], \n \"k_image\": \"d2ed8513f48724933df6229a9fb6ededdcf5d0963280ee44fa9216ceebe7941f\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"c9558a2daba528058e11b62fb334f0c1324096575db177d3676f2f3a7287f154\", \n \"view_tag\": \"64\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"cfc87315c4bbbfc8053d6f4921465da881c2675a531141d272c7b4e085829909\", \n \"view_tag\": \"c8\"\n }\n }\n }\n ], \n \"extra\": [ 1, 175, 81, 20, 55, 159, 144, 187, 104, 200, 71, 67, 190, 223, 144, 189, 99, 170, 57, 78, 166, 17, 145, 160, 195, 21, 201, 129, 109, 142, 11, 158, 253, 2, 9, 1, 209, 201, 5, 135, 138, 176, 75, 133\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 492480000, \n \"ecdhInfo\": [ {\n \"amount\": \"6f7857e7ade8bd0e\"\n }, {\n \"amount\": \"e83ef9696bc9b382\"\n }], \n \"outPk\": [ \"3e5b43d81c8f49c1f4c6e6ccdb93f7d3d500bcc38801eba594e5377f8339c5e2\", \"baed73a25fdcb6162c8cac13ab20adf8fa0652db145499fff83ca114f5b5c39b\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"d0531831fce743ab9dcdd3571f8a93c433dfa17e9885741b50e34ed92e5d3b41\", \n \"A1\": \"b43bd7a99784d3e95385b94d578222b3bd7e8f0d981a318c424c98eaeffb9cab\", \n \"B\": \"22da9b9ff6d666a72f2e56751796015b36634f455d2b1f99874b6b30707a2096\", \n \"r1\": \"1c12819b752f60ec321436fe0d3bfd681b14048578b14eef8bbedad3f672fc01\", \n \"s1\": \"0518a1c63af4ed08d8e7b875641eff37acf9fe46ca0a1b428ea685131eab8003\", \n \"d1\": \"a3cbe82bb87769ab6c5763eb468d04c985a650dde6db581ed3926f44f68bd904\", \n \"L\": [ \"96c94d7d0655ace603e046b12e0267718d575ac32b6fbfe0b911158198d2c5bc\", \"08c54b7f62ed326d8965c90e9bb29ad63fcb6d1d4ca5f849aba2eccb5a51ed1d\", \"28f2eceda481bd1f3a0229a9cce6b8816831ff5ac7d129f8f3088cdcfe356d73\", \"cd4b07a4453a016b3cd65bcb3ac7f85bed185044ffced69b4e111af67582c855\", \"51a1fee0e370eb1360611653b571f6dd35f1ab90b1af509dd630b88e6851322e\", \"65f1189e46bde8175c4add525e5975c8de17b718155ba857dcba3695543ef6e6\", \"a71d548c4940850707eeaf3769839ec636f3e0088d92bbdd5eb2e508529cfb4c\"\n ], \n \"R\": [ \"8cfa9204c35863256579aefad74fbc7e411174375840bc3070c74939a0de8341\", \"31dabafa47de92c03d38384d870f3b65f73e7e411ca4130de1f0a706399b64f6\", \"3dc809524dcdcc76b3a1c0eb963384c39779125ec10c7eeccf9190200a76c4df\", \"88dedc7a6b7661ef39be599844c88f5320a1288db2f6395f73907851f966fe67\", \"c584ec77c89b15e597254b6550c460f4607c9aa7287577c6c1fc3c3b0b01f0f3\", \"0818dce251455e18138c4509100cf7c1c79e9b0d332a57a0e3734b8bdaddfa0a\", \"3233560a441f2bff47b98de49b8c87e5401e7dfe844b7ad4e3b669394e44816e\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"4cb057b6afce2a9fe3915084deae4d047e698b6753ec6da7e4b3a34babe73206\", \"a5990f57933939c242e27891786882631d5fd264e26d2853aba16336ae15070f\", \"00602edfbfd9abf9702c7cf9607b52ad0a04d08518054c7f4cada44885309209\", \"0164292412d8c4196fb4ddb842669ea9de50421b6f219cb05a0befe63f618f04\", \"3ececbc47f1e3547e4e7abe935c972ed8f60258e1bb7b7b97a74109bfb5b6a03\", \"308939028b18f56c3128641e4bf7c7c5884fc05ee7ebdb76b85ad30cb9a1d906\", \"80dfdfecbfee9ea2e98bc50e3021f165dcf3890713572b0847f4767a244dc908\", \"c8931d54a79dbc34f35888485b977aa7d809b3513aec10d4a522175edfcc9e04\", \"4a15f68a6e1f5b2095f9c7791984e794b75d3f9940b602b8984773443e4c760b\", \"f28dca4e93a036aa4be7771073d23aca7732aae7a364c628437064dc5e8dbf0d\", \"fb1fe2333a90b6950dc764e90840ebb74fb98803afe6ccd792ad637183747402\", \"c3218537f5b9416b9559b6cadd148c5a8bb52b4f92be7cf130ef729227acac09\", \"2c13e6c8a9b5046688b81ded00f5ccfbd2f70bfb5aeec37de7d7ea4097feb500\", \"5590fab38475f3e37dcc9df3fa3a176d8f45e732acfd12e3f680bc4b511aa90e\", \"8c853f0d294f351f8a16a486dbd7bdbe582d48ff956a7c17d719220293d9b308\", \"8b24cbaa956ee96d662fe1f30b3d477c96dd3fab9902fcecf8692725440a380f\"], \n \"c1\": \"5ffe1c8872ada9fcba8ad026cbb5e9d8324943cb5ebde77eefdcd5bb175c750a\", \n \"D\": \"577032337113d6d1f45d488bfedbc3a25427633bb1873eb45a9a4ea726c33c0b\"\n }], \n \"pseudoOuts\": [ \"297af95c01880e1f3bcb0ac86f4d7105e5c6cee032282fc50aa2d34c06b4c4bb\"]\n }\n}", + "weight": 1539 + },{ + "blob_size": 1536, + "do_not_relay": false, + "double_spend_seen": false, + "fee": 491520000, + "id_hash": "aef60754dc1b2cd788faf23dd3c62afd3a0ac14e088cd6c8d22f1597860e47cd", + "kept_by_block": false, + "last_failed_height": 0, + "last_failed_id_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "last_relayed_time": 1721261653, + "max_used_block_height": 3195160, + "max_used_block_id_hash": "2f7b8ca3dbd64cb33f428ece414b2b1cef405cfcd85fab1a70383490cc7ed603", + "receive_time": 1721261653, + "relayed": true, + "tx_blob": "02000102001081d9e02d98cd8606d18f0cbc9919ce9704e78b04bdeb03e28806af8601cec110c54dafc502a6118b1c9743c7052c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee54980200031085022c023e67ccef3004d93b200be5df0cca89c5a17c51d76c3ee3c107d39190000332dd926cf0eac221cabeb5beee0cc9ef68a07a79748a69c3142377b5a8dbd6c7be2c019eb1fa0d8e9c4853c7db214bf6547ddd8b23bec77445b208e296fb26d0861a5b0209016ae500293b5f1167068080b0ea0113648ac5774104fe28d4ea6e7e28511a1c5c4189735a8c2e98bb12572283f3797ff1273d5dd44312e10cbb4cfdf34c3f7664dfc643db30f011129a53843d647e7cab9e03c2df68ef71b2040d59005f4501fb8885aaa4fc7f5fcae273f7a4612c629423864cb4d12c06044dad6629f40edfb2d6cb3c9dd78407302087c767c8bf17d507471a9cb9af54dc6528de99c8dd62ca69f07a623f4ca679eccc0fa03568e346cc061ff1624c4b73ee72a7a414c68164bc2abdf7f7798e6a0f13b2c3852371c2db35ebb00c7ba018a42b0c2c31fc06a748b257eafb46efc220a57a7767664f7744420bc3ee1c7c227aa711680dd301accf87381433838debd8e517ba86d06cb7475f3b8dfa301ee879112ab2e7a00807200f21ba8eeb86935deb899b3babb7a3ebb21b2f1dfd1bb08015ae4b8ff4f16356c7e807b6bf00f6178c752d92f9630508eeccf307f8efdc7dfae7592555a0824706000a2f329413ffe6ecfe68345053f441db1815286b951cbf83b4e7c327e27019fe81f3c534b297b9388438ffb1d5acd17514678a87719b843d64df591d1e478fdac7f3940088cfe0ea54a896fbcad933fb85c9e3f060db515cc8495f4a8939102799616733953f6bb959916ebdb8cfb8c958f62e787aa7b4a3bd45c311cd83be8617ee592413d8c76bf05922147cb08447e7b899344e9c7069cf710fe22a07744704b4b118e85e16e5dc3abc02e9e6fd45e95c73ec2412355907bc4a5d3d17849043695696eb46dbeb1389bc544c5ec11d5e35a9d9be1e5aa2884377c2f4410773a62507f87e7fe992e335377585bf63a7554e352066574e4e35fdf5d1c90a7ffa6b791e317f2c7c76a85532a7ae18a74c293e3ce9c853080fe23abc42d97fb8a8320d3586343706a56e8f293322aba82e9c159a41a3ceff344177dddc3df947cf9159c194d6b9c392dd76a5ddca3f1110ca3043f7b2e82ef1b55fbc87e8b3063bd71e497c90ae647f0c21719149341298ab1402e9bbee4b674bc9509070df1e5de5f3c400ef5230773a8c5b6fb1dc556bb25a8d0110b4c6f411785927930bcbc5b5a9057c4c6b55397a8000f4d8b6cc6159a4e642aa449512e56b405beb0ee74de267b38c7135eaba1f4171e7fbe7fa79807eba65b20a78e896c94e47ae0519d587d550fbf22015c99d604c7f55019901ac5fcbd674d0d2fd6d752087ca056f89bd649fc33ddb75888ecc0327e66d0642683825f262c5cec006599bdf1f07b0f9af7553379e798af1e9644d988e703c96ae415e22d053d4c42fbb19c44d062eec4bf6ce7888a7438c2f1d5b94a415ee8ff3bd95045429378b8c2b9fb7c600656b5a6f94b1149793517bce19d0ddf2f5b30fe3a16bbebed91ee0a03c47a003778322b5d214b960acc4b9b2cdd1e55c4aed04199aa9a0b5132b2d3553c3fe087b9ffbbd7409e47528c075ee1edc51e6cedd5fe05998fddab5a3f25edb0ff2083fcf87e8e261df81a9cf178f5facd906821a543560d3f5d0bb07cf0402d69302463bc90bf04cce521b1b115ea4c57c6512695ade926f3be142001ee73e7f130bc9e506619c93bdae690697a0a642fb15921a1426de845c949799218995a96207ebd6b2904f2a881b82a065eac32ade92b8cf9c73939b858cad212976836a8108f15230cfc5131e6a379c236531ea611b1e8d5277add4f4d211cb6f9f6364cf0e8c56ab32609f43aae59b20ada5e35e217d64e44d122665073837ed50ef23c90d8132b7a8e3a732f424f3166075ca4082ba690961a894a7ed2e6f42d2e772010133381b48d999bbd4cb548c381550626e9e5b8e91c7ea1e78d01e390b5a9de6ad42feb5d5d5f1408202abac88b8c70056af4bde7b2b1a412d8feb4b0b85b88c61", + "tx_json": "{\n \"version\": 2, \n \"unlock_time\": 0, \n \"vin\": [ {\n \"key\": {\n \"amount\": 0, \n \"key_offsets\": [ 95956097, 12691096, 198609, 412860, 68558, 67047, 62909, 99426, 17199, 270542, 9925, 41647, 2214, 3595, 8599, 711\n ], \n \"k_image\": \"2c479dbff819502441604a914af485db2f795b7f5bc0eab877d60a1419ee5498\"\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"1085022c023e67ccef3004d93b200be5df0cca89c5a17c51d76c3ee3c107d391\", \n \"view_tag\": \"90\"\n }\n }\n }, {\n \"amount\": 0, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"32dd926cf0eac221cabeb5beee0cc9ef68a07a79748a69c3142377b5a8dbd6c7\", \n \"view_tag\": \"be\"\n }\n }\n }\n ], \n \"extra\": [ 1, 158, 177, 250, 13, 142, 156, 72, 83, 199, 219, 33, 75, 246, 84, 125, 221, 139, 35, 190, 199, 116, 69, 178, 8, 226, 150, 251, 38, 208, 134, 26, 91, 2, 9, 1, 106, 229, 0, 41, 59, 95, 17, 103\n ], \n \"rct_signatures\": {\n \"type\": 6, \n \"txnFee\": 491520000, \n \"ecdhInfo\": [ {\n \"amount\": \"13648ac5774104fe\"\n }, {\n \"amount\": \"28d4ea6e7e28511a\"\n }], \n \"outPk\": [ \"1c5c4189735a8c2e98bb12572283f3797ff1273d5dd44312e10cbb4cfdf34c3f\", \"7664dfc643db30f011129a53843d647e7cab9e03c2df68ef71b2040d59005f45\"]\n }, \n \"rctsig_prunable\": {\n \"nbp\": 1, \n \"bpp\": [ {\n \"A\": \"fb8885aaa4fc7f5fcae273f7a4612c629423864cb4d12c06044dad6629f40edf\", \n \"A1\": \"b2d6cb3c9dd78407302087c767c8bf17d507471a9cb9af54dc6528de99c8dd62\", \n \"B\": \"ca69f07a623f4ca679eccc0fa03568e346cc061ff1624c4b73ee72a7a414c681\", \n \"r1\": \"64bc2abdf7f7798e6a0f13b2c3852371c2db35ebb00c7ba018a42b0c2c31fc06\", \n \"s1\": \"a748b257eafb46efc220a57a7767664f7744420bc3ee1c7c227aa711680dd301\", \n \"d1\": \"accf87381433838debd8e517ba86d06cb7475f3b8dfa301ee879112ab2e7a008\", \n \"L\": [ \"200f21ba8eeb86935deb899b3babb7a3ebb21b2f1dfd1bb08015ae4b8ff4f163\", \"56c7e807b6bf00f6178c752d92f9630508eeccf307f8efdc7dfae7592555a082\", \"4706000a2f329413ffe6ecfe68345053f441db1815286b951cbf83b4e7c327e2\", \"7019fe81f3c534b297b9388438ffb1d5acd17514678a87719b843d64df591d1e\", \"478fdac7f3940088cfe0ea54a896fbcad933fb85c9e3f060db515cc8495f4a89\", \"39102799616733953f6bb959916ebdb8cfb8c958f62e787aa7b4a3bd45c311cd\", \"83be8617ee592413d8c76bf05922147cb08447e7b899344e9c7069cf710fe22a\"\n ], \n \"R\": [ \"744704b4b118e85e16e5dc3abc02e9e6fd45e95c73ec2412355907bc4a5d3d17\", \"849043695696eb46dbeb1389bc544c5ec11d5e35a9d9be1e5aa2884377c2f441\", \"0773a62507f87e7fe992e335377585bf63a7554e352066574e4e35fdf5d1c90a\", \"7ffa6b791e317f2c7c76a85532a7ae18a74c293e3ce9c853080fe23abc42d97f\", \"b8a8320d3586343706a56e8f293322aba82e9c159a41a3ceff344177dddc3df9\", \"47cf9159c194d6b9c392dd76a5ddca3f1110ca3043f7b2e82ef1b55fbc87e8b3\", \"063bd71e497c90ae647f0c21719149341298ab1402e9bbee4b674bc9509070df\"\n ]\n }\n ], \n \"CLSAGs\": [ {\n \"s\": [ \"1e5de5f3c400ef5230773a8c5b6fb1dc556bb25a8d0110b4c6f411785927930b\", \"cbc5b5a9057c4c6b55397a8000f4d8b6cc6159a4e642aa449512e56b405beb0e\", \"e74de267b38c7135eaba1f4171e7fbe7fa79807eba65b20a78e896c94e47ae05\", \"19d587d550fbf22015c99d604c7f55019901ac5fcbd674d0d2fd6d752087ca05\", \"6f89bd649fc33ddb75888ecc0327e66d0642683825f262c5cec006599bdf1f07\", \"b0f9af7553379e798af1e9644d988e703c96ae415e22d053d4c42fbb19c44d06\", \"2eec4bf6ce7888a7438c2f1d5b94a415ee8ff3bd95045429378b8c2b9fb7c600\", \"656b5a6f94b1149793517bce19d0ddf2f5b30fe3a16bbebed91ee0a03c47a003\", \"778322b5d214b960acc4b9b2cdd1e55c4aed04199aa9a0b5132b2d3553c3fe08\", \"7b9ffbbd7409e47528c075ee1edc51e6cedd5fe05998fddab5a3f25edb0ff208\", \"3fcf87e8e261df81a9cf178f5facd906821a543560d3f5d0bb07cf0402d69302\", \"463bc90bf04cce521b1b115ea4c57c6512695ade926f3be142001ee73e7f130b\", \"c9e506619c93bdae690697a0a642fb15921a1426de845c949799218995a96207\", \"ebd6b2904f2a881b82a065eac32ade92b8cf9c73939b858cad212976836a8108\", \"f15230cfc5131e6a379c236531ea611b1e8d5277add4f4d211cb6f9f6364cf0e\", \"8c56ab32609f43aae59b20ada5e35e217d64e44d122665073837ed50ef23c90d\"], \n \"c1\": \"8132b7a8e3a732f424f3166075ca4082ba690961a894a7ed2e6f42d2e7720101\", \n \"D\": \"33381b48d999bbd4cb548c381550626e9e5b8e91c7ea1e78d01e390b5a9de6ad\"\n }], \n \"pseudoOuts\": [ \"42feb5d5d5f1408202abac88b8c70056af4bde7b2b1a412d8feb4b0b85b88c61\"]\n }\n}", + "weight": 1536 + }], + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_transaction_pool_stats, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1712..=1732, -// GetTransactionPoolStats, -// Request {}, -// AccessResponseBase { -// pool_stats: TxpoolStats, -// } -// } +define_request_and_response! { + get_transaction_pool_stats (other), + GET_TRANSACTION_POOL_STATS: &str, + Request = +r#"{}"#; + Response = +r#"{ + "credits": 0, + "pool_stats": { + "bytes_max": 11843, + "bytes_med": 2219, + "bytes_min": 1528, + "bytes_total": 144192, + "fee_total": 7018100000, + "histo": [{ + "bytes": 11219, + "txs": 4 + },{ + "bytes": 9737, + "txs": 5 + },{ + "bytes": 8757, + "txs": 4 + },{ + "bytes": 14763, + "txs": 4 + },{ + "bytes": 15007, + "txs": 6 + },{ + "bytes": 15924, + "txs": 6 + },{ + "bytes": 17869, + "txs": 8 + },{ + "bytes": 10894, + "txs": 5 + },{ + "bytes": 38485, + "txs": 10 + },{ + "bytes": 1537, + "txs": 1 + }], + "histo_98pc": 186, + "num_10m": 0, + "num_double_spends": 0, + "num_failing": 0, + "num_not_relayed": 0, + "oldest": 1721261651, + "txs_total": 53 + }, + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} -// define_request_and_response! { -// stop_daemon, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1814..=1831, -// StopDaemon, -// Request {}, -// ResponseBase { -// status: Status, -// } -// } +define_request_and_response! { + stop_daemon (other), + STOP_DAEMON: &str, + Request = +r#"{}"#; + Response = +r#"{ + "status": "OK" +}"#; +} -// define_request_and_response! { -// get_limit, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1852..=1874, -// GetLimit, -// Request {}, -// ResponseBase { -// limit_down: u64, -// limit_up: u64, -// } -// } +define_request_and_response! { + get_limit (other), + GET_LIMIT: &str, + Request = +r#"{}"#; + Response = +r#"{ + "limit_down": 1280000, + "limit_up": 1280000, + "status": "OK", + "untrusted": true +}"#; +} -// define_request_and_response! { -// set_limit, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1876..=1903, -// SetLimit, -// Request { -// limit_down: i64, -// limit_up: i64, -// }, -// ResponseBase { -// limit_down: i64, -// limit_up: i64, -// } -// } +define_request_and_response! { + set_limit (other), + SET_LIMIT: &str, + Request = +r#"{ + "limit_down": 1024 +}"#; + Response = +r#"{ + "limit_down": 1024, + "limit_up": 128, + "status": "OK" + "untrusted": false +}"#; +} -// define_request_and_response! { -// out_peers, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1876..=1903, -// OutPeers, -// Request { -// set: bool = default_true(), "default_true", -// out_peers: u32, -// }, -// ResponseBase { -// out_peers: u32, -// } -// } +define_request_and_response! { + out_peers (other), + OUT_PEERS: &str, + Request = +r#"{ + "out_peers": 3232235535 +}"#; + Response = +r#"{ + "out_peers": 3232235535, + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_net_stats, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 793..=822, -// GetNetStats, -// Request {}, -// ResponseBase { -// start_time: u64, -// total_packets_in: u64, -// total_bytes_in: u64, -// total_packets_out: u64, -// total_bytes_out: u64, -// } -// } +define_request_and_response! { + get_net_stats (other), + GET_NET_STATS: &str, + Request = +r#"{ + "in_peers": 3232235535 +}"#; + Response = +r#"{ + "in_peers": 3232235535, + "status": "OK", + "untrusted": false +}"#; +} -// define_request_and_response! { -// get_outs, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 567..=609, -// GetOuts, -// Request { -// outputs: Vec, -// get_txid: bool, -// }, -// ResponseBase { -// outs: Vec, -// } -// } +define_request_and_response! { + get_outs (other), + GET_OUTS: &str, + Request = +r#"{ + "outputs": [{ + "amount": 1, + "index": 0 + },{ + "amount": 1, + "index": 1 + }], + "get_txid": true +}"#; + Response = +r#"{ + "credits": 0, + "outs": [{ + "height": 51941, + "key": "08980d939ec297dd597119f498ad69fed9ca55e3a68f29f2782aae887ef0cf8e", + "mask": "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522", + "txid": "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601", + "unlocked": true + },{ + "height": 51945, + "key": "454fe46c405be77625fa7e3389a04d3be392346983f27603561ac3a3a74f4a75", + "mask": "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522", + "txid": "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0", + "unlocked": true + }], + "status": "OK", + "top_hash": "", + "untrusted": false +}"#; +} -// define_request_and_response! { -// update, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2324..=2359, -// Update, -// Request { -// command: String, -// path: String = default_string(), "default_string", -// }, -// ResponseBase { -// auto_uri: String, -// hash: String, -// path: String, -// update: bool, -// user_uri: String, -// version: String, -// } -// } +define_request_and_response! { + update (other), + UPDATE: &str, + Request = +r#"{ + "command": "check" +}"#; + Response = +r#"{ + "auto_uri": "", + "hash": "", + "path": "", + "status": "OK", + "untrusted": false, + "update": false, + "user_uri": "", + "version": "" +}"#; +} -// define_request_and_response! { -// pop_blocks, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2722..=2745, -// PopBlocks, -// Request { -// nblocks: u64, -// }, -// ResponseBase { -// height: u64, -// } -// } +define_request_and_response! { + pop_blocks (other), + POP_BLOCKS: &str, + Request = +r#"{ + "nblocks": 6 +}"#; + Response = +r#"{ + "height": 76482, + "status": "OK", + "untrusted": false +}"#; +} +// TODO: what are the inputs? // define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2798..=2823, -// GetTxIdsLoose, -// Request { -// txid_template: String, -// num_matching_bits: u32, -// }, -// ResponseBase { -// txids: Vec, -// } +// UNDOCUMENTED_ENDPOINT (other), +// GET_TX_IDS_LOOSE: &str, +// Request = +// r#""#; +// Response = +// r#""#; // } -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1615..=1635, -// GetTransactionPoolHashes, -// Request {}, -// ResponseBase { -// tx_hashes: Vec, -// } -// } +define_request_and_response! { + UNDOCUMENTED_ENDPOINT (other), + GET_TRANSACTION_POOL_HASHES: &str, + Request = +r#"{}"#; + Response = +r#"{ + "credits": 0, + "status": "OK", + "top_hash": "", + "tx_hashes": ["aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03","794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11","1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17","7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b","2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329","eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f","59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236","0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655","60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062","661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d","b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e","974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f","d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb","3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb","8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da","11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df","b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1","ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee"], + "untrusted": false +}"#; +} -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1419..=1448, -// GetPublicNodes, -// Request { -// gray: bool = default_false(), "default_false", -// white: bool = default_true(), "default_true", -// include_blocked: bool = default_false(), "default_false", -// }, -// ResponseBase { -// gray: Vec, -// white: Vec, -// } -// } +define_request_and_response! { + UNDOCUMENTED_ENDPOINT (other), + GET_PUBLIC_NODES: &str, + Request = +r#"{}"#; + Response = +r#"{ + "status": "OK", + "untrusted": false, + "white": [{ + "host": "70.52.75.3", + "last_seen": 1721246387, + "rpc_credits_per_hash": 0, + "rpc_port": 18081 + },{ + "host": "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083", + "last_seen": 1720186288, + "rpc_credits_per_hash": 0, + "rpc_port": 18089 + }] +}"#; +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] From 95ed1f46d0b1a5f9eb5c383bc2f1bde1d623f519 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 20:33:43 -0400 Subject: [PATCH 42/93] bin: add skeleton --- test-utils/src/rpc/data/bin.rs | 146 ++++++++---------------------- test-utils/src/rpc/data/macros.rs | 8 +- 2 files changed, 43 insertions(+), 111 deletions(-) diff --git a/test-utils/src/rpc/data/bin.rs b/test-utils/src/rpc/data/bin.rs index 3b1b36422..cf98a4aa8 100644 --- a/test-utils/src/rpc/data/bin.rs +++ b/test-utils/src/rpc/data/bin.rs @@ -1,120 +1,52 @@ //! Binary data from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). +//! +//! TODO: Not implemented yet. //---------------------------------------------------------------------------------------------------- Import +use crate::rpc::data::macros::define_request_and_response; //---------------------------------------------------------------------------------------------------- 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]>, -// } -// } - -// define_request_and_response! { -// get_blocks_by_heightbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 264..=286, -// GetBlocksByHeight, -// Request { -// heights: Vec, -// }, -// AccessResponseBase { -// blocks: Vec, -// } -// } +define_request_and_response! { + get_blocksbin, + GET_BLOCKS: &[u8], + Request = &[]; + Response = &[]; +} -// define_request_and_response! { -// get_hashesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 309..=338, -// GetHashes, -// Request { -// block_ids: ByteArrayVec<32>, -// start_height: u64, -// }, -// AccessResponseBase { -// m_blocks_ids: ByteArrayVec<32>, -// start_height: u64, -// current_height: u64, -// } -// } +define_request_and_response! { + get_blocks_by_heightbin, + GET_BLOCKS_BY_HEIGHT: &[u8], + Request = &[]; + Response = &[]; +} -// #[cfg(not(feature = "epee"))] -// define_request_and_response! { -// get_o_indexesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 487..=510, -// GetOutputIndexes, -// #[derive(Copy)] -// Request { -// txid: [u8; 32], -// }, -// AccessResponseBase { -// o_indexes: Vec, -// } -// } +define_request_and_response! { + get_hashesbin, + GET_HASHES: &[u8], + Request = &[]; + Response = &[]; +} -// #[cfg(feature = "epee")] -// define_request_and_response! { -// get_o_indexesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 487..=510, -// GetOutputIndexes, -// #[derive(Copy)] -// Request { -// txid: [u8; 32], -// }, -// AccessResponseBase { -// o_indexes: Vec as ContainerAsBlob, -// } -// } +define_request_and_response! { + get_o_indexesbin, + GET_O_INDEXES: &[u8], + Request = &[]; + Response = &[]; +} -// define_request_and_response! { -// get_outsbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 512..=565, -// GetOuts, -// Request { -// outputs: Vec, -// get_txid: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// outs: Vec, -// } -// } +define_request_and_response! { + get_outsbin, + GET_OUTS: &[u8], + Request = &[]; + Response = &[]; +} -// define_request_and_response! { -// get_transaction_pool_hashesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1593..=1613, -// GetTransactionPoolHashes, -// Request {}, -// AccessResponseBase { -// tx_hashes: ByteArrayVec<32>, -// } -// } +define_request_and_response! { + get_transaction_pool_hashesbin, + GET_TRANSACTION_POOL_HASHES: &[u8], + Request = &[]; + Response = &[]; +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/test-utils/src/rpc/data/macros.rs b/test-utils/src/rpc/data/macros.rs index 25f793bc8..5e7c5e0f5 100644 --- a/test-utils/src/rpc/data/macros.rs +++ b/test-utils/src/rpc/data/macros.rs @@ -25,11 +25,11 @@ macro_rules! define_request_and_response { // The request type (and any doc comments, derives, etc). $( #[$request_attr:meta] )* - Request = $request:literal; + Request = $request:expr; // The response type (and any doc comments, derives, etc). $( #[$response_attr:meta] )* - Response = $response:literal; + Response = $response:expr; ) => { paste::paste! { #[doc = $crate::rpc::data::macros::define_request_and_response_doc!( "response" => [<$name:upper _RESPONSE>], @@ -108,7 +108,7 @@ macro_rules! json_test { ) => { concat!( "```rust\n", - "use cuprate_test_utils::rpc::data::{json::*, bin::*, other::*};\n", + "use cuprate_test_utils::rpc::data::json::*;\n", "use serde_json::{to_value, Value};\n", "\n", "let value = serde_json::from_str::(&", @@ -143,7 +143,7 @@ macro_rules! json_test { ) => { concat!( "```rust\n", - "use cuprate_test_utils::rpc::data::{json::*, bin::*, other::*};\n", + "use cuprate_test_utils::rpc::data::other::*;\n", "use serde_json::{to_value, Value};\n", "\n", "let value = serde_json::from_str::(&", From b42584e56c09dad9669d54e67c13d8a2c7550ebd Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 20:59:59 -0400 Subject: [PATCH 43/93] docs --- test-utils/src/rpc/data/json.rs | 4 ++-- test-utils/src/rpc/data/macros.rs | 18 ++++++++++++------ test-utils/src/rpc/data/mod.rs | 11 +++++------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index 101d3998c..bf5babe19 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -20,7 +20,7 @@ define_request_and_response! { // The base const name: the type of the request/response. GET_BLOCK_TEMPLATE: &str, - // The request literal. + // The request data. Request = r#"{ "jsonrpc": "2.0", @@ -32,7 +32,7 @@ r#"{ } }"#; - // The response literal. + // The response data. Response = r#"{ "id": "0", diff --git a/test-utils/src/rpc/data/macros.rs b/test-utils/src/rpc/data/macros.rs index 5e7c5e0f5..632917a20 100644 --- a/test-utils/src/rpc/data/macros.rs +++ b/test-utils/src/rpc/data/macros.rs @@ -17,7 +17,7 @@ macro_rules! define_request_and_response { // add a `serde_json` test for the request/response data. $monero_daemon_rpc_doc_link:ident $(($test:ident))?, - // The base `struct` name. + // The base name. // Attributes added here will apply to _both_ // request and response types. $( #[$attr:meta] )* @@ -41,7 +41,7 @@ macro_rules! define_request_and_response { $( #[$request_attr] )* /// $( - #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _REQUEST>], $test)] + #[doc = $crate::rpc::data::macros::define_request_and_response_doc_test!([<$name:upper _REQUEST>], $test)] )? pub const [<$name:upper _REQUEST>]: $type = $request; @@ -55,7 +55,7 @@ macro_rules! define_request_and_response { $( #[$response_attr] )* /// $( - #[doc = $crate::rpc::data::macros::json_test!([<$name:upper _RESPONSE>], $test)] + #[doc = $crate::rpc::data::macros::define_request_and_response_doc_test!([<$name:upper _RESPONSE>], $test)] )? pub const [<$name:upper _RESPONSE>]: $type = $response; }}; @@ -101,9 +101,11 @@ pub(super) use define_request_and_response_doc; /// by the [`define_request_and_response`] macro. /// /// See it for more info on inputs. -macro_rules! json_test { +macro_rules! define_request_and_response_doc_test { + // `/json_rpc` doc test. ( - $name:ident, // TODO + // The ident of the `const` request/response. + $name:ident, json_rpc ) => { concat!( @@ -137,6 +139,8 @@ macro_rules! json_test { "```\n", ) }; + + // Other JSON endpoint doc test. ( $name:ident, other @@ -152,6 +156,8 @@ macro_rules! json_test { "```\n", ) }; + + // No doc test. ( $name:ident, $test:ident, @@ -159,4 +165,4 @@ macro_rules! json_test { "" }; } -pub(super) use json_test; +pub(super) use define_request_and_response_doc_test; diff --git a/test-utils/src/rpc/data/mod.rs b/test-utils/src/rpc/data/mod.rs index 662508185..09f0d602b 100644 --- a/test-utils/src/rpc/data/mod.rs +++ b/test-utils/src/rpc/data/mod.rs @@ -1,12 +1,11 @@ //! Monero RPC data. //! -//! This module contains real `monerod` RPC -//! requests/responses as `const` strings. +//! This module contains real `monerod` RPC requests/responses +//! as `const` [`str`]s and byte arrays (binary). //! -//! These strings include the JSON-RPC 2.0 portions of the JSON. -//! -//! Tests exist within Cuprate's `rpc/` crates that -//! ensure these strings are valid. +//! The strings include the JSON-RPC 2.0 portions of the JSON. +//! - Tests exist within this crate that ensure the JSON is valid +//! - Tests exist within Cuprate's `rpc/` crates that ensure these strings (de)serialize as valid types //! //! # Determinism //! Note that although both request/response data is defined, From e18081c17fb47d84a39bdbe2eaaa42e937f324db Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 17 Jul 2024 21:26:24 -0400 Subject: [PATCH 44/93] move type to correct file --- test-utils/src/rpc/data/json.rs | 26 +++++++++++++++++++++++++- test-utils/src/rpc/data/other.rs | 10 ---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index bf5babe19..2463e4563 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -1257,8 +1257,32 @@ r#"{ "status": "OK", "untrusted": false } +}"#; } -"#; + +define_request_and_response! { + UNDOCUMENTED_METHOD (json_rpc), + GET_TX_IDS_LOOSE: &str, + Request = +r#"{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_txids_loose", + "params": { + "txid_template": "0000000000000000aea473c43708aa50b2c9eaf0e441aa209afc9b43458fb09e", + "num_matching_bits": 192 + } +}"#; + Response = +r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "txids": "", + "status": "OK", + "untrusted": false + } +}"#; } //---------------------------------------------------------------------------------------------------- Tests diff --git a/test-utils/src/rpc/data/other.rs b/test-utils/src/rpc/data/other.rs index 081e2a4a4..2559bbe34 100644 --- a/test-utils/src/rpc/data/other.rs +++ b/test-utils/src/rpc/data/other.rs @@ -794,16 +794,6 @@ r#"{ }"#; } -// TODO: what are the inputs? -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT (other), -// GET_TX_IDS_LOOSE: &str, -// Request = -// r#""#; -// Response = -// r#""#; -// } - define_request_and_response! { UNDOCUMENTED_ENDPOINT (other), GET_TRANSACTION_POOL_HASHES: &str, From 11a24780a93e9b93bfebe946526704e9d103add2 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 18 Jul 2024 16:06:12 -0400 Subject: [PATCH 45/93] remove duplicated fields for custom epee --- rpc/types/src/bin.rs | 19 ++-------- rpc/types/src/misc/distribution.rs | 59 +++++++++++++----------------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 625c60408..4ec51e27c 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -146,6 +146,7 @@ define_request! { #[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 { status: Status, untrusted: bool, @@ -154,9 +155,8 @@ pub enum GetBlocksResponse { current_height: u64, output_indices: Vec, daemon_time: u64, - /// Will always serialize as [`PoolInfoExtent::None`]. - pool_info_extent: PoolInfoExtent, }, + /// Will always serialize a [`PoolInfoExtent::Incremental`] field. PoolInfoIncremental { status: Status, untrusted: bool, @@ -165,12 +165,11 @@ pub enum GetBlocksResponse { current_height: u64, output_indices: Vec, daemon_time: u64, - /// Will always serialize as [`PoolInfoExtent::Incremental`]. - pool_info_extent: PoolInfoExtent, added_pool_txs: Vec, remaining_added_pool_txids: ByteArrayVec<32>, removed_pool_txids: ByteArrayVec<32>, }, + /// Will always serialize a [`PoolInfoExtent::Full`] field. PoolInfoFull { status: Status, untrusted: bool, @@ -179,8 +178,6 @@ pub enum GetBlocksResponse { current_height: u64, output_indices: Vec, daemon_time: u64, - /// Will always serialize as [`PoolInfoExtent::Full`]. - pool_info_extent: PoolInfoExtent, added_pool_txs: Vec, remaining_added_pool_txids: ByteArrayVec<32>, }, @@ -196,7 +193,6 @@ impl Default for GetBlocksResponse { current_height: u64::default(), output_indices: Vec::::default(), daemon_time: u64::default(), - pool_info_extent: PoolInfoExtent::default(), } } } @@ -274,7 +270,6 @@ impl EpeeObjectBuilder for __GetBlocksResponseEpeeBuilder { current_height, output_indices, daemon_time, - pool_info_extent, }, PoolInfoExtent::Incremental => GetBlocksResponse::PoolInfoIncremental { status, @@ -284,7 +279,6 @@ impl EpeeObjectBuilder for __GetBlocksResponseEpeeBuilder { current_height, output_indices, daemon_time, - pool_info_extent, 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)?, @@ -297,7 +291,6 @@ impl EpeeObjectBuilder for __GetBlocksResponseEpeeBuilder { current_height, output_indices, daemon_time, - pool_info_extent, added_pool_txs: self.added_pool_txs.ok_or(ELSE)?, remaining_added_pool_txids: self.remaining_added_pool_txids.ok_or(ELSE)?, }, @@ -334,7 +327,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, } => { const POOL_INFO_EXTENT: u8 = PoolInfoExtent::None.to_u8(); add_field! { @@ -357,7 +349,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, added_pool_txs, remaining_added_pool_txids, removed_pool_txids, @@ -385,7 +376,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, added_pool_txs, remaining_added_pool_txids, } => { @@ -428,7 +418,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, } => { // This is on purpose `lower_case` instead of // `CONST_UPPER` due to `stringify!`. @@ -453,7 +442,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, added_pool_txs, remaining_added_pool_txids, removed_pool_txids, @@ -481,7 +469,6 @@ impl EpeeObject for GetBlocksResponse { current_height, output_indices, daemon_time, - pool_info_extent, added_pool_txs, remaining_added_pool_txids, } => { diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index 8de833e21..29fee38fe 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -3,9 +3,6 @@ //---------------------------------------------------------------------------------------------------- Use use std::mem::size_of; -#[cfg(feature = "serde")] -use crate::serde::{serde_false, serde_true}; -use cuprate_epee_encoding::read_varint; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -13,7 +10,8 @@ use serde::{Deserialize, Serialize}; use cuprate_epee_encoding::{ epee_object, error, macros::bytes::{Buf, BufMut}, - read_epee_value, write_field, write_varint, EpeeObject, EpeeObjectBuilder, EpeeValue, Marker, + read_epee_value, read_varint, write_field, write_varint, EpeeObject, EpeeObjectBuilder, + EpeeValue, Marker, }; use super::OutputDistributionData; @@ -53,7 +51,20 @@ fn decompress_integer_array(array: Vec) -> Vec { )] /// Used in [`crate::json::GetOutputDistributionResponse`]. /// -/// This enum's variant depends upon the `binary` and `compress` fields. +/// # 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))] @@ -69,9 +80,6 @@ pub enum Distribution { distribution: Vec, amount: u64, binary: bool, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))] - /// Will always be serialized as `false`. - compress: bool, }, /// Distribution data will be (de)serialized as compressed binary. CompressedBinary { @@ -79,12 +87,6 @@ pub enum Distribution { base: u64, compressed_data: String, amount: u64, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] - /// Will always be serialized as `true`. - binary: bool, - #[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))] - /// Will always be serialized as `true`. - compress: bool, }, } @@ -96,7 +98,6 @@ impl Default for Distribution { distribution: Vec::::default(), amount: u64::default(), binary: false, - compress: false, } } } @@ -133,12 +134,13 @@ impl EpeeObjectBuilder for __DistributionEpeeBuilder { }; } + self.compressed_data = read_epee_value(r).ok().unwrap_or_default(); + self.distribution = read_epee_value(r).ok().unwrap_or_default(); + read_epee_field! { start_height, base, - distribution, amount, - compressed_data, binary, compress } @@ -152,27 +154,24 @@ impl EpeeObjectBuilder for __DistributionEpeeBuilder { let start_height = self.start_height.ok_or(ELSE)?; let base = self.base.ok_or(ELSE)?; let amount = self.amount.ok_or(ELSE)?; - let binary = self.binary.ok_or(ELSE)?; - let compress = self.compress.ok_or(ELSE)?; - let distribution = if binary && compress { + let distribution = if let Some(compressed_data) = self.compressed_data { Distribution::CompressedBinary { - compressed_data: self.compressed_data.ok_or(ELSE)?, + compressed_data, start_height, base, amount, - binary, - compress, } - } else { + } else if let Some(distribution) = self.distribution { Distribution::Uncompressed { - distribution: self.distribution.ok_or(ELSE)?, + binary: self.binary.ok_or(ELSE)?, + distribution, start_height, base, amount, - binary, - compress, } + } else { + return Err(ELSE); }; Ok(distribution) @@ -203,7 +202,6 @@ impl EpeeObject for Distribution { base, amount, binary, - compress, } => { const COMPRESS: bool = false; add_field! { @@ -220,8 +218,6 @@ impl EpeeObject for Distribution { base, compressed_data, amount, - binary, - compress, } => { const BINARY: bool = true; const COMPRESS: bool = true; @@ -257,7 +253,6 @@ impl EpeeObject for Distribution { base, amount, binary, - compress, } => { // This is on purpose `lower_case` instead of // `CONST_UPPER` due to `stringify!`. @@ -277,8 +272,6 @@ impl EpeeObject for Distribution { base, compressed_data, amount, - binary, - compress, } => { let binary = true; let compress = true; From 7616a1e44a568b72c7a6c6317dd229cdbe040d96 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 18 Jul 2024 16:42:48 -0400 Subject: [PATCH 46/93] rpc: `client/{client,constants}.rs` -> `client.rs` --- test-utils/src/rpc/{client => }/client.rs | 4 +++- test-utils/src/rpc/client/constants.rs | 7 ------- test-utils/src/rpc/client/mod.rs | 25 ----------------------- 3 files changed, 3 insertions(+), 33 deletions(-) rename test-utils/src/rpc/{client => }/client.rs (97%) delete mode 100644 test-utils/src/rpc/client/constants.rs delete mode 100644 test-utils/src/rpc/client/mod.rs diff --git a/test-utils/src/rpc/client/client.rs b/test-utils/src/rpc/client.rs similarity index 97% rename from test-utils/src/rpc/client/client.rs rename to test-utils/src/rpc/client.rs index 45cf1f2d1..28c49d8e8 100644 --- a/test-utils/src/rpc/client/client.rs +++ b/test-utils/src/rpc/client.rs @@ -12,7 +12,9 @@ use monero_serai::{ use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation}; -use crate::rpc::client::constants::LOCALHOST_RPC_URL; +//---------------------------------------------------------------------------------------------------- Constants +/// The default URL used for Monero RPC connections. +pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081"; //---------------------------------------------------------------------------------------------------- HttpRpcClient /// An HTTP RPC client for Monero. diff --git a/test-utils/src/rpc/client/constants.rs b/test-utils/src/rpc/client/constants.rs deleted file mode 100644 index ce44a88b4..000000000 --- a/test-utils/src/rpc/client/constants.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! RPC-related Constants. - -//---------------------------------------------------------------------------------------------------- Use - -//---------------------------------------------------------------------------------------------------- Constants -/// The default URL used for Monero RPC connections. -pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081"; diff --git a/test-utils/src/rpc/client/mod.rs b/test-utils/src/rpc/client/mod.rs deleted file mode 100644 index 14c963ac4..000000000 --- a/test-utils/src/rpc/client/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Monero RPC client. -//! -//! This module is a client for Monero RPC that maps the types -//! into the native types used by Cuprate found in `cuprate_types`. -//! -//! # Usage -//! ```rust,ignore -//! #[tokio::main] -//! async fn main() { -//! // Create RPC client. -//! let rpc = HttpRpcClient::new(None).await; -//! -//! // Collect 20 blocks. -//! let mut vec: Vec = vec![]; -//! for height in (3130269 - 20)..3130269 { -//! vec.push(rpc.get_verified_block_information(height).await); -//! } -//! } -//! ``` - -mod client; -pub use client::HttpRpcClient; - -mod constants; -pub use constants::LOCALHOST_RPC_URL; From a716538793f0fff7c84aa65a0c979ed1feef6ea4 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 18 Jul 2024 16:43:34 -0400 Subject: [PATCH 47/93] lib.rs: remove `clippy::module_inception` --- test-utils/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index f09ae292c..068f28ff8 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,5 +1,4 @@ #![doc = include_str!("../README.md")] -#![allow(clippy::module_inception)] pub mod data; pub mod monerod; From ed81b1604cf0116ec9fcc32adc0e0f20f2f1ef6b Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 18 Jul 2024 20:47:47 -0400 Subject: [PATCH 48/93] macros: add json doc test macro --- Cargo.lock | 2 ++ rpc/types/src/macros.rs | 47 +++++++++++++++++++++++++++++++++++++++++ typos.toml | 1 + 3 files changed, 50 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6b506739b..52c6c5a88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -764,6 +764,8 @@ version = "0.0.0" dependencies = [ "cuprate-epee-encoding", "cuprate-fixed-bytes", + "cuprate-json-rpc", + "cuprate-test-utils", "cuprate-types", "monero-serai", "paste", diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index fa0d51882..e001d183b 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -371,3 +371,50 @@ macro_rules! monero_definition_link { }; } pub(crate) use monero_definition_link; + +//---------------------------------------------------------------------------------------------------- Macro +macro_rules! json_rpc_doc_test { + ( + $cuprate_test_utils_rpc_const:ident => $expected:expr + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::json::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, json::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "// Assert it can be turned into a JSON value.\n", + "let value = from_str::(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "let Value::Object(map) = value else {\n", + " panic!();\n", + "};\n", + "\n", + "// If a request...\n", + "if let Some(params) = map.get(\"params\") {\n", + " let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(params.clone()).unwrap();\n", + " assert_eq!(response, expected);\n", + " return;\n", + "}\n", + "\n", + "// Else, if a response...\n", + "let result = map.get(\"result\").unwrap().clone();\n", + "let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(result.clone()).unwrap();\n", + "assert_eq!(response, expected);\n", + "```\n", + ) + } + }; +} +pub(crate) use json_rpc_doc_test; diff --git a/typos.toml b/typos.toml index 0317c404a..fbd66d099 100644 --- a/typos.toml +++ b/typos.toml @@ -18,4 +18,5 @@ extend-exclude = [ "/misc/gpg_keys/", "cryptonight/", "/test-utils/src/rpc/data/json.rs", + "rpc/types/src/json.rs", ] From 302c268cd802f9edfaaaf2ec813a8e3a6164095c Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 18 Jul 2024 20:47:58 -0400 Subject: [PATCH 49/93] json: add some tests --- rpc/types/Cargo.toml | 3 + rpc/types/src/base.rs | 38 ++ rpc/types/src/json.rs | 1179 ++++++++++++++++++++++++++--------------- 3 files changed, 792 insertions(+), 428 deletions(-) diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 768186839..afa39cb15 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -23,4 +23,7 @@ paste = { workspace = true } serde = { workspace = true, optional = true } [dev-dependencies] +cuprate-test-utils = { path = "../../test-utils" } +cuprate-json-rpc = { path = "../json-rpc" } + serde_json = { workspace = true } diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index 4990cdd6a..5970f006a 100644 --- a/rpc/types/src/base.rs +++ b/rpc/types/src/base.rs @@ -57,6 +57,24 @@ pub struct ResponseBase { pub untrusted: bool, } +impl ResponseBase { + /// TODO + pub const fn ok() -> Self { + Self { + status: Status::Ok, + untrusted: false, + } + } + + /// TODO + pub const fn ok_untrusted() -> Self { + Self { + status: Status::Ok, + untrusted: true, + } + } +} + #[cfg(feature = "epee")] epee_object! { ResponseBase, @@ -80,6 +98,26 @@ pub struct AccessResponseBase { pub top_hash: String, } +impl AccessResponseBase { + /// TODO + pub const fn ok() -> Self { + Self { + response_base: ResponseBase::ok(), + credits: 0, + top_hash: String::new(), + } + } + + /// TODO + pub const fn ok_untrusted() -> Self { + Self { + response_base: ResponseBase::ok_untrusted(), + credits: 0, + top_hash: String::new(), + } + } +} + #[cfg(feature = "epee")] epee_object! { AccessResponseBase, diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index f4bca993c..7847fe832 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -7,7 +7,7 @@ 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_and_response, json_rpc_doc_test}, misc::{ AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, @@ -15,6 +15,7 @@ use crate::{ }; //---------------------------------------------------------------------------------------------------- Definitions + // This generates 2 structs: // // - `GetBlockTemplateRequest` @@ -41,7 +42,15 @@ define_request_and_response! { // // If there are any additional attributes (`/// docs` or `#[derive]`s) // for the struct, they go here, e.g.: - // #[derive(Copy)] + // + #[doc = json_rpc_doc_test!( + GET_BLOCK_TEMPLATE_REQUEST => GetBlockTemplateRequest { + extra_nonce: String::default(), + prev_block: String::default(), + reserve_size: 60, + wallet_address: "44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns".into(), + } + )] Request { // Within the `{}` is an infinite matching pattern of: // ``` @@ -66,17 +75,16 @@ define_request_and_response! { // // This is a HACK since `serde`'s default attribute only takes in // string literals and macros (stringify) within attributes do not work. - extra_nonce: String /* = default_expression, "default_literal" */, + extra_nonce: String = default_string(), "default_string", + prev_block: String = default_string(), "default_string", // Another optional expression: // This indicates to the macro to (de)serialize // this field as another type in epee. // // See `cuprate_epee_encoding::epee_object` for info. - prev_block: String /* as Type */, + reserve_size: u64 /* as Type */, - // Regular fields. - reserve_size: u64, wallet_address: String, }, @@ -92,6 +100,23 @@ define_request_and_response! { // "Flatten" means the field(s) of a struct gets inlined // directly into the struct during (de)serialization, see: // . + #[doc = json_rpc_doc_test!( + GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse { + base: ResponseBase::ok(), + blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(), + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + difficulty_top64: 0, + difficulty: 283305047039, + expected_reward: 600000000000, + height: 3195018, + next_seed_hash: "".into(), + prev_hash: "9d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a".into(), + reserved_offset: 131, + seed_hash: "e2aa0b7b55042cd48b02e395d78fa66a29815ccc1584e38db2d1f0e8485cd44f".into(), + seed_height: 3194880, + wide_difficulty: "0x41f64bf3ff".into(), + } + )] ResponseBase { // This is using [`crate::base::ResponseBase`], // so the type we generate will contain this field: @@ -131,6 +156,12 @@ define_request_and_response! { // type alias to `()` instead of a `struct`. Request {}, + #[doc = json_rpc_doc_test!( + GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse { + base: ResponseBase::ok(), + count: 3195019, + } + )] ResponseBase { count: u64, } @@ -140,15 +171,14 @@ define_request_and_response! { on_get_block_hash, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 935..=939, + OnGetBlockHash, - /// ```rust - /// use serde_json::*; - /// use cuprate_rpc_types::json::*; - /// - /// let x = OnGetBlockHashRequest { block_height: [3] }; - /// let x = to_string(&x).unwrap(); - /// assert_eq!(x, "[3]"); - /// ``` + + #[doc = json_rpc_doc_test!( + ON_GET_BLOCK_HASH_REQUEST => OnGetBlockHashRequest { + block_height: [912345], + } + )] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] #[derive(Copy)] @@ -157,14 +187,12 @@ define_request_and_response! { // it must be a 1 length array or else it will error. block_height: [u64; 1], }, - /// ```rust - /// use serde_json::*; - /// use cuprate_rpc_types::json::*; - /// - /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; - /// let x = to_string(&x).unwrap(); - /// assert_eq!(x, "\"asdf\""); - /// ``` + + #[doc = json_rpc_doc_test!( + ON_GET_BLOCK_HASH_RESPONSE => OnGetBlockHashResponse { + block_hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + } + )] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] Response { @@ -176,15 +204,14 @@ define_request_and_response! { submit_block, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1114..=1128, + SubmitBlock, - /// ```rust - /// use serde_json::*; - /// use cuprate_rpc_types::json::*; - /// - /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; - /// let x = to_string(&x).unwrap(); - /// assert_eq!(x, r#"["a"]"#); - /// ``` + + #[doc = json_rpc_doc_test!( + SUBMIT_BLOCK_REQUEST => SubmitBlockRequest { + block_blob: ["0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000".into()], + } + )] #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] Request { @@ -192,6 +219,8 @@ define_request_and_response! { // it must be a 1 length array or else it will error. block_blob: [String; 1], }, + + // FIXME: `cuprate_test_utils` only has an `error` response for this. ResponseBase { block_id: String, } @@ -201,13 +230,31 @@ define_request_and_response! { generateblocks, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1130..=1161, + GenerateBlocks, + + #[doc = json_rpc_doc_test!( + GENERATE_BLOCKS_REQUEST => GenerateBlocksRequest { + amount_of_blocks: 1, + prev_block: String::default(), + wallet_address: "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A".into(), + starting_nonce: 0 + } + )] Request { amount_of_blocks: u64, - prev_block: String, + prev_block: String = default_string(), "default_string", starting_nonce: u32, wallet_address: String, }, + + #[doc = json_rpc_doc_test!( + GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse { + base: ResponseBase::ok(), + blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()], + height: 9783, + } + )] ResponseBase { blocks: Vec, height: u64, @@ -218,11 +265,43 @@ define_request_and_response! { get_last_block_header, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1214..=1238, + GetLastBlockHeader, + #[derive(Copy)] Request { fill_pow_hash: bool = default_false(), "default_false", }, + + #[doc = json_rpc_doc_test!( + GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse { + base: AccessResponseBase::ok(), + block_header: BlockHeader { + block_size: 200419, + block_weight: 200419, + cumulative_difficulty: 366125734645190820, + cumulative_difficulty_top64: 0, + depth: 0, + difficulty: 282052561854, + difficulty_top64: 0, + hash: "57238217820195ac4c08637a144a885491da167899cf1d20e8e7ce0ae0a3434e".into(), + height: 3195020, + long_term_weight: 200419, + major_version: 16, + miner_tx_hash: "7a42667237d4f79891bb407c49c712a9299fb87fce799833a7b633a3a9377dbd".into(), + minor_version: 16, + nonce: 1885649739, + num_txes: 37, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "22c72248ae9c5a2863c94735d710a3525c499f70707d1c2f395169bc5c8a0da3".into(), + reward: 615702960000, + timestamp: 1721245548, + wide_cumulative_difficulty: "0x514bd6a74a7d0a4".into(), + wide_difficulty: "0x41aba48bbe".into() + } + } + )] AccessResponseBase { block_header: BlockHeader, } @@ -233,14 +312,52 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1240..=1269, GetBlockHeaderByHash, + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADER_BY_HASH_REQUEST => GetBlockHeaderByHashRequest { + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + hashes: vec![], + fill_pow_hash: false, + } + )] Request { hash: String, - hashes: Vec, + hashes: Vec = default_vec::(), "default_vec", fill_pow_hash: bool = default_false(), "default_false", }, + + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse { + base: AccessResponseBase::ok(), + block_headers: vec![], + block_header: BlockHeader { + block_size: 210, + block_weight: 210, + cumulative_difficulty: 754734824984346, + cumulative_difficulty_top64: 0, + depth: 2282676, + difficulty: 815625611, + difficulty_top64: 0, + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + height: 912345, + long_term_weight: 210, + major_version: 1, + miner_tx_hash: "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30".into(), + minor_version: 2, + nonce: 1646, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78".into(), + reward: 7388968946286, + timestamp: 1452793716, + wide_cumulative_difficulty: "0x2ae6d65248f1a".into(), + wide_difficulty: "0x309d758b".into() + }, + } + )] AccessResponseBase { block_header: BlockHeader, - block_headers: Vec, + block_headers: Vec = default_vec::(), "default_vec", } } @@ -248,413 +365,619 @@ define_request_and_response! { get_block_header_by_height, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1271..=1296, + GetBlockHeaderByHeight, - #[derive(Copy)] - Request { - height: u64, - fill_pow_hash: bool = default_false(), "default_false", - }, - AccessResponseBase { - block_header: BlockHeader, - } -} -define_request_and_response! { - get_block_headers_range, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1756..=1783, - GetBlockHeadersRange, #[derive(Copy)] + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADER_BY_HEIGHT_REQUEST => GetBlockHeaderByHeightRequest { + height: 912345, + fill_pow_hash: false, + } + )] Request { - start_height: u64, - end_height: u64, + height: u64, fill_pow_hash: bool = default_false(), "default_false", }, - AccessResponseBase { - headers: Vec, - } -} -define_request_and_response! { - get_block, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1298..=1313, - GetBlock, - Request { - // `monerod` has both `hash` and `height` fields. - // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. - // - hash: String = default_string(), "default_string", - height: u64 = default_height(), "default_height", - fill_pow_hash: bool = default_false(), "default_false", - }, + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse { + base: AccessResponseBase::ok(), + block_header: BlockHeader { + block_size: 210, + block_weight: 210, + cumulative_difficulty: 754734824984346, + cumulative_difficulty_top64: 0, + depth: 2282677, + difficulty: 815625611, + difficulty_top64: 0, + hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), + height: 912345, + long_term_weight: 210, + major_version: 1, + miner_tx_hash: "c7da3965f25c19b8eb7dd8db48dcd4e7c885e2491db77e289f0609bf8e08ec30".into(), + minor_version: 2, + nonce: 1646, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78".into(), + reward: 7388968946286, + timestamp: 1452793716, + wide_cumulative_difficulty: "0x2ae6d65248f1a".into(), + wide_difficulty: "0x309d758b".into() + }, + } + )] AccessResponseBase { - blob: String, block_header: BlockHeader, - json: String, // FIXME: this should be defined in a struct, it has many fields. - miner_tx_hash: String, - tx_hashes: Vec, - } -} - -define_request_and_response! { - get_connections, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1734..=1754, - GetConnections, - Request {}, - ResponseBase { - // FIXME: This is a `std::list` in `monerod` because...? - connections: Vec, - } -} - -define_request_and_response! { - get_info, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 693..=789, - GetInfo, - Request {}, - AccessResponseBase { - adjusted_time: u64, - alt_blocks_count: u64, - block_size_limit: u64, - block_size_median: u64, - block_weight_limit: u64, - block_weight_median: u64, - bootstrap_daemon_address: String, - busy_syncing: bool, - cumulative_difficulty_top64: u64, - cumulative_difficulty: u64, - database_size: u64, - difficulty_top64: u64, - difficulty: u64, - free_space: u64, - grey_peerlist_size: u64, - height: u64, - height_without_bootstrap: u64, - incoming_connections_count: u64, - mainnet: bool, - nettype: String, - offline: bool, - outgoing_connections_count: u64, - restricted: bool, - rpc_connections_count: u64, - stagenet: bool, - start_time: u64, - synchronized: bool, - target_height: u64, - target: u64, - testnet: bool, - top_block_hash: String, - tx_count: u64, - tx_pool_size: u64, - update_available: bool, - version: String, - was_bootstrap_ever_used: bool, - white_peerlist_size: u64, - wide_cumulative_difficulty: String, - wide_difficulty: String, - } -} - -define_request_and_response! { - hard_fork_info, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1958..=1995, - HardForkInfo, - Request {}, - AccessResponseBase { - earliest_height: u64, - enabled: bool, - state: u32, - threshold: u32, - version: u8, - votes: u32, - voting: u8, - window: u32, } } -define_request_and_response! { - set_bans, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2032..=2067, - SetBans, - Request { - bans: Vec, - }, - ResponseBase {} -} - -define_request_and_response! { - get_bans, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1997..=2030, - GetBans, - Request {}, - ResponseBase { - bans: Vec, - } -} - -define_request_and_response! { - banned, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2069..=2094, - Banned, - #[cfg_attr(feature = "serde", serde(transparent))] - #[repr(transparent)] - Request { - address: String, - }, - Response { - banned: bool, - seconds: u32, - status: Status, - } -} - -define_request_and_response! { - flush_txpool, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2096..=2116, - FlushTransactionPool, - Request { - txids: Vec = default_vec::(), "default_vec", - }, - #[cfg_attr(feature = "serde", serde(transparent))] - #[repr(transparent)] - Response { - status: Status, - } -} - -define_request_and_response! { - get_output_histogram, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2118..=2168, - GetOutputHistogram, - Request { - amounts: Vec, - min_count: u64, - max_count: u64, - unlocked: bool, - recent_cutoff: u64, - }, - AccessResponseBase { - histogram: Vec, - } -} - -define_request_and_response! { - get_coinbase_tx_sum, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2213..=2248, - GetCoinbaseTxSum, - Request { - height: u64, - count: u64, - }, - AccessResponseBase { - emission_amount: u64, - emission_amount_top64: u64, - fee_amount: u64, - fee_amount_top64: u64, - wide_emission_amount: String, - wide_fee_amount: String, - } -} - -define_request_and_response! { - get_version, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2170..=2211, - GetVersion, - Request {}, - ResponseBase { - version: u32, - release: bool, - #[serde(skip_serializing_if = "is_zero")] - current_height: u64 = default_zero::(), "default_zero", - #[serde(skip_serializing_if = "is_zero")] - target_height: u64 = default_zero::(), "default_zero", - #[serde(skip_serializing_if = "Vec::is_empty")] - hard_forks: Vec = default_vec(), "default_vec", - } -} - -define_request_and_response! { - get_fee_estimate, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2250..=2277, - GetFeeEstimate, - Request {}, - AccessResponseBase { - fee: u64, - fees: Vec, - #[serde(skip_serializing_if = "is_one")] - quantization_mask: u64, - } -} - -define_request_and_response! { - get_alternate_chains, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2279..=2310, - GetAlternateChains, - Request {}, - ResponseBase { - chains: Vec, - } -} - -define_request_and_response! { - relay_tx, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2361..=2381, - RelayTx, - Request { - txids: Vec, - }, - #[cfg_attr(feature = "serde", serde(transparent))] - #[repr(transparent)] - Response { - status: Status, - } -} - -define_request_and_response! { - sync_info, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2383..=2443, - SyncInfo, - Request {}, - AccessResponseBase { - height: u64, - next_needed_pruning_seed: u32, - overview: String, - // FIXME: This is a `std::list` in `monerod` because...? - peers: Vec, - // FIXME: This is a `std::list` in `monerod` because...? - spans: Vec, - target_height: u64, - } -} - -define_request_and_response! { - get_txpool_backlog, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1637..=1664, - GetTransactionPoolBacklog, - Request {}, - ResponseBase { - // TODO: this is a [`BinaryString`]. - backlog: Vec, - } -} - -define_request_and_response! { - get_output_distribution, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2445..=2520, - /// This type is also used in the (undocumented) - /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) - /// binary endpoint. - GetOutputDistribution, - Request { - amounts: Vec, - binary: bool, - compress: bool, - cumulative: bool, - from_height: u64, - to_height: u64, - }, - AccessResponseBase { - distributions: Vec, - } -} - -define_request_and_response! { - get_miner_data, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 996..=1044, - GetMinerData, - Request {}, - ResponseBase { - major_version: u8, - height: u64, - prev_id: String, - seed_hash: String, - difficulty: String, - median_weight: u64, - already_generated_coins: u64, - } -} - -define_request_and_response! { - prune_blockchain, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2747..=2772, - PruneBlockchain, - #[derive(Copy)] - Request { - check: bool = default_false(), "default_false", - }, - ResponseBase { - pruned: bool, - pruning_seed: u32, - } -} - -define_request_and_response! { - calc_pow, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1046..=1066, - CalcPow, - Request { - major_version: u8, - height: u64, - block_blob: String, - seed_hash: String, - }, - #[cfg_attr(feature = "serde", serde(transparent))] - #[repr(transparent)] - Response { - pow_hash: String, - } -} - -define_request_and_response! { - flush_cache, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2774..=2796, - FlushCache, - #[derive(Copy)] - Request { - bad_txs: bool = default_false(), "default_false", - bad_blocks: bool = default_false(), "default_false", - }, - ResponseBase {} -} - -define_request_and_response! { - add_aux_pow, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 1068..=1112, - AddAuxPow, - Request { - blocktemplate_blob: String, - aux_pow: Vec, - }, - ResponseBase { - blocktemplate_blob: String, - blockhashing_blob: String, - merkle_root: String, - merkle_tree_depth: u64, - aux_pow: Vec, - } -} +// define_request_and_response! { +// get_block_headers_range, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1756..=1783, +// GetBlockHeadersRange, +// #[derive(Copy)] +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// start_height: u64, +// end_height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1298..=1313, +// GetBlock, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// // `monerod` has both `hash` and `height` fields. +// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. +// // +// hash: String = default_string(), "default_string", +// height: u64 = default_height(), "default_height", +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// blob: String, +// block_header: BlockHeader, +// json: String, // FIXME: this should be defined in a struct, it has many fields. +// miner_tx_hash: String, +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// get_connections, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1734..=1754, +// GetConnections, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// // FIXME: This is a `std::list` in `monerod` because...? +// connections: Vec, +// } +// } + +// define_request_and_response! { +// get_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 693..=789, +// GetInfo, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// adjusted_time: u64, +// alt_blocks_count: u64, +// block_size_limit: u64, +// block_size_median: u64, +// block_weight_limit: u64, +// block_weight_median: u64, +// bootstrap_daemon_address: String, +// busy_syncing: bool, +// cumulative_difficulty_top64: u64, +// cumulative_difficulty: u64, +// database_size: u64, +// difficulty_top64: u64, +// difficulty: u64, +// free_space: u64, +// grey_peerlist_size: u64, +// height: u64, +// height_without_bootstrap: u64, +// incoming_connections_count: u64, +// mainnet: bool, +// nettype: String, +// offline: bool, +// outgoing_connections_count: u64, +// restricted: bool, +// rpc_connections_count: u64, +// stagenet: bool, +// start_time: u64, +// synchronized: bool, +// target_height: u64, +// target: u64, +// testnet: bool, +// top_block_hash: String, +// tx_count: u64, +// tx_pool_size: u64, +// update_available: bool, +// version: String, +// was_bootstrap_ever_used: bool, +// white_peerlist_size: u64, +// wide_cumulative_difficulty: String, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// hard_fork_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1958..=1995, +// HardForkInfo, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// earliest_height: u64, +// enabled: bool, +// state: u32, +// threshold: u32, +// version: u8, +// votes: u32, +// voting: u8, +// window: u32, +// } +// } + +// define_request_and_response! { +// set_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2032..=2067, +// SetBans, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// bans: Vec, +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase {} +// } + +// define_request_and_response! { +// get_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1997..=2030, +// GetBans, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// bans: Vec, +// } +// } + +// define_request_and_response! { +// banned, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2069..=2094, +// Banned, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// address: String, +// }, +// Response { +// banned: bool, +// seconds: u32, +// status: Status, +// } +// } + +// define_request_and_response! { +// flush_txpool, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2096..=2116, +// FlushTransactionPool, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// txids: Vec = default_vec::(), "default_vec", +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_output_histogram, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2118..=2168, +// GetOutputHistogram, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// amounts: Vec, +// min_count: u64, +// max_count: u64, +// unlocked: bool, +// recent_cutoff: u64, +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// histogram: Vec, +// } +// } + +// define_request_and_response! { +// get_coinbase_tx_sum, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2213..=2248, +// GetCoinbaseTxSum, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// height: u64, +// count: u64, +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// emission_amount: u64, +// emission_amount_top64: u64, +// fee_amount: u64, +// fee_amount_top64: u64, +// wide_emission_amount: String, +// wide_fee_amount: String, +// } +// } + +// define_request_and_response! { +// get_version, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2170..=2211, +// GetVersion, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// version: u32, +// release: bool, +// #[serde(skip_serializing_if = "is_zero")] +// current_height: u64 = default_zero::(), "default_zero", +// #[serde(skip_serializing_if = "is_zero")] +// target_height: u64 = default_zero::(), "default_zero", +// #[serde(skip_serializing_if = "Vec::is_empty")] +// hard_forks: Vec = default_vec(), "default_vec", +// } +// } + +// define_request_and_response! { +// get_fee_estimate, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2250..=2277, +// GetFeeEstimate, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// fee: u64, +// fees: Vec, +// #[serde(skip_serializing_if = "is_one")] +// quantization_mask: u64, +// } +// } + +// define_request_and_response! { +// get_alternate_chains, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2279..=2310, +// GetAlternateChains, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// chains: Vec, +// } +// } + +// define_request_and_response! { +// relay_tx, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2361..=2381, +// RelayTx, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// txids: Vec, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// sync_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2383..=2443, +// SyncInfo, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// height: u64, +// next_needed_pruning_seed: u32, +// overview: String, +// // FIXME: This is a `std::list` in `monerod` because...? +// peers: Vec, +// // FIXME: This is a `std::list` in `monerod` because...? +// spans: Vec, +// target_height: u64, +// } +// } + +// define_request_and_response! { +// get_txpool_backlog, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1637..=1664, +// GetTransactionPoolBacklog, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// // TODO: this is a [`BinaryString`]. +// backlog: Vec, +// } +// } + +// define_request_and_response! { +// get_output_distribution, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2445..=2520, +// /// This type is also used in the (undocumented) +// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) +// /// binary endpoint. +// GetOutputDistribution, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// amounts: Vec, +// binary: bool, +// compress: bool, +// cumulative: bool, +// from_height: u64, +// to_height: u64, +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// AccessResponseBase { +// distributions: Vec, +// } +// } + +// define_request_and_response! { +// get_miner_data, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 996..=1044, +// GetMinerData, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request {}, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// major_version: u8, +// height: u64, +// prev_id: String, +// seed_hash: String, +// difficulty: String, +// median_weight: u64, +// already_generated_coins: u64, +// } +// } + +// define_request_and_response! { +// prune_blockchain, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2747..=2772, +// PruneBlockchain, +// #[derive(Copy)] +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// check: bool = default_false(), "default_false", +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// pruned: bool, +// pruning_seed: u32, +// } +// } + +// define_request_and_response! { +// calc_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1046..=1066, +// CalcPow, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// major_version: u8, +// height: u64, +// block_blob: String, +// seed_hash: String, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// pow_hash: String, +// } +// } + +// define_request_and_response! { +// flush_cache, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2774..=2796, +// FlushCache, +// #[derive(Copy)] +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// bad_txs: bool = default_false(), "default_false", +// bad_blocks: bool = default_false(), "default_false", +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase {} +// } + +// define_request_and_response! { +// add_aux_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1068..=1112, +// AddAuxPow, +// #[doc = json_rpc_doc_test!( +// _REQUEST => Request { +// } +// )] +// Request { +// blocktemplate_blob: String, +// aux_pow: Vec, +// }, +// #[doc = json_rpc_doc_test!( +// _RESPONSE => Response { +// } +// )] +// ResponseBase { +// blocktemplate_blob: String, +// blockhashing_blob: String, +// merkle_root: String, +// merkle_tree_depth: u64, +// aux_pow: Vec, +// } +// } //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] From 5f83e3790a61f78d879f9e7d1b40026beac0d605 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 19 Jul 2024 17:50:42 -0400 Subject: [PATCH 50/93] json: add doc-test for all types --- rpc/types/src/defaults.rs | 6 + rpc/types/src/json.rs | 1623 ++++++++++++++++++---------- rpc/types/src/misc/distribution.rs | 63 +- rpc/types/src/misc/misc.rs | 10 +- test-utils/src/rpc/data/json.rs | 7 +- 5 files changed, 1134 insertions(+), 575 deletions(-) diff --git a/rpc/types/src/defaults.rs b/rpc/types/src/defaults.rs index 9366a266b..6addd0abf 100644 --- a/rpc/types/src/defaults.rs +++ b/rpc/types/src/defaults.rs @@ -53,6 +53,12 @@ pub(crate) fn default_zero>() -> T { T::from(0) } +/// Default `1` value used in request/response types. +#[inline] +pub(crate) fn default_one>() -> T { + T::from(1) +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 7847fe832..ebca19e15 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -5,12 +5,16 @@ //---------------------------------------------------------------------------------------------------- Import use crate::{ base::{AccessResponseBase, ResponseBase}, - defaults::{default_false, default_height, default_string, default_vec, default_zero}, + defaults::{ + default_false, default_height, default_one, default_string, default_true, default_vec, + default_zero, + }, free::{is_one, is_zero}, macros::{define_request_and_response, json_rpc_doc_test}, misc::{ - AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, HardforkEntry, - HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, + AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, + GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, + Span, Status, SyncInfoPeer, TxBacklogEntry, }, }; @@ -414,570 +418,1055 @@ define_request_and_response! { } } -// define_request_and_response! { -// get_block_headers_range, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1756..=1783, -// GetBlockHeadersRange, -// #[derive(Copy)] -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// start_height: u64, -// end_height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1298..=1313, -// GetBlock, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// // `monerod` has both `hash` and `height` fields. -// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. -// // -// hash: String = default_string(), "default_string", -// height: u64 = default_height(), "default_height", -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// blob: String, -// block_header: BlockHeader, -// json: String, // FIXME: this should be defined in a struct, it has many fields. -// miner_tx_hash: String, -// tx_hashes: Vec, -// } -// } - -// define_request_and_response! { -// get_connections, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1734..=1754, -// GetConnections, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// // FIXME: This is a `std::list` in `monerod` because...? -// connections: Vec, -// } -// } - -// define_request_and_response! { -// get_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 693..=789, -// GetInfo, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// adjusted_time: u64, -// alt_blocks_count: u64, -// block_size_limit: u64, -// block_size_median: u64, -// block_weight_limit: u64, -// block_weight_median: u64, -// bootstrap_daemon_address: String, -// busy_syncing: bool, -// cumulative_difficulty_top64: u64, -// cumulative_difficulty: u64, -// database_size: u64, -// difficulty_top64: u64, -// difficulty: u64, -// free_space: u64, -// grey_peerlist_size: u64, -// height: u64, -// height_without_bootstrap: u64, -// incoming_connections_count: u64, -// mainnet: bool, -// nettype: String, -// offline: bool, -// outgoing_connections_count: u64, -// restricted: bool, -// rpc_connections_count: u64, -// stagenet: bool, -// start_time: u64, -// synchronized: bool, -// target_height: u64, -// target: u64, -// testnet: bool, -// top_block_hash: String, -// tx_count: u64, -// tx_pool_size: u64, -// update_available: bool, -// version: String, -// was_bootstrap_ever_used: bool, -// white_peerlist_size: u64, -// wide_cumulative_difficulty: String, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// hard_fork_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1958..=1995, -// HardForkInfo, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// earliest_height: u64, -// enabled: bool, -// state: u32, -// threshold: u32, -// version: u8, -// votes: u32, -// voting: u8, -// window: u32, -// } -// } - -// define_request_and_response! { -// set_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2032..=2067, -// SetBans, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// bans: Vec, -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase {} -// } - -// define_request_and_response! { -// get_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1997..=2030, -// GetBans, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// bans: Vec, -// } -// } - -// define_request_and_response! { -// banned, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2069..=2094, -// Banned, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// address: String, -// }, -// Response { -// banned: bool, -// seconds: u32, -// status: Status, -// } -// } - -// define_request_and_response! { -// flush_txpool, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2096..=2116, -// FlushTransactionPool, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// txids: Vec = default_vec::(), "default_vec", -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_output_histogram, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2118..=2168, -// GetOutputHistogram, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// amounts: Vec, -// min_count: u64, -// max_count: u64, -// unlocked: bool, -// recent_cutoff: u64, -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// histogram: Vec, -// } -// } - -// define_request_and_response! { -// get_coinbase_tx_sum, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2213..=2248, -// GetCoinbaseTxSum, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// height: u64, -// count: u64, -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// emission_amount: u64, -// emission_amount_top64: u64, -// fee_amount: u64, -// fee_amount_top64: u64, -// wide_emission_amount: String, -// wide_fee_amount: String, -// } -// } - -// define_request_and_response! { -// get_version, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2170..=2211, -// GetVersion, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// version: u32, -// release: bool, -// #[serde(skip_serializing_if = "is_zero")] -// current_height: u64 = default_zero::(), "default_zero", -// #[serde(skip_serializing_if = "is_zero")] -// target_height: u64 = default_zero::(), "default_zero", -// #[serde(skip_serializing_if = "Vec::is_empty")] -// hard_forks: Vec = default_vec(), "default_vec", -// } -// } - -// define_request_and_response! { -// get_fee_estimate, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2250..=2277, -// GetFeeEstimate, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// fee: u64, -// fees: Vec, -// #[serde(skip_serializing_if = "is_one")] -// quantization_mask: u64, -// } -// } - -// define_request_and_response! { -// get_alternate_chains, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2279..=2310, -// GetAlternateChains, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// chains: Vec, -// } -// } - -// define_request_and_response! { -// relay_tx, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2361..=2381, -// RelayTx, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// txids: Vec, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// sync_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2383..=2443, -// SyncInfo, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// height: u64, -// next_needed_pruning_seed: u32, -// overview: String, -// // FIXME: This is a `std::list` in `monerod` because...? -// peers: Vec, -// // FIXME: This is a `std::list` in `monerod` because...? -// spans: Vec, -// target_height: u64, -// } -// } - -// define_request_and_response! { -// get_txpool_backlog, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1637..=1664, -// GetTransactionPoolBacklog, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// // TODO: this is a [`BinaryString`]. -// backlog: Vec, -// } -// } - -// define_request_and_response! { -// get_output_distribution, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2445..=2520, -// /// This type is also used in the (undocumented) -// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) -// /// binary endpoint. -// GetOutputDistribution, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// amounts: Vec, -// binary: bool, -// compress: bool, -// cumulative: bool, -// from_height: u64, -// to_height: u64, -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// AccessResponseBase { -// distributions: Vec, -// } -// } - -// define_request_and_response! { -// get_miner_data, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 996..=1044, -// GetMinerData, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request {}, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// major_version: u8, -// height: u64, -// prev_id: String, -// seed_hash: String, -// difficulty: String, -// median_weight: u64, -// already_generated_coins: u64, -// } -// } - -// define_request_and_response! { -// prune_blockchain, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2747..=2772, -// PruneBlockchain, -// #[derive(Copy)] -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// check: bool = default_false(), "default_false", -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// pruned: bool, -// pruning_seed: u32, -// } -// } - -// define_request_and_response! { -// calc_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1046..=1066, -// CalcPow, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// major_version: u8, -// height: u64, -// block_blob: String, -// seed_hash: String, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// pow_hash: String, -// } -// } - -// define_request_and_response! { -// flush_cache, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2774..=2796, -// FlushCache, -// #[derive(Copy)] -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// bad_txs: bool = default_false(), "default_false", -// bad_blocks: bool = default_false(), "default_false", -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase {} -// } - -// define_request_and_response! { -// add_aux_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1068..=1112, -// AddAuxPow, -// #[doc = json_rpc_doc_test!( -// _REQUEST => Request { -// } -// )] -// Request { -// blocktemplate_blob: String, -// aux_pow: Vec, -// }, -// #[doc = json_rpc_doc_test!( -// _RESPONSE => Response { -// } -// )] -// ResponseBase { -// blocktemplate_blob: String, -// blockhashing_blob: String, -// merkle_root: String, -// merkle_tree_depth: u64, -// aux_pow: Vec, -// } -// } +define_request_and_response! { + get_block_headers_range, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1756..=1783, + + GetBlockHeadersRange, + + #[derive(Copy)] + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADERS_RANGE_REQUEST => GetBlockHeadersRangeRequest { + start_height: 1545999, + end_height: 1546000, + fill_pow_hash: false, + } + )] + Request { + start_height: u64, + end_height: u64, + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = json_rpc_doc_test!( + GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse { + base: AccessResponseBase::ok(), + headers: vec![ + BlockHeader { + block_size: 301413, + block_weight: 301413, + cumulative_difficulty: 13185267971483472, + cumulative_difficulty_top64: 0, + depth: 1649024, + difficulty: 134636057921, + difficulty_top64: 0, + hash: "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a".into(), + height: 1545999, + long_term_weight: 301413, + major_version: 6, + miner_tx_hash: "9909c6f8a5267f043c3b2b079fb4eacc49ef9c1dee1c028eeb1a259b95e6e1d9".into(), + minor_version: 6, + nonce: 3246403956, + num_txes: 20, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "0ef6e948f77b8f8806621003f5de24b1bcbea150bc0e376835aea099674a5db5".into(), + reward: 5025593029981, + timestamp: 1523002893, + wide_cumulative_difficulty: "0x2ed7ee6db56750".into(), + wide_difficulty: "0x1f58ef3541".into() + }, + BlockHeader { + block_size: 13322, + block_weight: 13322, + cumulative_difficulty: 13185402687569710, + cumulative_difficulty_top64: 0, + depth: 1649023, + difficulty: 134716086238, + difficulty_top64: 0, + hash: "b408bf4cfcd7de13e7e370c84b8314c85b24f0ba4093ca1d6eeb30b35e34e91a".into(), + height: 1546000, + long_term_weight: 13322, + major_version: 7, + miner_tx_hash: "7f749c7c64acb35ef427c7454c45e6688781fbead9bbf222cb12ad1a96a4e8f6".into(), + minor_version: 7, + nonce: 3737164176, + num_txes: 1, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "86d1d20a40cefcf3dd410ff6967e0491613b77bf73ea8f1bf2e335cf9cf7d57a".into(), + reward: 4851952181070, + timestamp: 1523002931, + wide_cumulative_difficulty: "0x2ed80dcb69bf2e".into(), + wide_difficulty: "0x1f5db457de".into() + } + ], + } + )] + AccessResponseBase { + headers: Vec, + } +} + +define_request_and_response! { + get_block, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1298..=1313, + GetBlock, + + #[doc = json_rpc_doc_test!( + GET_BLOCK_REQUEST => GetBlockRequest { + height: 2751506, + hash: String::default(), + fill_pow_hash: false, + } + )] + Request { + // `monerod` has both `hash` and `height` fields. + // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. + // + hash: String = default_string(), "default_string", + height: u64 = default_height(), "default_height", + fill_pow_hash: bool = default_false(), "default_false", + }, + + #[doc = json_rpc_doc_test!( + GET_BLOCK_RESPONSE => GetBlockResponse { + base: AccessResponseBase::ok(), + blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(), + block_header: BlockHeader { + block_size: 106, + block_weight: 106, + cumulative_difficulty: 236046001376524168, + cumulative_difficulty_top64: 0, + depth: 443517, + difficulty: 313732272488, + difficulty_top64: 0, + hash: "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428".into(), + height: 2751506, + long_term_weight: 176470, + major_version: 16, + miner_tx_hash: "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd".into(), + minor_version: 16, + nonce: 4110909056, + num_txes: 0, + orphan_status: false, + pow_hash: "".into(), + prev_hash: "b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7".into(), + reward: 600000000000, + timestamp: 1667941829, + wide_cumulative_difficulty: "0x3469a966eb2f788".into(), + wide_difficulty: "0x490be69168".into() + }, + json: "{\n \"major_version\": 16, \n \"minor_version\": 16, \n \"timestamp\": 1667941829, \n \"prev_id\": \"b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7\", \n \"nonce\": 4110909056, \n \"miner_tx\": {\n \"version\": 2, \n \"unlock_time\": 2751566, \n \"vin\": [ {\n \"gen\": {\n \"height\": 2751506\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 600000000000, \n \"target\": {\n \"tagged_key\": {\n \"key\": \"d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85\", \n \"view_tag\": \"d0\"\n }\n }\n }\n ], \n \"extra\": [ 1, 159, 98, 157, 139, 54, 189, 22, 162, 191, 206, 62, 168, 12, 49, 220, 77, 135, 98, 198, 113, 101, 174, 194, 24, 69, 73, 78, 50, 183, 88, 47, 224, 2, 17, 0, 0, 0, 41, 122, 120, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n ], \n \"rct_signatures\": {\n \"type\": 0\n }\n }, \n \"tx_hashes\": [ ]\n}".into(), + miner_tx_hash: "e49b854c5f339d7410a77f2a137281d8042a0ffc7ef9ab24cd670b67139b24cd".into(), + tx_hashes: vec![], + } + )] + AccessResponseBase { + blob: String, + block_header: BlockHeader, + json: String, // FIXME: this should be defined in a struct, it has many fields. + miner_tx_hash: String, + tx_hashes: Vec = default_vec::(), "default_vec", + } +} + +define_request_and_response! { + get_connections, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1734..=1754, + + GetConnections, + + Request {}, + + #[doc = json_rpc_doc_test!( + GET_CONNECTIONS_RESPONSE => GetConnectionsResponse { + base: ResponseBase::ok(), + connections: vec![ + ConnectionInfo { + address: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion:18083".into(), + address_type: 4, + avg_download: 0, + avg_upload: 0, + connection_id: "22ef856d0f1d44cc95e84fecfd065fe2".into(), + current_download: 0, + current_upload: 0, + height: 3195026, + host: "3evk3kezfjg44ma6tvesy7rbxwwpgpympj45xar5fo4qajrsmkoaqdqd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 76651, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 0, + recv_count: 240328, + recv_idle_time: 34, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 3406572, + send_idle_time: 30, + state: "normal".into(), + support_flags: 0 + }, + ConnectionInfo { + address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), + address_type: 4, + avg_download: 0, + avg_upload: 0, + connection_id: "c7734e15936f485a86d2b0534f87e499".into(), + current_download: 0, + current_upload: 0, + height: 3195024, + host: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 76755, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 389, + recv_count: 237657, + recv_idle_time: 120, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 3370566, + send_idle_time: 120, + state: "normal".into(), + support_flags: 0 + } + ], + } + )] + ResponseBase { + // FIXME: This is a `std::list` in `monerod` because...? + connections: Vec, + } +} + +define_request_and_response! { + get_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 693..=789, + GetInfo, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_INFO_RESPONSE => GetInfoResponse { + base: AccessResponseBase::ok(), + adjusted_time: 1721245289, + alt_blocks_count: 16, + block_size_limit: 600000, + block_size_median: 300000, + block_weight_limit: 600000, + block_weight_median: 300000, + bootstrap_daemon_address: "".into(), + busy_syncing: false, + cumulative_difficulty: 366127702242611947, + cumulative_difficulty_top64: 0, + database_size: 235169075200, + difficulty: 280716748706, + difficulty_top64: 0, + free_space: 30521749504, + grey_peerlist_size: 4996, + height: 3195028, + height_without_bootstrap: 3195028, + incoming_connections_count: 62, + mainnet: true, + nettype: "mainnet".into(), + offline: false, + outgoing_connections_count: 1143, + restricted: false, + rpc_connections_count: 1, + stagenet: false, + start_time: 1720462427, + synchronized: true, + target: 120, + target_height: 0, + testnet: false, + top_block_hash: "bdf06d18ed1931a8ee62654e9b6478cc459bc7072628b8e36f4524d339552946".into(), + tx_count: 43205750, + tx_pool_size: 12, + update_available: false, + version: "0.18.3.3-release".into(), + was_bootstrap_ever_used: false, + white_peerlist_size: 1000, + wide_cumulative_difficulty: "0x514bf349299d2eb".into(), + wide_difficulty: "0x415c05a7a2".into() + } + )] + AccessResponseBase { + adjusted_time: u64, + alt_blocks_count: u64, + block_size_limit: u64, + block_size_median: u64, + block_weight_limit: u64, + block_weight_median: u64, + bootstrap_daemon_address: String, + busy_syncing: bool, + cumulative_difficulty_top64: u64, + cumulative_difficulty: u64, + database_size: u64, + difficulty_top64: u64, + difficulty: u64, + free_space: u64, + grey_peerlist_size: u64, + height: u64, + height_without_bootstrap: u64, + incoming_connections_count: u64, + mainnet: bool, + nettype: String, + offline: bool, + outgoing_connections_count: u64, + restricted: bool, + rpc_connections_count: u64, + stagenet: bool, + start_time: u64, + synchronized: bool, + target_height: u64, + target: u64, + testnet: bool, + top_block_hash: String, + tx_count: u64, + tx_pool_size: u64, + update_available: bool, + version: String, + was_bootstrap_ever_used: bool, + white_peerlist_size: u64, + wide_cumulative_difficulty: String, + wide_difficulty: String, + } +} + +define_request_and_response! { + hard_fork_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1958..=1995, + HardForkInfo, + Request {}, + + #[doc = json_rpc_doc_test!( + HARD_FORK_INFO_RESPONSE => HardForkInfoResponse { + base: AccessResponseBase::ok(), + earliest_height: 2689608, + enabled: true, + state: 0, + threshold: 0, + version: 16, + votes: 10080, + voting: 16, + window: 10080 + } + )] + AccessResponseBase { + earliest_height: u64, + enabled: bool, + state: u32, + threshold: u32, + version: u8, + votes: u32, + voting: u8, + window: u32, + } +} + +define_request_and_response! { + set_bans, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2032..=2067, + SetBans, + + #[doc = json_rpc_doc_test!( + SET_BANS_REQUEST => SetBansRequest { + bans: vec![ SetBan { + host: "192.168.1.51".into(), + ip: 0, + ban: true, + seconds: 30 + }] + } + )] + Request { + bans: Vec, + }, + + #[doc = json_rpc_doc_test!( + SET_BANS_RESPONSE => SetBansResponse { + base: ResponseBase::ok(), + } + )] + ResponseBase {} +} + +define_request_and_response! { + get_bans, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1997..=2030, + GetBans, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_BANS_RESPONSE => GetBansResponse { + base: ResponseBase::ok(), + bans: vec![ + GetBan { + host: "104.248.206.131".into(), + ip: 2211379304, + seconds: 689754 + }, + GetBan { + host: "209.222.252.0/24".into(), + ip: 0, + seconds: 689754 + } + ] + } + )] + ResponseBase { + bans: Vec, + } +} + +define_request_and_response! { + banned, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2069..=2094, + Banned, + + #[doc = json_rpc_doc_test!( + BANNED_REQUEST => BannedRequest { + address: "95.216.203.255".into(), + } + )] + Request { + address: String, + }, + + #[doc = json_rpc_doc_test!( + BANNED_RESPONSE => BannedResponse { + banned: true, + seconds: 689655, + status: Status::Ok, + } + )] + Response { + banned: bool, + seconds: u32, + status: Status, + } +} + +define_request_and_response! { + flush_txpool, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2096..=2116, + FlushTransactionPool, + + #[doc = json_rpc_doc_test!( + FLUSH_TRANSACTION_POOL_REQUEST => FlushTransactionPoolRequest { + txids: vec!["dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into()], + } + )] + Request { + txids: Vec = default_vec::(), "default_vec", + }, + + #[doc = json_rpc_doc_test!( + FLUSH_TRANSACTION_POOL_RESPONSE => FlushTransactionPoolResponse { + status: Status::Ok, + } + )] + #[repr(transparent)] + Response { + status: Status, + } +} + +define_request_and_response! { + get_output_histogram, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2118..=2168, + GetOutputHistogram, + + #[doc = json_rpc_doc_test!( + GET_OUTPUT_HISTOGRAM_REQUEST => GetOutputHistogramRequest { + amounts: vec![20000000000], + min_count: 0, + max_count: 0, + unlocked: false, + recent_cutoff: 0, + } + )] + Request { + amounts: Vec, + min_count: u64 = default_zero::(), "default_zero", + max_count: u64 = default_zero::(), "default_zero", + unlocked: bool = default_false(), "default_false", + recent_cutoff: u64 = default_zero::(), "default_zero", + }, + + #[doc = json_rpc_doc_test!( + GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse { + base: AccessResponseBase::ok(), + histogram: vec![HistogramEntry { + amount: 20000000000, + recent_instances: 0, + total_instances: 381490, + unlocked_instances: 0 + }] + } + )] + AccessResponseBase { + histogram: Vec, + } +} + +define_request_and_response! { + get_coinbase_tx_sum, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2213..=2248, + + GetCoinbaseTxSum, + + #[doc = json_rpc_doc_test!( + GET_COINBASE_TX_SUM_REQUEST => GetCoinbaseTxSumRequest { + height: 1563078, + count: 2 + } + )] + Request { + height: u64, + count: u64, + }, + + #[doc = json_rpc_doc_test!( + GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse { + base: AccessResponseBase::ok(), + emission_amount: 9387854817320, + emission_amount_top64: 0, + fee_amount: 83981380000, + fee_amount_top64: 0, + wide_emission_amount: "0x889c7c06828".into(), + wide_fee_amount: "0x138dae29a0".into() + } + )] + AccessResponseBase { + emission_amount: u64, + emission_amount_top64: u64, + fee_amount: u64, + fee_amount_top64: u64, + wide_emission_amount: String, + wide_fee_amount: String, + } +} + +define_request_and_response! { + get_version, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2170..=2211, + + GetVersion, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_VERSION_RESPONSE => GetVersionResponse { + base: ResponseBase::ok(), + current_height: 3195051, + hard_forks: vec![ + HardforkEntry { + height: 1, + hf_version: 1 + }, + HardforkEntry { + height: 1009827, + hf_version: 2 + }, + HardforkEntry { + height: 1141317, + hf_version: 3 + }, + HardforkEntry { + height: 1220516, + hf_version: 4 + }, + HardforkEntry { + height: 1288616, + hf_version: 5 + }, + HardforkEntry { + height: 1400000, + hf_version: 6 + }, + HardforkEntry { + height: 1546000, + hf_version: 7 + }, + HardforkEntry { + height: 1685555, + hf_version: 8 + }, + HardforkEntry { + height: 1686275, + hf_version: 9 + }, + HardforkEntry { + height: 1788000, + hf_version: 10 + }, + HardforkEntry { + height: 1788720, + hf_version: 11 + }, + HardforkEntry { + height: 1978433, + hf_version: 12 + }, + HardforkEntry { + height: 2210000, + hf_version: 13 + }, + HardforkEntry { + height: 2210720, + hf_version: 14 + }, + HardforkEntry { + height: 2688888, + hf_version: 15 + }, + HardforkEntry { + height: 2689608, + hf_version: 16 + } + ], + release: true, + version: 196621, + target_height: 0, + } + )] + ResponseBase { + version: u32, + release: bool, + current_height: u64 = default_zero::(), "default_zero", + target_height: u64 = default_zero::(), "default_zero", + hard_forks: Vec = default_vec(), "default_vec", + } +} + +define_request_and_response! { + get_fee_estimate, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2250..=2277, + GetFeeEstimate, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse { + base: AccessResponseBase::ok(), + fee: 20000, + fees: vec![20000,80000,320000,4000000], + quantization_mask: 10000, + } + )] + AccessResponseBase { + fee: u64, + fees: Vec, + quantization_mask: u64 = default_one::(), "default_one", + } +} + +define_request_and_response! { + get_alternate_chains, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2279..=2310, + GetAlternateChains, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse { + base: ResponseBase::ok(), + chains: vec![ + ChainInfo { + block_hash: "4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into(), + block_hashes: vec!["4826c7d45d7cf4f02985b5c405b0e5d7f92c8d25e015492ce19aa3b209295dce".into()], + difficulty: 357404825113208373, + difficulty_top64: 0, + height: 3167471, + length: 1, + main_chain_parent_block: "69b5075ea627d6ba06b1c30b7e023884eeaef5282cf58ec847dab838ddbcdd86".into(), + wide_difficulty: "0x4f5c1cb79e22635".into(), + }, + ChainInfo { + block_hash: "33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401".into(), + block_hashes: vec!["33ee476f5a1c5b9d889274cbbe171f5e0112df7ed69021918042525485deb401".into()], + difficulty: 354736121711617293, + difficulty_top64: 0, + height: 3157465, + length: 1, + main_chain_parent_block: "fd522fcc4cefe5c8c0e5c5600981b3151772c285df3a4e38e5c4011cf466d2cb".into(), + wide_difficulty: "0x4ec469f8b9ee50d".into(), + } + ], + } + )] + ResponseBase { + chains: Vec, + } +} + +define_request_and_response! { + relay_tx, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2361..=2381, + + RelayTx, + + #[doc = json_rpc_doc_test!( + RELAY_TX_REQUEST => RelayTxRequest { + txids: vec!["9fd75c429cbe52da9a52f2ffc5fbd107fe7fd2099c0d8de274dc8a67e0c98613".into()] + } + )] + Request { + txids: Vec, + }, + + #[doc = json_rpc_doc_test!( + RELAY_TX_RESPONSE => RelayTxResponse { + status: Status::Ok, + } + )] + #[repr(transparent)] + Response { + status: Status, + } +} + +define_request_and_response! { + sync_info, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2383..=2443, + + SyncInfo, + Request {}, + + #[doc = json_rpc_doc_test!( + SYNC_INFO_RESPONSE => SyncInfoResponse { + base: AccessResponseBase::ok(), + height: 3195157, + next_needed_pruning_seed: 0, + overview: "[]".into(), + spans: vec![], + peers: vec![ + SyncInfoPeer { + info: ConnectionInfo { + address: "142.93.128.65:44986".into(), + address_type: 1, + avg_download: 1, + avg_upload: 1, + connection_id: "a5803c4c2dac49e7b201dccdef54c862".into(), + current_download: 2, + current_upload: 1, + height: 3195157, + host: "142.93.128.65".into(), + incoming: true, + ip: "142.93.128.65".into(), + live_time: 18, + local_ip: false, + localhost: false, + peer_id: "6830e9764d3e5687".into(), + port: "44986".into(), + pruning_seed: 0, + recv_count: 20340, + recv_idle_time: 0, + rpc_credits_per_hash: 0, + rpc_port: 18089, + send_count: 32235, + send_idle_time: 6, + state: "normal".into(), + support_flags: 1 + } + }, + SyncInfoPeer { + info: ConnectionInfo { + address: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion:18083".into(), + address_type: 4, + avg_download: 0, + avg_upload: 0, + connection_id: "277f7c821bc546878c8bd29977e780f5".into(), + current_download: 0, + current_upload: 0, + height: 3195157, + host: "4iykytmumafy5kjahdqc7uzgcs34s2vwsadfjpk4znvsa5vmcxeup2qd.onion".into(), + incoming: false, + ip: "".into(), + live_time: 2246, + local_ip: false, + localhost: false, + peer_id: "0000000000000001".into(), + port: "".into(), + pruning_seed: 389, + recv_count: 65164, + recv_idle_time: 15, + rpc_credits_per_hash: 0, + rpc_port: 0, + send_count: 99120, + send_idle_time: 15, + state: "normal".into(), + support_flags: 0 + } + } + ], + target_height: 0, + } + )] + AccessResponseBase { + height: u64, + next_needed_pruning_seed: u32, + overview: String, + // FIXME: This is a `std::list` in `monerod` because...? + peers: Vec = default_vec::(), "default_vec", + // FIXME: This is a `std::list` in `monerod` because...? + spans: Vec = default_vec::(), "default_vec", + target_height: u64, + } +} + +define_request_and_response! { + get_txpool_backlog, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1637..=1664, + GetTransactionPoolBacklog, + Request {}, + + // TODO: enable test after binary string impl. + // #[doc = json_rpc_doc_test!( + // GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse { + // base: ResponseBase::ok(), + // backlog: "...Binary...".into(), + // } + // )] + ResponseBase { + // TODO: this is a [`BinaryString`]. + backlog: Vec, + } +} + +define_request_and_response! { + get_output_distribution, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2445..=2520, + + /// This type is also used in the (undocumented) + /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) + /// binary endpoint. + GetOutputDistribution, + + #[doc = json_rpc_doc_test!( + GET_OUTPUT_DISTRIBUTION_REQUEST => GetOutputDistributionRequest { + amounts: vec![628780000], + from_height: 1462078, + binary: true, + compress: false, + cumulative: false, + to_height: 0, + } + )] + Request { + amounts: Vec, + binary: bool = default_true(), "default_true", + compress: bool = default_false(), "default_false", + cumulative: bool = default_false(), "default_false", + from_height: u64 = default_zero::(), "default_zero", + to_height: u64 = default_zero::(), "default_zero", + }, + + #[doc = json_rpc_doc_test!( + GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { + base: AccessResponseBase::ok(), + distributions: vec![Distribution::Uncompressed { + start_height: 1462078, + base: 0, + distribution: "".into(), + amount: 2628780000, + binary: true, + }], + } + )] + AccessResponseBase { + distributions: Vec, + } +} + +define_request_and_response! { + get_miner_data, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 996..=1044, + GetMinerData, + Request {}, + + #[doc = json_rpc_doc_test!( + GET_MINER_DATA_RESPONSE => GetMinerDataResponse { + base: ResponseBase::ok(), + already_generated_coins: 18186022843595960691, + difficulty: "0x48afae42de".into(), + height: 2731375, + major_version: 16, + median_weight: 300000, + prev_id: "78d50c5894d187c4946d54410990ca59a75017628174a9e8c7055fa4ca5c7c6d".into(), + seed_hash: "a6b869d50eca3a43ec26fe4c369859cf36ae37ce6ecb76457d31ffeb8a6ca8a6".into(), + tx_backlog: vec![ + GetMinerDataTxBacklogEntry { + fee: 30700000, + id: "9868490d6bb9207fdd9cf17ca1f6c791b92ca97de0365855ea5c089f67c22208".into(), + weight: 1535 + }, + GetMinerDataTxBacklogEntry { + fee: 44280000, + id: "b6000b02bbec71e18ad704bcae09fb6e5ae86d897ced14a718753e76e86c0a0a".into(), + weight: 2214 + }, + ], + } + )] + ResponseBase { + major_version: u8, + height: u64, + prev_id: String, + seed_hash: String, + difficulty: String, + median_weight: u64, + already_generated_coins: u64, + tx_backlog: Vec, + } +} + +define_request_and_response! { + prune_blockchain, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2747..=2772, + + PruneBlockchain, + + #[derive(Copy)] + #[doc = json_rpc_doc_test!( + PRUNE_BLOCKCHAIN_REQUEST => PruneBlockchainRequest { + check: true + } + )] + Request { + check: bool = default_false(), "default_false", + }, + + #[doc = json_rpc_doc_test!( + PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse { + base: ResponseBase::ok(), + pruned: true, + pruning_seed: 387, + } + )] + ResponseBase { + pruned: bool, + pruning_seed: u32, + } +} + +define_request_and_response! { + calc_pow, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1046..=1066, + + CalcPow, + + #[doc = json_rpc_doc_test!( + CALC_POW_REQUEST => CalcPowRequest { + major_version: 14, + height: 2286447, + block_blob: "0e0ed286da8006ecdc1aab3033cf1716c52f13f9d8ae0051615a2453643de94643b550d543becd0000000002abc78b0101ffefc68b0101fcfcf0d4b422025014bb4a1eade6622fd781cb1063381cad396efa69719b41aa28b4fce8c7ad4b5f019ce1dc670456b24a5e03c2d9058a2df10fec779e2579753b1847b74ee644f16b023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051399a1bc46a846474f5b33db24eae173a26393b976054ee14f9feefe99925233802867097564c9db7a36af5bb5ed33ab46e63092bd8d32cef121608c3258edd55562812e21cc7e3ac73045745a72f7d74581d9a0849d6f30e8b2923171253e864f4e9ddea3acb5bc755f1c4a878130a70c26297540bc0b7a57affb6b35c1f03d8dbd54ece8457531f8cba15bb74516779c01193e212050423020e45aa2c15dcb".into(), + seed_hash: "d432f499205150873b2572b5f033c9c6e4b7c6f3394bd2dd93822cd7085e7307".into(), + } + )] + Request { + major_version: u8, + height: u64, + block_blob: String, + seed_hash: String, + }, + + #[doc = json_rpc_doc_test!( + CALC_POW_RESPONSE => CalcPowResponse { + pow_hash: "d0402d6834e26fb94a9ce38c6424d27d2069896a9b8b1ce685d79936bca6e0a8".into(), + } + )] + #[cfg_attr(feature = "serde", serde(transparent))] + #[repr(transparent)] + Response { + pow_hash: String, + } +} + +define_request_and_response! { + flush_cache, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 2774..=2796, + + FlushCache, + + #[derive(Copy)] + #[doc = json_rpc_doc_test!( + FLUSH_CACHE_REQUEST => FlushCacheRequest { + bad_txs: true, + bad_blocks: true + } + )] + Request { + bad_txs: bool = default_false(), "default_false", + bad_blocks: bool = default_false(), "default_false", + }, + + #[doc = json_rpc_doc_test!( + FLUSH_CACHE_RESPONSE => FlushCacheResponse { + base: ResponseBase::ok(), + } + )] + ResponseBase {} +} + +define_request_and_response! { + add_aux_pow, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1068..=1112, + + AddAuxPow, + + #[doc = json_rpc_doc_test!( + ADD_AUX_POW_REQUEST => AddAuxPowRequest { + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + aux_pow: vec![AuxPow { + id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(), + hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into() + }] + } + )] + Request { + blocktemplate_blob: String, + aux_pow: Vec, + }, + + #[doc = json_rpc_doc_test!( + ADD_AUX_POW_RESPONSE => AddAuxPowResponse { + base: ResponseBase::ok(), + aux_pow: vec![AuxPow { + hash: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(), + id: "3200b4ea97c3b2081cd4190b58e49572b2319fed00d030ad51809dff06b5d8c8".into(), + }], + blockhashing_blob: "1010ee97e2a106e9f8ebe8887e5b609949ac8ea6143e560ed13552b110cb009b21f0cfca1eaccf00000000b2685c1283a646bc9020c758daa443be145b7370ce5a6efacb3e614117032e2c22".into(), + blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), + merkle_root: "7b35762de164b20885e15dbe656b1138db06bb402fa1796f5765a23933d8859a".into(), + merkle_tree_depth: 0, + } + )] + ResponseBase { + blocktemplate_blob: String, + blockhashing_blob: String, + merkle_root: String, + merkle_tree_depth: u64, + aux_pow: Vec, + } +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/types/src/misc/distribution.rs b/rpc/types/src/misc/distribution.rs index 29fee38fe..dc265d814 100644 --- a/rpc/types/src/misc/distribution.rs +++ b/rpc/types/src/misc/distribution.rs @@ -3,6 +3,8 @@ //---------------------------------------------------------------------------------------------------- Use use std::mem::size_of; +#[cfg(feature = "serde")] +use crate::defaults::default_true; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -65,6 +67,60 @@ fn decompress_integer_array(array: Vec) -> Vec { /// Upon deserialization, the presence of a `compressed_data` /// field signifies that the [`Self::CompressedBinary`] should /// be selected. +/// +/// # JSON Example +/// ```rust +/// use cuprate_rpc_types::misc::Distribution; +/// use serde_json::*; +/// +/// // Uncompressed. +/// let json = json!({ +/// "amount": 2628780000_u64, +/// "base": 0_u64, +/// "distribution": "asdf", +/// "start_height": 1462078_u64 +/// }); +/// let d = from_value::(json).unwrap(); +/// match d { +/// Distribution::Uncompressed { +/// start_height, +/// base, +/// distribution, +/// amount, +/// binary, +/// } => { +/// assert_eq!(start_height, 1462078); +/// assert_eq!(base, 0); +/// assert_eq!(distribution, "asdf"); +/// assert_eq!(amount, 2628780000); +/// assert_eq!(binary, true); +/// }, +/// Distribution::CompressedBinary { .. } => unreachable!(), +/// } +/// +/// // Compressed binary. +/// let json = json!({ +/// "amount": 2628780000_u64, +/// "base": 0_u64, +/// "compressed_data": "asdf", +/// "start_height": 1462078_u64 +/// }); +/// let d = from_value::(json).unwrap(); +/// match d { +/// Distribution::CompressedBinary { +/// start_height, +/// base, +/// compressed_data, +/// amount, +/// } => { +/// assert_eq!(start_height, 1462078); +/// assert_eq!(base, 0); +/// assert_eq!(compressed_data, "asdf"); +/// assert_eq!(amount, 2628780000); +/// }, +/// Distribution::Uncompressed { .. } => unreachable!(), +/// } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] @@ -77,8 +133,9 @@ pub enum Distribution { /// /// Considering both the `binary` field and `/get_output_distribution.bin` /// endpoint are undocumented in the first place, Cuprate could just drop support for this. - distribution: Vec, + distribution: String, amount: u64, + #[cfg_attr(feature = "serde", serde(default = "default_true"))] binary: bool, }, /// Distribution data will be (de)serialized as compressed binary. @@ -95,7 +152,7 @@ impl Default for Distribution { Self::Uncompressed { start_height: u64::default(), base: u64::default(), - distribution: Vec::::default(), + distribution: String::default(), amount: u64::default(), binary: false, } @@ -113,7 +170,7 @@ impl Default for Distribution { pub struct __DistributionEpeeBuilder { pub start_height: Option, pub base: Option, - pub distribution: Option>, + pub distribution: Option, pub amount: Option, pub compressed_data: Option, pub binary: Option, diff --git a/rpc/types/src/misc/misc.rs b/rpc/types/src/misc/misc.rs index 4643ecc59..2b31cabf3 100644 --- a/rpc/types/src/misc/misc.rs +++ b/rpc/types/src/misc/misc.rs @@ -22,7 +22,7 @@ use crate::{ CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK, CORE_RPC_STATUS_PAYMENT_REQUIRED, }, - defaults::default_zero, + defaults::{default_string, default_zero}, macros::monero_definition_link, }; @@ -51,9 +51,9 @@ macro_rules! define_struct_and_impl_epee { )* } ) => { - $( #[$struct_attr] )* #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + $( #[$struct_attr] )* pub struct $struct_name { $( $( #[$field_attr] )* @@ -142,7 +142,9 @@ define_struct_and_impl_epee! { rpc_port: u16, send_count: u64, send_idle_time: u64, - ssl: bool, + // Exists in the original definition, but isn't + // used or (de)serialized for RPC purposes. + // ssl: bool, state: String, support_flags: u32, } @@ -156,7 +158,9 @@ define_struct_and_impl_epee! { )] /// Used in [`crate::json::SetBansRequest`]. SetBan { + #[cfg_attr(feature = "serde", serde(default = "default_string"))] host: String, + #[cfg_attr(feature = "serde", serde(default = "default_zero"))] ip: u32, ban: bool, seconds: u32, diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index 2463e4563..05df19541 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -771,7 +771,7 @@ r#"{ "id": "0", "method": "get_output_histogram", "params": { - "amounts": ["20000000000"] + "amounts": [20000000000] } }"#; Response = @@ -1106,13 +1106,16 @@ r#"{ "id": "0", "jsonrpc": "2.0", "result": { + "credits": 0, "distributions": [{ "amount": 2628780000, "base": 0, "distribution": "", "start_height": 1462078 }], - "status": "OK" + "status": "OK", + "top_hash": "", + "untrusted": false } }"#; } From ad9e1c1676efc514682c4ee8b67d4d004cb19455 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 19 Jul 2024 20:50:56 -0400 Subject: [PATCH 51/93] add all other JSON doc-tests --- rpc/types/src/macros.rs | 54 +++- rpc/types/src/other.rs | 513 +++++++++++++++++++++++++++++-- test-utils/src/rpc/data/other.rs | 43 ++- 3 files changed, 569 insertions(+), 41 deletions(-) diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index e001d183b..c7e79a3cb 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -372,7 +372,8 @@ macro_rules! monero_definition_link { } pub(crate) use monero_definition_link; -//---------------------------------------------------------------------------------------------------- Macro +//---------------------------------------------------------------------------------------------------- json_rpc_doc_test +/// TODO macro_rules! json_rpc_doc_test { ( $cuprate_test_utils_rpc_const:ident => $expected:expr @@ -418,3 +419,54 @@ macro_rules! json_rpc_doc_test { }; } pub(crate) use json_rpc_doc_test; + +//---------------------------------------------------------------------------------------------------- json_other_doc_test +/// TODO +macro_rules! json_other_doc_test { + // TODO + ($cuprate_test_utils_rpc_const:ident) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "```\n", + ) + } + }; + // TODO + ( + $cuprate_test_utils_rpc_const:ident => $expected:expr + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "\n", + "assert_eq!(string, expected);\n", + "```\n", + ) + } + }; +} +pub(crate) use json_other_doc_test; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 41530cba1..902963bb9 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -5,8 +5,8 @@ //---------------------------------------------------------------------------------------------------- Import use crate::{ base::{AccessResponseBase, ResponseBase}, - defaults::{default_false, default_string, default_true}, - macros::define_request_and_response, + defaults::{default_false, default_string, default_true, default_vec, default_zero}, + macros::{define_request_and_response, json_other_doc_test}, misc::{ GetOutputsOut, KeyImageSpentStatus, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, TxpoolStats, @@ -20,6 +20,14 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 138..=160, GetHeight, Request {}, + + #[doc = json_other_doc_test!( + GET_HEIGHT_RESPONSE => GetHeightResponse { + base: ResponseBase::ok(), + hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(), + height: 3195160, + } + )] ResponseBase { hash: String, height: u64, @@ -31,6 +39,15 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 370..=451, GetTransactions, + + #[doc = json_other_doc_test!( + GET_TRANSACTIONS_REQUEST => GetTransactionsRequest { + txs_hashes: vec!["d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408".into()], + decode_as_json: false, + prune: false, + split: false, + } + )] Request { txs_hashes: Vec, // FIXME: this is documented as optional but it isn't serialized as an optional @@ -40,11 +57,13 @@ define_request_and_response! { prune: bool = default_false(), "default_false", split: bool = default_false(), "default_false", }, + + #[doc = json_other_doc_test!(GET_TRANSACTIONS_RESPONSE)] AccessResponseBase { - txs_as_hex: Vec, - txs_as_json: Vec, - missed_tx: Vec, - txs: Vec, + txs_as_hex: Vec = default_vec::(), "default_vec", + txs_as_json: Vec = default_vec::(), "default_vec", + missed_tx: Vec = default_vec::(), "default_vec", + txs: Vec = default_vec::(), "default_vec", } } @@ -54,6 +73,13 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 288..=308, GetAltBlocksHashes, Request {}, + + #[doc = json_other_doc_test!( + GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse { + base: AccessResponseBase::ok(), + blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()], + } + )] AccessResponseBase { blks_hashes: Vec, } @@ -63,10 +89,27 @@ define_request_and_response! { is_key_image_spent, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 454..=484, + IsKeyImageSpent, + + #[doc = json_other_doc_test!( + IS_KEY_IMAGE_SPENT_REQUEST => IsKeyImageSpentRequest { + key_images: vec![ + "8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3".into(), + "7319134bfc50668251f5b899c66b005805ee255c136f0e1cecbb0f3a912e09d4".into() + ] + } + )] Request { key_images: Vec, }, + + #[doc = json_other_doc_test!( + IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse { + base: AccessResponseBase::ok(), + spent_status: vec![1, 1], + } + )] AccessResponseBase { /// FIXME: These are [`KeyImageSpentStatus`] in [`u8`] form. spent_status: Vec, @@ -77,19 +120,54 @@ define_request_and_response! { send_raw_transaction, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 370..=451, + SendRawTransaction, + + #[doc = json_other_doc_test!( + SEND_RAW_TRANSACTION_REQUEST => SendRawTransactionRequest { + tx_as_hex: "dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into(), + do_not_relay: false, + do_sanity_checks: true, + } + )] Request { tx_as_hex: String, do_not_relay: bool = default_false(), "default_false", do_sanity_checks: bool = default_true(), "default_true", }, + + #[doc = json_other_doc_test!( + SEND_RAW_TRANSACTION_RESPONSE => SendRawTransactionResponse { + base: AccessResponseBase { + response_base: ResponseBase { + status: Status::Other("Failed".into()), + untrusted: false, + }, + credits: 0, + top_hash: "".into(), + }, + double_spend: false, + fee_too_low: false, + invalid_input: false, + invalid_output: false, + low_mixin: false, + not_relayed: false, + overspend: false, + reason: "".into(), + sanity_check_failed: false, + too_big: false, + too_few_outputs: false, + tx_extra_too_big: false, + nonzero_unlock_time: false, + } + )] AccessResponseBase { double_spend: bool, fee_too_low: bool, invalid_input: bool, invalid_output: bool, low_mixin: bool, - nonzero_unlock_time: bool, + nonzero_unlock_time: bool = default_false(), "default_false", not_relayed: bool, overspend: bool, reason: String, @@ -104,13 +182,29 @@ define_request_and_response! { start_mining, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 665..=691, + StartMining, + + #[doc = json_other_doc_test!( + START_MINING_REQUEST => StartMiningRequest { + do_background_mining: false, + ignore_battery: true, + miner_address: "47xu3gQpF569au9C2ajo5SSMrWji6xnoE5vhr94EzFRaKAGw6hEGFXYAwVADKuRpzsjiU1PtmaVgcjUJF89ghGPhUXkndHc".into(), + threads_count: 1 + } + )] Request { miner_address: String, threads_count: u64, do_background_mining: bool, ignore_battery: bool, }, + + #[doc = json_other_doc_test!( + START_MINING_RESPONSE => StartMiningResponse { + base: ResponseBase::ok(), + } + )] ResponseBase {} } @@ -120,6 +214,12 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 825..=843, StopMining, Request {}, + + #[doc = json_other_doc_test!( + STOP_MINING_RESPONSE => StopMiningResponse { + base: ResponseBase::ok(), + } + )] ResponseBase {} } @@ -129,6 +229,27 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 846..=895, MiningStatus, Request {}, + + #[doc = json_other_doc_test!( + MINING_STATUS_RESPONSE => MiningStatusResponse { + base: ResponseBase::ok(), + active: false, + address: "".into(), + bg_idle_threshold: 0, + bg_ignore_battery: false, + bg_min_idle_seconds: 0, + bg_target: 0, + block_reward: 0, + block_target: 120, + difficulty: 292022797663, + difficulty_top64: 0, + is_background_mining_enabled: false, + pow_algorithm: "RandomX".into(), + speed: 0, + threads_count: 0, + wide_difficulty: "0x43fdea455f".into(), + } + )] ResponseBase { active: bool, address: String, @@ -154,6 +275,12 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 898..=916, SaveBc, Request {}, + + #[doc = json_other_doc_test!( + SAVE_BC_RESPONSE => SaveBcResponse { + base: ResponseBase::ok(), + } + )] ResponseBase {} } @@ -161,11 +288,79 @@ define_request_and_response! { get_peer_list, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1369..=1417, + GetPeerList, + + #[doc = json_other_doc_test!( + GET_PEER_LIST_REQUEST => GetPeerListRequest { + public_only: true, + include_blocked: false, + } + )] Request { public_only: bool = default_true(), "default_true", include_blocked: bool = default_false(), "default_false", }, + + #[doc = json_other_doc_test!( + GET_PEER_LIST_RESPONSE => GetPeerListResponse { + base: ResponseBase::ok(), + gray_list: vec![ + Peer { + host: "161.97.193.0".into(), + id: 18269586253849566614, + ip: 12673441, + last_seen: 0, + port: 18080, + rpc_port: 0, + rpc_credits_per_hash: 0, + pruning_seed: 0, + }, + Peer { + host: "193.142.4.2".into(), + id: 10865563782170056467, + ip: 33853121, + last_seen: 0, + port: 18085, + pruning_seed: 387, + rpc_port: 19085, + rpc_credits_per_hash: 0, + } + ], + white_list: vec![ + Peer { + host: "78.27.98.0".into(), + id: 11368279936682035606, + ip: 6429518, + last_seen: 1721246387, + port: 18080, + pruning_seed: 384, + rpc_port: 0, + rpc_credits_per_hash: 0, + }, + Peer { + host: "67.4.163.2".into(), + id: 16545113262826842499, + ip: 44237891, + last_seen: 1721246387, + port: 18080, + rpc_port: 0, + rpc_credits_per_hash: 0, + pruning_seed: 0, + }, + Peer { + host: "70.52.75.3".into(), + id: 3863337548778177169, + ip: 55260230, + last_seen: 1721246387, + port: 18080, + rpc_port: 18081, + rpc_credits_per_hash: 0, + pruning_seed: 0, + } + ] + } + )] ResponseBase { white_list: Vec, gray_list: Vec, @@ -177,10 +372,22 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1450..=1470, SetLogHashRate, + #[derive(Copy)] + #[doc = json_other_doc_test!( + SET_LOG_HASH_RATE_REQUEST => SetLogHashRateRequest { + visible: true, + } + )] Request { - visible: bool, + visible: bool = default_false(), "default_false", }, + + #[doc = json_other_doc_test!( + SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse { + base: ResponseBase::ok(), + } + )] ResponseBase {} } @@ -188,11 +395,24 @@ define_request_and_response! { set_log_level, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1450..=1470, + SetLogLevel, + #[derive(Copy)] + #[doc = json_other_doc_test!( + SET_LOG_LEVEL_REQUEST => SetLogLevelRequest { + level: 1 + } + )] Request { level: u8, }, + + #[doc = json_other_doc_test!( + SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse { + base: ResponseBase::ok(), + } + )] ResponseBase {} } @@ -200,10 +420,24 @@ define_request_and_response! { set_log_categories, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1494..=1517, + SetLogCategories, + + #[doc = json_other_doc_test!( + SET_LOG_CATEGORIES_REQUEST => SetLogCategoriesRequest { + categories: "*:INFO".into(), + } + )] Request { categories: String = default_string(), "default_string", }, + + #[doc = json_other_doc_test!( + SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse { + base: ResponseBase::ok(), + categories: "*:INFO".into(), + } + )] ResponseBase { categories: String, } @@ -213,13 +447,29 @@ define_request_and_response! { set_bootstrap_daemon, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1785..=1812, + SetBootstrapDaemon, + + #[doc = json_other_doc_test!( + SET_BOOTSTRAP_DAEMON_REQUEST => SetBootstrapDaemonRequest { + address: "http://getmonero.org:18081".into(), + username: String::new(), + password: String::new(), + proxy: String::new(), + } + )] Request { address: String, - username: String, - password: String, - proxy: String, + username: String = default_string(), "default_string", + password: String = default_string(), "default_string", + proxy: String = default_string(), "default_string", }, + + #[doc = json_other_doc_test!( + SET_BOOTSTRAP_DAEMON_RESPONSE => SetBootstrapDaemonResponse { + status: Status::Ok, + } + )] Response { status: Status, } @@ -229,8 +479,11 @@ define_request_and_response! { get_transaction_pool, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1569..=1591, + GetTransactionPool, Request {}, + + #[doc = json_other_doc_test!(GET_TRANSACTION_POOL_RESPONSE)] AccessResponseBase { transactions: Vec, spent_key_images: Vec, @@ -241,8 +494,41 @@ define_request_and_response! { get_transaction_pool_stats, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1712..=1732, + GetTransactionPoolStats, Request {}, + + #[doc = json_other_doc_test!( + GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse { + base: AccessResponseBase::ok(), + pool_stats: TxpoolStats { + bytes_max: 11843, + bytes_med: 2219, + bytes_min: 1528, + bytes_total: 144192, + fee_total: 7018100000, + histo: vec![ + TxpoolHisto { bytes: 11219, txs: 4 }, + TxpoolHisto { bytes: 9737, txs: 5 }, + TxpoolHisto { bytes: 8757, txs: 4 }, + TxpoolHisto { bytes: 14763, txs: 4 }, + TxpoolHisto { bytes: 15007, txs: 6 }, + TxpoolHisto { bytes: 15924, txs: 6 }, + TxpoolHisto { bytes: 17869, txs: 8 }, + TxpoolHisto { bytes: 10894, txs: 5 }, + TxpoolHisto { bytes: 38485, txs: 10 }, + TxpoolHisto { bytes: 1537, txs: 1 }, + ], + histo_98pc: 186, + num_10m: 0, + num_double_spends: 0, + num_failing: 0, + num_not_relayed: 0, + oldest: 1721261651, + txs_total: 53 + } + } + )] AccessResponseBase { pool_stats: TxpoolStats, } @@ -252,9 +538,16 @@ define_request_and_response! { stop_daemon, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1814..=1831, + StopDaemon, Request {}, - ResponseBase { + + #[doc = json_other_doc_test!( + STOP_DAEMON_RESPONSE => StopDaemonResponse { + status: Status::Ok, + } + )] + Response { status: Status, } } @@ -263,8 +556,17 @@ define_request_and_response! { get_limit, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1852..=1874, + GetLimit, Request {}, + + #[doc = json_other_doc_test!( + GET_LIMIT_RESPONSE => GetLimitResponse { + base: ResponseBase::ok(), + limit_down: 1280000, + limit_up: 1280000, + } + )] ResponseBase { limit_down: u64, limit_up: u64, @@ -275,11 +577,27 @@ define_request_and_response! { set_limit, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1876..=1903, + SetLimit, + #[doc = json_other_doc_test!( + SET_LIMIT_REQUEST => SetLimitRequest { + limit_down: 1024, + limit_up: 0, + } + )] Request { - limit_down: i64, - limit_up: i64, + // FIXME: These may need to be `Option`. + limit_down: i64 = default_zero::(), "default_zero", + limit_up: i64 = default_zero::(), "default_zero", }, + + #[doc = json_other_doc_test!( + SET_LIMIT_RESPONSE => SetLimitResponse { + base: ResponseBase::ok(), + limit_down: 1024, + limit_up: 128, + } + )] ResponseBase { limit_down: i64, limit_up: i64, @@ -290,11 +608,26 @@ define_request_and_response! { out_peers, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1876..=1903, + OutPeers, + + #[doc = json_other_doc_test!( + OUT_PEERS_REQUEST => OutPeersRequest { + out_peers: 3232235535, + set: true, + } + )] Request { set: bool = default_true(), "default_true", out_peers: u32, }, + + #[doc = json_other_doc_test!( + OUT_PEERS_RESPONSE => OutPeersResponse { + base: ResponseBase::ok(), + out_peers: 3232235535, + } + )] ResponseBase { out_peers: u32, } @@ -304,8 +637,20 @@ define_request_and_response! { get_net_stats, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 793..=822, + GetNetStats, Request {}, + + #[doc = json_other_doc_test!( + GET_NET_STATS_RESPONSE => GetNetStatsResponse { + base: ResponseBase::ok(), + start_time: 1721251858, + total_bytes_in: 16283817214, + total_bytes_out: 34225244079, + total_packets_in: 5981922, + total_packets_out: 3627107, + } + )] ResponseBase { start_time: u64, total_packets_in: u64, @@ -319,11 +664,43 @@ define_request_and_response! { get_outs, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 567..=609, + GetOuts, + #[doc = json_other_doc_test!( + GET_OUTS_REQUEST => GetOutsRequest { + outputs: vec![ + GetOutputsOut { amount: 1, index: 0 }, + GetOutputsOut { amount: 1, index: 1 }, + ], + get_txid: true + } + )] Request { outputs: Vec, get_txid: bool, }, + + #[doc = json_other_doc_test!( + GET_OUTS_RESPONSE => GetOutsResponse { + base: ResponseBase::ok(), + outs: vec![ + OutKey { + height: 51941, + key: "08980d939ec297dd597119f498ad69fed9ca55e3a68f29f2782aae887ef0cf8e".into(), + mask: "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522".into(), + txid: "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601".into(), + unlocked: true + }, + OutKey { + height: 51945, + key: "454fe46c405be77625fa7e3389a04d3be392346983f27603561ac3a3a74f4a75".into(), + mask: "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522".into(), + txid: "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0".into(), + unlocked: true + }, + ] + } + )] ResponseBase { outs: Vec, } @@ -333,11 +710,31 @@ define_request_and_response! { update, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2324..=2359, + Update, + + #[doc = json_other_doc_test!( + UPDATE_REQUEST => UpdateRequest { + command: "check".into(), + path: "".into(), + } + )] Request { command: String, path: String = default_string(), "default_string", }, + + #[doc = json_other_doc_test!( + UPDATE_RESPONSE => UpdateResponse { + base: ResponseBase::ok(), + auto_uri: "".into(), + hash: "".into(), + path: "".into(), + update: false, + user_uri: "".into(), + version: "".into(), + } + )] ResponseBase { auto_uri: String, hash: String, @@ -352,26 +749,26 @@ define_request_and_response! { pop_blocks, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2722..=2745, + PopBlocks, + + #[doc = json_other_doc_test!( + POP_BLOCKS_REQUEST => PopBlocksRequest { + nblocks: 6 + } + )] Request { nblocks: u64, }, - ResponseBase { - height: u64, - } -} -define_request_and_response! { - UNDOCUMENTED_ENDPOINT, - cc73fe71162d564ffda8e549b79a350bca53c454 => - core_rpc_server_commands_defs.h => 2798..=2823, - GetTxIdsLoose, - Request { - txid_template: String, - num_matching_bits: u32, - }, + #[doc = json_other_doc_test!( + POP_BLOCKS_RESPONSE => PopBlocksResponse { + base: ResponseBase::ok(), + height: 76482, + } + )] ResponseBase { - txids: Vec, + height: u64, } } @@ -379,8 +776,35 @@ define_request_and_response! { UNDOCUMENTED_ENDPOINT, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1615..=1635, + GetTransactionPoolHashes, Request {}, + + #[doc = json_other_doc_test!( + GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse { + base: ResponseBase::ok(), + tx_hashes: vec![ + "aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03".into(), + "794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11".into(), + "1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17".into(), + "7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b".into(), + "2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329".into(), + "eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f".into(), + "59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236".into(), + "0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655".into(), + "60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062".into(), + "661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d".into(), + "b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e".into(), + "974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f".into(), + "d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb".into(), + "3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb".into(), + "8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da".into(), + "11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df".into(), + "b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1".into(), + "ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee".into() + ], + } + )] ResponseBase { tx_hashes: Vec, } @@ -391,14 +815,43 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1419..=1448, GetPublicNodes, + + #[doc = json_other_doc_test!( + GET_PUBLIC_NODES_REQUEST => GetPublicNodesRequest { + gray: false, + white: true, + include_blocked: false, + } + )] Request { gray: bool = default_false(), "default_false", white: bool = default_true(), "default_true", include_blocked: bool = default_false(), "default_false", }, + + #[doc = json_other_doc_test!( + GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse { + base: ResponseBase::ok(), + gray: vec![], + white: vec![ + PublicNode { + host: "70.52.75.3".into(), + last_seen: 1721246387, + rpc_credits_per_hash: 0, + rpc_port: 18081, + }, + PublicNode { + host: "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083".into(), + last_seen: 1720186288, + rpc_credits_per_hash: 0, + rpc_port: 18089, + } + ] + } + )] ResponseBase { - gray: Vec, - white: Vec, + gray: Vec = default_vec::(), "default_vec", + white: Vec = default_vec::(), "default_vec", } } diff --git a/test-utils/src/rpc/data/other.rs b/test-utils/src/rpc/data/other.rs index 2559bbe34..80a48ab18 100644 --- a/test-utils/src/rpc/data/other.rs +++ b/test-utils/src/rpc/data/other.rs @@ -234,11 +234,13 @@ define_request_and_response! { set_log_hash_rate (other), SET_LOG_HASH_RATE: &str, Request = -r#"{}"#; +r#"{ + "visible": true +}"#; Response = r#" { - "status": "OK" + "status": "OK", "untrusted": false }"#; } @@ -252,7 +254,7 @@ r#"{ }"#; Response = r#"{ - "status": "OK" + "status": "OK", "untrusted": false }"#; } @@ -673,7 +675,7 @@ r#"{ "limit_down": 1280000, "limit_up": 1280000, "status": "OK", - "untrusted": true + "untrusted": false }"#; } @@ -688,7 +690,7 @@ r#"{ r#"{ "limit_down": 1024, "limit_up": 128, - "status": "OK" + "status": "OK", "untrusted": false }"#; } @@ -712,13 +714,15 @@ define_request_and_response! { get_net_stats (other), GET_NET_STATS: &str, Request = -r#"{ - "in_peers": 3232235535 -}"#; +r#"{}"#; Response = r#"{ - "in_peers": 3232235535, + "start_time": 1721251858, "status": "OK", + "total_bytes_in": 16283817214, + "total_bytes_out": 34225244079, + "total_packets_in": 5981922, + "total_packets_out": 3627107, "untrusted": false }"#; } @@ -804,7 +808,26 @@ r#"{ "credits": 0, "status": "OK", "top_hash": "", - "tx_hashes": ["aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03","794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11","1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17","7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b","2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329","eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f","59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236","0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655","60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062","661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d","b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e","974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f","d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb","3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb","8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da","11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df","b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1","ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee"], + "tx_hashes": [ + "aa928aed888acd6152c60194d50a4df29b0b851be6169acf11b6a8e304dd6c03", + "794345f321a98f3135151f3056c0fdf8188646a8dab27de971428acf3551dd11", + "1e9d2ae11f2168a228942077483e70940d34e8658c972bbc3e7f7693b90edf17", + "7375c928f261d00f07197775eb0bfa756e5f23319819152faa0b3c670fe54c1b", + "2e4d5f8c5a45498f37fb8b6ca4ebc1efa0c371c38c901c77e66b08c072287329", + "eee6d596cf855adfb10e1597d2018e3a61897ac467ef1d4a5406b8d20bfbd52f", + "59c574d7ba9bb4558470f74503c7518946a85ea22c60fccfbdec108ce7d8f236", + "0d57bec1e1075a9e1ac45cf3b3ced1ad95ccdf2a50ce360190111282a0178655", + "60d627b2369714a40009c07d6185ebe7fa4af324fdfa8d95a37a936eb878d062", + "661d7e728a901a8cb4cf851447d9cd5752462687ed0b776b605ba706f06bdc7d", + "b80e1f09442b00b3fffe6db5d263be6267c7586620afff8112d5a8775a6fc58e", + "974063906d1ddfa914baf85176b0f689d616d23f3d71ed4798458c8b4f9b9d8f", + "d2575ae152a180be4981a9d2fc009afcd073adaa5c6d8b022c540a62d6c905bb", + "3d78aa80ee50f506683bab9f02855eb10257a08adceda7cbfbdfc26b10f6b1bb", + "8b5bc125bdb73b708500f734501d55088c5ac381a0879e1141634eaa72b6a4da", + "11c06f4d2f00c912ca07313ed2ea5366f3cae914a762bed258731d3d9e3706df", + "b3644dc7c9a3a53465fe80ad3769e516edaaeb7835e16fdd493aac110d472ae1", + "ed2478ad793b923dbf652c8612c40799d764e5468897021234a14a37346bc6ee" + ], "untrusted": false }"#; } From 1f8f5a947d34bb4b54a47a03edd3b1590f479871 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 21 Jul 2024 15:41:45 -0400 Subject: [PATCH 52/93] move doc-test macros to files --- rpc/types/src/json.rs | 167 ++++++++++++++++++++++++++++------------ rpc/types/src/macros.rs | 99 ------------------------ rpc/types/src/other.rs | 159 ++++++++++++++++++++++++++++---------- 3 files changed, 233 insertions(+), 192 deletions(-) diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index ebca19e15..74ccb9a0e 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -10,7 +10,7 @@ use crate::{ default_zero, }, free::{is_one, is_zero}, - macros::{define_request_and_response, json_rpc_doc_test}, + macros::define_request_and_response, misc::{ AuxPow, BlockHeader, ChainInfo, ConnectionInfo, Distribution, GetBan, GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, @@ -18,8 +18,66 @@ use crate::{ }, }; -//---------------------------------------------------------------------------------------------------- Definitions +//---------------------------------------------------------------------------------------------------- Macro +/// Adds a (de)serialization doc-test to a type in `json.rs`. +/// +/// It expects a const string from `cuprate_test_utils::rpc::data` +/// and the expected value it should (de)serialize into/from. +/// +/// It tests that the provided const JSON string can properly +/// (de)serialize into the expected value. +/// +/// See below for example usage. This macro is only used in this file. +macro_rules! serde_doc_test { + ( + // `const` string from `cuprate_test_utils::rpc::data` + // v + $cuprate_test_utils_rpc_const:ident => $expected:expr + // ^ + // Expected value as an expression + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::json::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, json::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "// Assert it can be turned into a JSON value.\n", + "let value = from_str::(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "let Value::Object(map) = value else {\n", + " panic!();\n", + "};\n", + "\n", + "// If a request...\n", + "if let Some(params) = map.get(\"params\") {\n", + " let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(params.clone()).unwrap();\n", + " assert_eq!(response, expected);\n", + " return;\n", + "}\n", + "\n", + "// Else, if a response...\n", + "let result = map.get(\"result\").unwrap().clone();\n", + "let response = from_value::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(result.clone()).unwrap();\n", + "assert_eq!(response, expected);\n", + "```\n", + ) + } + }; +} +//---------------------------------------------------------------------------------------------------- Definitions // This generates 2 structs: // // - `GetBlockTemplateRequest` @@ -47,7 +105,14 @@ define_request_and_response! { // If there are any additional attributes (`/// docs` or `#[derive]`s) // for the struct, they go here, e.g.: // - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( + // ^ This is a macro that adds a doc-test to this type. + // It is optional but it is added to nearly all types. + // The syntax is: + // `$const` => `$expected` + // where `$const` is a `const` string from + // `cuprate_test_utils::rpc::data` and `$expected` is an + // actual expression that the string _should_ (de)serialize into/from. GET_BLOCK_TEMPLATE_REQUEST => GetBlockTemplateRequest { extra_nonce: String::default(), prev_block: String::default(), @@ -104,7 +169,7 @@ define_request_and_response! { // "Flatten" means the field(s) of a struct gets inlined // directly into the struct during (de)serialization, see: // . - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_TEMPLATE_RESPONSE => GetBlockTemplateResponse { base: ResponseBase::ok(), blockhashing_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a00000000e0c20372be23d356347091025c5b5e8f2abf83ab618378565cce2b703491523401".into(), @@ -160,7 +225,7 @@ define_request_and_response! { // type alias to `()` instead of a `struct`. Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_COUNT_RESPONSE => GetBlockCountResponse { base: ResponseBase::ok(), count: 3195019, @@ -178,7 +243,7 @@ define_request_and_response! { OnGetBlockHash, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( ON_GET_BLOCK_HASH_REQUEST => OnGetBlockHashRequest { block_height: [912345], } @@ -192,7 +257,7 @@ define_request_and_response! { block_height: [u64; 1], }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( ON_GET_BLOCK_HASH_RESPONSE => OnGetBlockHashResponse { block_hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), } @@ -211,7 +276,7 @@ define_request_and_response! { SubmitBlock, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( SUBMIT_BLOCK_REQUEST => SubmitBlockRequest { block_blob: ["0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000".into()], } @@ -237,7 +302,7 @@ define_request_and_response! { GenerateBlocks, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GENERATE_BLOCKS_REQUEST => GenerateBlocksRequest { amount_of_blocks: 1, prev_block: String::default(), @@ -252,7 +317,7 @@ define_request_and_response! { wallet_address: String, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GENERATE_BLOCKS_RESPONSE => GenerateBlocksResponse { base: ResponseBase::ok(), blocks: vec!["49b712db7760e3728586f8434ee8bc8d7b3d410dac6bb6e98bf5845c83b917e4".into()], @@ -277,7 +342,7 @@ define_request_and_response! { fill_pow_hash: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_LAST_BLOCK_HEADER_RESPONSE => GetLastBlockHeaderResponse { base: AccessResponseBase::ok(), block_header: BlockHeader { @@ -316,7 +381,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1240..=1269, GetBlockHeaderByHash, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADER_BY_HASH_REQUEST => GetBlockHeaderByHashRequest { hash: "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6".into(), hashes: vec![], @@ -329,7 +394,7 @@ define_request_and_response! { fill_pow_hash: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADER_BY_HASH_RESPONSE => GetBlockHeaderByHashResponse { base: AccessResponseBase::ok(), block_headers: vec![], @@ -373,7 +438,7 @@ define_request_and_response! { GetBlockHeaderByHeight, #[derive(Copy)] - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADER_BY_HEIGHT_REQUEST => GetBlockHeaderByHeightRequest { height: 912345, fill_pow_hash: false, @@ -384,7 +449,7 @@ define_request_and_response! { fill_pow_hash: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADER_BY_HEIGHT_RESPONSE => GetBlockHeaderByHeightResponse { base: AccessResponseBase::ok(), block_header: BlockHeader { @@ -426,7 +491,7 @@ define_request_and_response! { GetBlockHeadersRange, #[derive(Copy)] - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADERS_RANGE_REQUEST => GetBlockHeadersRangeRequest { start_height: 1545999, end_height: 1546000, @@ -439,7 +504,7 @@ define_request_and_response! { fill_pow_hash: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_HEADERS_RANGE_RESPONSE => GetBlockHeadersRangeResponse { base: AccessResponseBase::ok(), headers: vec![ @@ -505,7 +570,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1298..=1313, GetBlock, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_REQUEST => GetBlockRequest { height: 2751506, hash: String::default(), @@ -521,7 +586,7 @@ define_request_and_response! { fill_pow_hash: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BLOCK_RESPONSE => GetBlockResponse { base: AccessResponseBase::ok(), blob: "1010c58bab9b06b27bdecfc6cd0a46172d136c08831cf67660377ba992332363228b1b722781e7807e07f502cef8a70101ff92f8a7010180e0a596bb1103d7cbf826b665d7a532c316982dc8dbc24f285cbc18bbcc27c7164cd9b3277a85d034019f629d8b36bd16a2bfce3ea80c31dc4d8762c67165aec21845494e32b7582fe00211000000297a787a000000000000000000000000".into(), @@ -572,7 +637,7 @@ define_request_and_response! { Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_CONNECTIONS_RESPONSE => GetConnectionsResponse { base: ResponseBase::ok(), connections: vec![ @@ -646,7 +711,7 @@ define_request_and_response! { GetInfo, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_INFO_RESPONSE => GetInfoResponse { base: AccessResponseBase::ok(), adjusted_time: 1721245289, @@ -740,7 +805,7 @@ define_request_and_response! { HardForkInfo, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( HARD_FORK_INFO_RESPONSE => HardForkInfoResponse { base: AccessResponseBase::ok(), earliest_height: 2689608, @@ -771,7 +836,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 2032..=2067, SetBans, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( SET_BANS_REQUEST => SetBansRequest { bans: vec![ SetBan { host: "192.168.1.51".into(), @@ -785,7 +850,7 @@ define_request_and_response! { bans: Vec, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( SET_BANS_RESPONSE => SetBansResponse { base: ResponseBase::ok(), } @@ -800,7 +865,7 @@ define_request_and_response! { GetBans, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_BANS_RESPONSE => GetBansResponse { base: ResponseBase::ok(), bans: vec![ @@ -828,7 +893,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 2069..=2094, Banned, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( BANNED_REQUEST => BannedRequest { address: "95.216.203.255".into(), } @@ -837,7 +902,7 @@ define_request_and_response! { address: String, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( BANNED_RESPONSE => BannedResponse { banned: true, seconds: 689655, @@ -857,7 +922,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 2096..=2116, FlushTransactionPool, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( FLUSH_TRANSACTION_POOL_REQUEST => FlushTransactionPoolRequest { txids: vec!["dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into()], } @@ -866,7 +931,7 @@ define_request_and_response! { txids: Vec = default_vec::(), "default_vec", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( FLUSH_TRANSACTION_POOL_RESPONSE => FlushTransactionPoolResponse { status: Status::Ok, } @@ -883,7 +948,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 2118..=2168, GetOutputHistogram, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_OUTPUT_HISTOGRAM_REQUEST => GetOutputHistogramRequest { amounts: vec![20000000000], min_count: 0, @@ -900,7 +965,7 @@ define_request_and_response! { recent_cutoff: u64 = default_zero::(), "default_zero", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_OUTPUT_HISTOGRAM_RESPONSE => GetOutputHistogramResponse { base: AccessResponseBase::ok(), histogram: vec![HistogramEntry { @@ -923,7 +988,7 @@ define_request_and_response! { GetCoinbaseTxSum, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_COINBASE_TX_SUM_REQUEST => GetCoinbaseTxSumRequest { height: 1563078, count: 2 @@ -934,7 +999,7 @@ define_request_and_response! { count: u64, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_COINBASE_TX_SUM_RESPONSE => GetCoinbaseTxSumResponse { base: AccessResponseBase::ok(), emission_amount: 9387854817320, @@ -963,7 +1028,7 @@ define_request_and_response! { GetVersion, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_VERSION_RESPONSE => GetVersionResponse { base: ResponseBase::ok(), current_height: 3195051, @@ -1054,7 +1119,7 @@ define_request_and_response! { GetFeeEstimate, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_FEE_ESTIMATE_RESPONSE => GetFeeEstimateResponse { base: AccessResponseBase::ok(), fee: 20000, @@ -1076,7 +1141,7 @@ define_request_and_response! { GetAlternateChains, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_ALTERNATE_CHAINS_RESPONSE => GetAlternateChainsResponse { base: ResponseBase::ok(), chains: vec![ @@ -1115,7 +1180,7 @@ define_request_and_response! { RelayTx, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( RELAY_TX_REQUEST => RelayTxRequest { txids: vec!["9fd75c429cbe52da9a52f2ffc5fbd107fe7fd2099c0d8de274dc8a67e0c98613".into()] } @@ -1124,7 +1189,7 @@ define_request_and_response! { txids: Vec, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( RELAY_TX_RESPONSE => RelayTxResponse { status: Status::Ok, } @@ -1143,7 +1208,7 @@ define_request_and_response! { SyncInfo, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( SYNC_INFO_RESPONSE => SyncInfoResponse { base: AccessResponseBase::ok(), height: 3195157, @@ -1233,7 +1298,7 @@ define_request_and_response! { Request {}, // TODO: enable test after binary string impl. - // #[doc = json_rpc_doc_test!( + // #[doc = serde_doc_test!( // GET_TRANSACTION_POOL_BACKLOG_RESPONSE => GetTransactionPoolBacklogResponse { // base: ResponseBase::ok(), // backlog: "...Binary...".into(), @@ -1255,7 +1320,7 @@ define_request_and_response! { /// binary endpoint. GetOutputDistribution, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_OUTPUT_DISTRIBUTION_REQUEST => GetOutputDistributionRequest { amounts: vec![628780000], from_height: 1462078, @@ -1274,7 +1339,7 @@ define_request_and_response! { to_height: u64 = default_zero::(), "default_zero", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { base: AccessResponseBase::ok(), distributions: vec![Distribution::Uncompressed { @@ -1298,7 +1363,7 @@ define_request_and_response! { GetMinerData, Request {}, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( GET_MINER_DATA_RESPONSE => GetMinerDataResponse { base: ResponseBase::ok(), already_generated_coins: 18186022843595960691, @@ -1342,7 +1407,7 @@ define_request_and_response! { PruneBlockchain, #[derive(Copy)] - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( PRUNE_BLOCKCHAIN_REQUEST => PruneBlockchainRequest { check: true } @@ -1351,7 +1416,7 @@ define_request_and_response! { check: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( PRUNE_BLOCKCHAIN_RESPONSE => PruneBlockchainResponse { base: ResponseBase::ok(), pruned: true, @@ -1371,7 +1436,7 @@ define_request_and_response! { CalcPow, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( CALC_POW_REQUEST => CalcPowRequest { major_version: 14, height: 2286447, @@ -1386,7 +1451,7 @@ define_request_and_response! { seed_hash: String, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( CALC_POW_RESPONSE => CalcPowResponse { pow_hash: "d0402d6834e26fb94a9ce38c6424d27d2069896a9b8b1ce685d79936bca6e0a8".into(), } @@ -1406,7 +1471,7 @@ define_request_and_response! { FlushCache, #[derive(Copy)] - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( FLUSH_CACHE_REQUEST => FlushCacheRequest { bad_txs: true, bad_blocks: true @@ -1417,7 +1482,7 @@ define_request_and_response! { bad_blocks: bool = default_false(), "default_false", }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( FLUSH_CACHE_RESPONSE => FlushCacheResponse { base: ResponseBase::ok(), } @@ -1432,7 +1497,7 @@ define_request_and_response! { AddAuxPow, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( ADD_AUX_POW_REQUEST => AddAuxPowRequest { blocktemplate_blob: "1010f4bae0b4069d648e741d85ca0e7acb4501f051b27e9b107d3cd7a3f03aa7f776089117c81a0000000002c681c30101ff8a81c3010180e0a596bb11033b7eedf47baf878f3490cb20b696079c34bd017fe59b0d070e74d73ffabc4bb0e05f011decb630f3148d0163b3bd39690dde4078e4cfb69fecf020d6278a27bad10c58023c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(), aux_pow: vec![AuxPow { @@ -1446,7 +1511,7 @@ define_request_and_response! { aux_pow: Vec, }, - #[doc = json_rpc_doc_test!( + #[doc = serde_doc_test!( ADD_AUX_POW_RESPONSE => AddAuxPowResponse { base: ResponseBase::ok(), aux_pow: vec![AuxPow { diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index c7e79a3cb..fa0d51882 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -371,102 +371,3 @@ macro_rules! monero_definition_link { }; } pub(crate) use monero_definition_link; - -//---------------------------------------------------------------------------------------------------- json_rpc_doc_test -/// TODO -macro_rules! json_rpc_doc_test { - ( - $cuprate_test_utils_rpc_const:ident => $expected:expr - ) => { - paste::paste! { - concat!( - "```rust\n", - "use cuprate_test_utils::rpc::data::json::*;\n", - "use cuprate_rpc_types::{misc::*, base::*, json::*};\n", - "use serde_json::{Value, from_str, from_value};\n", - "\n", - "// The expected data.\n", - "let expected = ", - stringify!($expected), - ";\n", - "\n", - "// Assert it can be turned into a JSON value.\n", - "let value = from_str::(", - stringify!($cuprate_test_utils_rpc_const), - ").unwrap();\n", - "let Value::Object(map) = value else {\n", - " panic!();\n", - "};\n", - "\n", - "// If a request...\n", - "if let Some(params) = map.get(\"params\") {\n", - " let response = from_value::<", - stringify!([<$cuprate_test_utils_rpc_const:camel>]), - ">(params.clone()).unwrap();\n", - " assert_eq!(response, expected);\n", - " return;\n", - "}\n", - "\n", - "// Else, if a response...\n", - "let result = map.get(\"result\").unwrap().clone();\n", - "let response = from_value::<", - stringify!([<$cuprate_test_utils_rpc_const:camel>]), - ">(result.clone()).unwrap();\n", - "assert_eq!(response, expected);\n", - "```\n", - ) - } - }; -} -pub(crate) use json_rpc_doc_test; - -//---------------------------------------------------------------------------------------------------- json_other_doc_test -/// TODO -macro_rules! json_other_doc_test { - // TODO - ($cuprate_test_utils_rpc_const:ident) => { - paste::paste! { - concat!( - "```rust\n", - "use cuprate_test_utils::rpc::data::other::*;\n", - "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", - "use serde_json::{Value, from_str, from_value};\n", - "\n", - "let string = from_str::<", - stringify!([<$cuprate_test_utils_rpc_const:camel>]), - ">(", - stringify!($cuprate_test_utils_rpc_const), - ").unwrap();\n", - "```\n", - ) - } - }; - // TODO - ( - $cuprate_test_utils_rpc_const:ident => $expected:expr - ) => { - paste::paste! { - concat!( - "```rust\n", - "use cuprate_test_utils::rpc::data::other::*;\n", - "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", - "use serde_json::{Value, from_str, from_value};\n", - "\n", - "// The expected data.\n", - "let expected = ", - stringify!($expected), - ";\n", - "\n", - "let string = from_str::<", - stringify!([<$cuprate_test_utils_rpc_const:camel>]), - ">(", - stringify!($cuprate_test_utils_rpc_const), - ").unwrap();\n", - "\n", - "assert_eq!(string, expected);\n", - "```\n", - ) - } - }; -} -pub(crate) use json_other_doc_test; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 902963bb9..c1407778e 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -6,13 +6,88 @@ use crate::{ base::{AccessResponseBase, ResponseBase}, defaults::{default_false, default_string, default_true, default_vec, default_zero}, - macros::{define_request_and_response, json_other_doc_test}, + macros::define_request_and_response, misc::{ GetOutputsOut, KeyImageSpentStatus, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, TxpoolStats, }, }; +//---------------------------------------------------------------------------------------------------- Macro +/// Adds a (de)serialization doc-test to a type in `other.rs`. +/// +/// It expects a const string from `cuprate_test_utils::rpc::data` +/// and the expected value it should (de)serialize into/from. +/// +/// It tests that the provided const JSON string can properly +/// (de)serialize into the expected value. +/// +/// See below for example usage. This macro is only used in this file. +macro_rules! serde_doc_test { + // This branch _only_ tests that the type can be deserialize + // from the string, not that any value is correct. + // + // Practically, this is used for structs that have + // many values that are complicated to test, e.g. `GET_TRANSACTIONS_RESPONSE`. + // + // HACK: + // The type itself doesn't need to be specified because it happens + // to just be the `CamelCase` version of the provided const. + ( + // `const` string from `cuprate_test_utils::rpc::data`. + $cuprate_test_utils_rpc_const:ident + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "```\n", + ) + } + }; + + // This branch tests that the type can be deserialize + // from the string AND that values are correct. + ( + // `const` string from `cuprate_test_utils::rpc::data` + // v + $cuprate_test_utils_rpc_const:ident => $expected:expr + // ^ + // Expected value as an expression + ) => { + paste::paste! { + concat!( + "```rust\n", + "use cuprate_test_utils::rpc::data::other::*;\n", + "use cuprate_rpc_types::{misc::*, base::*, other::*};\n", + "use serde_json::{Value, from_str, from_value};\n", + "\n", + "// The expected data.\n", + "let expected = ", + stringify!($expected), + ";\n", + "\n", + "let string = from_str::<", + stringify!([<$cuprate_test_utils_rpc_const:camel>]), + ">(", + stringify!($cuprate_test_utils_rpc_const), + ").unwrap();\n", + "\n", + "assert_eq!(string, expected);\n", + "```\n", + ) + } + }; +} + //---------------------------------------------------------------------------------------------------- Definitions define_request_and_response! { get_height, @@ -21,7 +96,7 @@ define_request_and_response! { GetHeight, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_HEIGHT_RESPONSE => GetHeightResponse { base: ResponseBase::ok(), hash: "68bb1a1cff8e2a44c3221e8e1aff80bc6ca45d06fa8eff4d2a3a7ac31d4efe3f".into(), @@ -40,7 +115,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 370..=451, GetTransactions, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_TRANSACTIONS_REQUEST => GetTransactionsRequest { txs_hashes: vec!["d6e48158472848e6687173a91ae6eebfa3e1d778e65252ee99d7515d63090408".into()], decode_as_json: false, @@ -58,7 +133,7 @@ define_request_and_response! { split: bool = default_false(), "default_false", }, - #[doc = json_other_doc_test!(GET_TRANSACTIONS_RESPONSE)] + #[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)] AccessResponseBase { txs_as_hex: Vec = default_vec::(), "default_vec", txs_as_json: Vec = default_vec::(), "default_vec", @@ -74,7 +149,7 @@ define_request_and_response! { GetAltBlocksHashes, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_ALT_BLOCKS_HASHES_RESPONSE => GetAltBlocksHashesResponse { base: AccessResponseBase::ok(), blks_hashes: vec!["8ee10db35b1baf943f201b303890a29e7d45437bd76c2bd4df0d2f2ee34be109".into()], @@ -92,7 +167,7 @@ define_request_and_response! { IsKeyImageSpent, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( IS_KEY_IMAGE_SPENT_REQUEST => IsKeyImageSpentRequest { key_images: vec![ "8d1bd8181bf7d857bdb281e0153d84cd55a3fcaa57c3e570f4a49f935850b5e3".into(), @@ -104,7 +179,7 @@ define_request_and_response! { key_images: Vec, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( IS_KEY_IMAGE_SPENT_RESPONSE => IsKeyImageSpentResponse { base: AccessResponseBase::ok(), spent_status: vec![1, 1], @@ -123,7 +198,7 @@ define_request_and_response! { SendRawTransaction, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SEND_RAW_TRANSACTION_REQUEST => SendRawTransactionRequest { tx_as_hex: "dc16fa8eaffe1484ca9014ea050e13131d3acf23b419f33bb4cc0b32b6c49308".into(), do_not_relay: false, @@ -136,7 +211,7 @@ define_request_and_response! { do_sanity_checks: bool = default_true(), "default_true", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SEND_RAW_TRANSACTION_RESPONSE => SendRawTransactionResponse { base: AccessResponseBase { response_base: ResponseBase { @@ -185,7 +260,7 @@ define_request_and_response! { StartMining, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( START_MINING_REQUEST => StartMiningRequest { do_background_mining: false, ignore_battery: true, @@ -200,7 +275,7 @@ define_request_and_response! { ignore_battery: bool, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( START_MINING_RESPONSE => StartMiningResponse { base: ResponseBase::ok(), } @@ -215,7 +290,7 @@ define_request_and_response! { StopMining, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( STOP_MINING_RESPONSE => StopMiningResponse { base: ResponseBase::ok(), } @@ -230,7 +305,7 @@ define_request_and_response! { MiningStatus, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( MINING_STATUS_RESPONSE => MiningStatusResponse { base: ResponseBase::ok(), active: false, @@ -276,7 +351,7 @@ define_request_and_response! { SaveBc, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SAVE_BC_RESPONSE => SaveBcResponse { base: ResponseBase::ok(), } @@ -291,7 +366,7 @@ define_request_and_response! { GetPeerList, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_PEER_LIST_REQUEST => GetPeerListRequest { public_only: true, include_blocked: false, @@ -302,7 +377,7 @@ define_request_and_response! { include_blocked: bool = default_false(), "default_false", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_PEER_LIST_RESPONSE => GetPeerListResponse { base: ResponseBase::ok(), gray_list: vec![ @@ -374,7 +449,7 @@ define_request_and_response! { SetLogHashRate, #[derive(Copy)] - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_HASH_RATE_REQUEST => SetLogHashRateRequest { visible: true, } @@ -383,7 +458,7 @@ define_request_and_response! { visible: bool = default_false(), "default_false", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_HASH_RATE_RESPONSE => SetLogHashRateResponse { base: ResponseBase::ok(), } @@ -399,7 +474,7 @@ define_request_and_response! { SetLogLevel, #[derive(Copy)] - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_LEVEL_REQUEST => SetLogLevelRequest { level: 1 } @@ -408,7 +483,7 @@ define_request_and_response! { level: u8, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_LEVEL_RESPONSE => SetLogLevelResponse { base: ResponseBase::ok(), } @@ -423,7 +498,7 @@ define_request_and_response! { SetLogCategories, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_CATEGORIES_REQUEST => SetLogCategoriesRequest { categories: "*:INFO".into(), } @@ -432,7 +507,7 @@ define_request_and_response! { categories: String = default_string(), "default_string", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LOG_CATEGORIES_RESPONSE => SetLogCategoriesResponse { base: ResponseBase::ok(), categories: "*:INFO".into(), @@ -450,7 +525,7 @@ define_request_and_response! { SetBootstrapDaemon, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_BOOTSTRAP_DAEMON_REQUEST => SetBootstrapDaemonRequest { address: "http://getmonero.org:18081".into(), username: String::new(), @@ -465,7 +540,7 @@ define_request_and_response! { proxy: String = default_string(), "default_string", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_BOOTSTRAP_DAEMON_RESPONSE => SetBootstrapDaemonResponse { status: Status::Ok, } @@ -483,7 +558,7 @@ define_request_and_response! { GetTransactionPool, Request {}, - #[doc = json_other_doc_test!(GET_TRANSACTION_POOL_RESPONSE)] + #[doc = serde_doc_test!(GET_TRANSACTION_POOL_RESPONSE)] AccessResponseBase { transactions: Vec, spent_key_images: Vec, @@ -498,7 +573,7 @@ define_request_and_response! { GetTransactionPoolStats, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_TRANSACTION_POOL_STATS_RESPONSE => GetTransactionPoolStatsResponse { base: AccessResponseBase::ok(), pool_stats: TxpoolStats { @@ -542,7 +617,7 @@ define_request_and_response! { StopDaemon, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( STOP_DAEMON_RESPONSE => StopDaemonResponse { status: Status::Ok, } @@ -560,7 +635,7 @@ define_request_and_response! { GetLimit, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_LIMIT_RESPONSE => GetLimitResponse { base: ResponseBase::ok(), limit_down: 1280000, @@ -579,7 +654,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1876..=1903, SetLimit, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LIMIT_REQUEST => SetLimitRequest { limit_down: 1024, limit_up: 0, @@ -591,7 +666,7 @@ define_request_and_response! { limit_up: i64 = default_zero::(), "default_zero", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( SET_LIMIT_RESPONSE => SetLimitResponse { base: ResponseBase::ok(), limit_down: 1024, @@ -611,7 +686,7 @@ define_request_and_response! { OutPeers, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( OUT_PEERS_REQUEST => OutPeersRequest { out_peers: 3232235535, set: true, @@ -622,7 +697,7 @@ define_request_and_response! { out_peers: u32, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( OUT_PEERS_RESPONSE => OutPeersResponse { base: ResponseBase::ok(), out_peers: 3232235535, @@ -641,7 +716,7 @@ define_request_and_response! { GetNetStats, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_NET_STATS_RESPONSE => GetNetStatsResponse { base: ResponseBase::ok(), start_time: 1721251858, @@ -666,7 +741,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 567..=609, GetOuts, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_OUTS_REQUEST => GetOutsRequest { outputs: vec![ GetOutputsOut { amount: 1, index: 0 }, @@ -680,7 +755,7 @@ define_request_and_response! { get_txid: bool, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_OUTS_RESPONSE => GetOutsResponse { base: ResponseBase::ok(), outs: vec![ @@ -713,7 +788,7 @@ define_request_and_response! { Update, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( UPDATE_REQUEST => UpdateRequest { command: "check".into(), path: "".into(), @@ -724,7 +799,7 @@ define_request_and_response! { path: String = default_string(), "default_string", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( UPDATE_RESPONSE => UpdateResponse { base: ResponseBase::ok(), auto_uri: "".into(), @@ -752,7 +827,7 @@ define_request_and_response! { PopBlocks, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( POP_BLOCKS_REQUEST => PopBlocksRequest { nblocks: 6 } @@ -761,7 +836,7 @@ define_request_and_response! { nblocks: u64, }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( POP_BLOCKS_RESPONSE => PopBlocksResponse { base: ResponseBase::ok(), height: 76482, @@ -780,7 +855,7 @@ define_request_and_response! { GetTransactionPoolHashes, Request {}, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_TRANSACTION_POOL_HASHES_RESPONSE => GetTransactionPoolHashesResponse { base: ResponseBase::ok(), tx_hashes: vec![ @@ -816,7 +891,7 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 1419..=1448, GetPublicNodes, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_PUBLIC_NODES_REQUEST => GetPublicNodesRequest { gray: false, white: true, @@ -829,7 +904,7 @@ define_request_and_response! { include_blocked: bool = default_false(), "default_false", }, - #[doc = json_other_doc_test!( + #[doc = serde_doc_test!( GET_PUBLIC_NODES_RESPONSE => GetPublicNodesResponse { base: ResponseBase::ok(), gray: vec![], From e7dbd7b348e923d37b5e9aff0398f0bf40c7baf7 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 21 Jul 2024 15:54:16 -0400 Subject: [PATCH 53/93] base: add doc-tests --- rpc/types/src/base.rs | 96 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/rpc/types/src/base.rs b/rpc/types/src/base.rs index 5970f006a..c131e41ec 100644 --- a/rpc/types/src/base.rs +++ b/rpc/types/src/base.rs @@ -58,7 +58,37 @@ pub struct ResponseBase { } impl ResponseBase { - /// TODO + /// `const` version of [`Default::default`]. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let new = ResponseBase::new(); + /// assert_eq!(new, ResponseBase { + /// status: Status::Ok, + /// untrusted: false, + /// }); + /// ``` + pub const fn new() -> Self { + Self { + status: Status::Ok, + untrusted: false, + } + } + + /// Returns OK and trusted [`Self`]. + /// + /// This is the most common version of [`Self`]. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let ok = ResponseBase::ok(); + /// assert_eq!(ok, ResponseBase { + /// status: Status::Ok, + /// untrusted: false, + /// }); + /// ``` pub const fn ok() -> Self { Self { status: Status::Ok, @@ -66,7 +96,17 @@ impl ResponseBase { } } - /// TODO + /// Same as [`Self::ok`] but with [`Self::untrusted`] set to `true`. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let ok_untrusted = ResponseBase::ok_untrusted(); + /// assert_eq!(ok_untrusted, ResponseBase { + /// status: Status::Ok, + /// untrusted: true, + /// }); + /// ``` pub const fn ok_untrusted() -> Self { Self { status: Status::Ok, @@ -99,7 +139,44 @@ pub struct AccessResponseBase { } impl AccessResponseBase { - /// TODO + /// Creates a new [`Self`] with default values. + /// + /// Since RPC payment is semi-deprecated, [`Self::credits`] + /// and [`Self::top_hash`] will always be set to the default + /// values. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let new = AccessResponseBase::new(ResponseBase::ok()); + /// assert_eq!(new, AccessResponseBase { + /// response_base: ResponseBase::ok(), + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` + pub const fn new(response_base: ResponseBase) -> Self { + Self { + response_base, + credits: 0, + top_hash: String::new(), + } + } + + /// Returns OK and trusted [`Self`]. + /// + /// This is the most common version of [`Self`]. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let ok = AccessResponseBase::ok(); + /// assert_eq!(ok, AccessResponseBase { + /// response_base: ResponseBase::ok(), + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` pub const fn ok() -> Self { Self { response_base: ResponseBase::ok(), @@ -108,7 +185,18 @@ impl AccessResponseBase { } } - /// TODO + /// Same as [`Self::ok`] but with `untrusted` set to `true`. + /// + /// ```rust + /// use cuprate_rpc_types::{misc::*, base::*}; + /// + /// let ok_untrusted = AccessResponseBase::ok_untrusted(); + /// assert_eq!(ok_untrusted, AccessResponseBase { + /// response_base: ResponseBase::ok_untrusted(), + /// credits: 0, + /// top_hash: "".into(), + /// }); + /// ``` pub const fn ok_untrusted() -> Self { Self { response_base: ResponseBase::ok_untrusted(), From ea8f2e681c21654645f0defefffc562526ce97d5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 21 Jul 2024 16:42:46 -0400 Subject: [PATCH 54/93] rpc: add `cuprate-rpc-interface` skeleton files --- Cargo.lock | 4 + rpc/interface/Cargo.toml | 4 + rpc/interface/README.md | 89 +++++ rpc/interface/src/constants.rs | 11 + rpc/interface/src/error.rs | 11 + rpc/interface/src/free.rs | 18 + rpc/interface/src/lib.rs | 114 ++++++ rpc/interface/src/macros.rs | 379 ++++++++++++++++++ rpc/interface/src/request.rs | 11 + rpc/interface/src/response.rs | 11 + rpc/interface/src/route/bin.rs | 125 ++++++ rpc/interface/src/route/json.rs | 660 +++++++++++++++++++++++++++++++ rpc/interface/src/route/mod.rs | 5 + rpc/interface/src/route/other.rs | 400 +++++++++++++++++++ rpc/interface/src/state.rs | 379 ++++++++++++++++++ 15 files changed, 2221 insertions(+) create mode 100644 rpc/interface/README.md create mode 100644 rpc/interface/src/constants.rs create mode 100644 rpc/interface/src/error.rs create mode 100644 rpc/interface/src/free.rs create mode 100644 rpc/interface/src/macros.rs create mode 100644 rpc/interface/src/request.rs create mode 100644 rpc/interface/src/response.rs create mode 100644 rpc/interface/src/route/bin.rs create mode 100644 rpc/interface/src/route/json.rs create mode 100644 rpc/interface/src/route/mod.rs create mode 100644 rpc/interface/src/route/other.rs create mode 100644 rpc/interface/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 426ccc2fe..c6e3ccc07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,6 +757,10 @@ dependencies = [ [[package]] name = "cuprate-rpc-interface" version = "0.0.0" +dependencies = [ + "cuprate-epee-encoding", + "serde", +] [[package]] name = "cuprate-rpc-types" diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 47af5cd65..06635ca63 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -9,7 +9,11 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte keywords = ["cuprate", "rpc", "interface"] [features] +default = [] [dependencies] +cuprate-epee-encoding = { path = "../../net/epee-encoding" } + +serde = { workspace = true } [dev-dependencies] diff --git a/rpc/interface/README.md b/rpc/interface/README.md new file mode 100644 index 000000000..eb8da0134 --- /dev/null +++ b/rpc/interface/README.md @@ -0,0 +1,89 @@ +Monero RPC types. + +# What +This crate ports the types used in Monero's RPC interface, including: +- JSON types +- Binary (epee) types +- Mixed types +- Other commonly used RPC types + +# Modules +This crate's types are split in the following manner: + +| Module | Purpose | +|--------|---------| +| The root module | Miscellaneous items, e.g. constants. +| [`json`] | Contains JSON request/response (some mixed with binary) that all share the common `/json_rpc` endpoint. | +| [`bin`] | Contains request/response types that are expected to be fully in binary (`cuprate_epee_encoding`) in `monerod` and `cuprated`'s RPC interface. These are called at a custom endpoint instead of `/json_rpc`, e.g. `/get_blocks.bin`. | +| [`other`] | Contains request/response types that are JSON, but aren't called at `/json_rpc` (e.g. [`crate::other::GetHeightRequest`]). | +| [`misc`] | Contains miscellaneous types, e.g. [`crate::misc::Status`]. Many of types here are found and used in request/response types, for example, [`crate::misc::BlockHeader`] is used in [`crate::json::GetLastBlockHeaderResponse`]. | +| [`base`] | Contains base types flattened into many request/response types. + +Each type in `{json,bin,other}` come in pairs and have identical names, but are suffixed with either `Request` or `Response`. e.g. [`GetBlockCountRequest`](crate::json::GetBlockCountRequest) & [`GetBlockCountResponse`](crate::json::GetBlockCountResponse). + +# Documentation +The documentation for types within `{json,bin,other}` are omitted, as they can be found in [Monero's RPC documentation](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html). + +However, each type will document: +- **Definition**: the exact type definition location in `monerod` +- **Documentation**: the Monero RPC documentation link +- **Request/response**: the other side of this type, either the request or response + +# Naming +The naming for types within `{json,bin,other}` follow the following scheme: +1. Convert the endpoint or method name into `UpperCamelCase` +1. Remove any suffix extension +1. Add `Request/Response` suffix + +For example: + +| Endpoint/method | Crate location and name | +|-----------------|-------------------------| +| [`get_block_count`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count) | [`json::GetBlockCountRequest`] & [`json::GetBlockCountResponse`] +| [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blockbin) | [`bin::GetBlocksRequest`] & [`bin::GetBlocksResponse`] +| [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) | [`other::GetHeightRequest`] & [`other::GetHeightResponse`] + +# Mixed types +Note that some types mix JSON & binary together, i.e., the message overall is JSON, +however some fields contain binary values inside JSON strings, for example: + +```json +{ + "string": "", + "float": 30.0, + "integer": 30, + "binary": "" +} +``` + +`binary` here is (de)serialized as a normal [`String`]. In order to be clear on which fields contain binary data, the struct fields that have them will use [`crate::misc::BinaryString`] instead of [`String`]. + +These mixed types are: +- [`crate::json::GetTransactionPoolBacklogResponse`] +- [`crate::json::GetOutputDistributionResponse`] + +TODO: we need to figure out a type that (de)serializes correctly, `String` errors with `serde_json` + +# Fixed byte containers +TODO + + + +# Feature flags +List of feature flags for `cuprate-rpc-types`. + +All are enabled by default. + +| Feature flag | Does what | +|--------------|-----------| +| `serde` | Implements `serde` on all types +| `epee` | Implements `cuprate_epee_encoding` on all types \ No newline at end of file diff --git a/rpc/interface/src/constants.rs b/rpc/interface/src/constants.rs new file mode 100644 index 000000000..668b3b201 --- /dev/null +++ b/rpc/interface/src/constants.rs @@ -0,0 +1,11 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Status + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/error.rs b/rpc/interface/src/error.rs new file mode 100644 index 000000000..668b3b201 --- /dev/null +++ b/rpc/interface/src/error.rs @@ -0,0 +1,11 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Status + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs new file mode 100644 index 000000000..043a52093 --- /dev/null +++ b/rpc/interface/src/free.rs @@ -0,0 +1,18 @@ +//! Free functions. + +//---------------------------------------------------------------------------------------------------- Serde +// These are functions used for conditionally (de)serialization. + +/// Returns `true` if the input `u` is equal to `0`. +#[inline] +#[allow(clippy::trivially_copy_pass_by_ref)] // serde needs `&` +pub(crate) const fn is_zero(u: &u64) -> bool { + *u == 0 +} + +/// Returns `true` the input `u` is equal to `1`. +#[inline] +#[allow(clippy::trivially_copy_pass_by_ref)] // serde needs `&` +pub(crate) const fn is_one(u: &u64) -> bool { + *u == 1 +} diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 8b1378917..489a16532 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -1 +1,115 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] +//---------------------------------------------------------------------------------------------------- Lints +// Forbid lints. +// Our code, and code generated (e.g macros) cannot overrule these. +#![forbid( + // `unsafe` is allowed but it _must_ be + // commented with `SAFETY: reason`. + clippy::undocumented_unsafe_blocks, + // Never. + unused_unsafe, + redundant_semicolons, + unused_allocation, + coherence_leak_check, + while_true, + + // Maybe can be put into `#[deny]`. + unconditional_recursion, + for_loops_over_fallibles, + unused_braces, + unused_labels, + keyword_idents, + non_ascii_idents, + variant_size_differences, + single_use_lifetimes, + + // Probably can be put into `#[deny]`. + future_incompatible, + let_underscore, + break_with_label_and_loop, + duplicate_macro_attributes, + exported_private_dependencies, + large_assignments, + overlapping_range_endpoints, + semicolon_in_expressions_from_macros, + noop_method_call, +)] +// Deny lints. +// Some of these are `#[allow]`'ed on a per-case basis. +#![deny( + clippy::all, + clippy::correctness, + clippy::suspicious, + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + unused_doc_comments, + unused_mut, + missing_docs, + deprecated, + unused_comparisons, + nonstandard_style, + unreachable_pub +)] +#![allow( + // FIXME: this lint affects crates outside of + // `database/` for some reason, allow for now. + clippy::cargo_common_metadata, + + // FIXME: adding `#[must_use]` onto everything + // might just be more annoying than useful... + // although it is sometimes nice. + clippy::must_use_candidate, + + // FIXME: good lint but too many false positives + // with our `Env` + `RwLock` setup. + clippy::significant_drop_tightening, + + // FIXME: good lint but is less clear in most cases. + clippy::items_after_statements, + + // TODO + rustdoc::bare_urls, + + clippy::module_name_repetitions, + clippy::module_inception, + clippy::redundant_pub_crate, + clippy::option_if_let_else, +)] +// Allow some lints when running in debug mode. +#![cfg_attr( + debug_assertions, + allow( + clippy::todo, + clippy::multiple_crate_versions, + unused_imports, + unused_variables + ) +)] +// Allow some lints in tests. +#![cfg_attr( + test, + allow( + clippy::cognitive_complexity, + clippy::needless_pass_by_value, + clippy::cast_possible_truncation, + clippy::too_many_lines + ) +)] +// TODO: remove me after finishing impl +#![allow(dead_code)] + +//---------------------------------------------------------------------------------------------------- Mod +mod constants; +mod error; +mod free; +mod macros; +mod request; +mod response; +mod route; +mod state; diff --git a/rpc/interface/src/macros.rs b/rpc/interface/src/macros.rs new file mode 100644 index 000000000..e13013872 --- /dev/null +++ b/rpc/interface/src/macros.rs @@ -0,0 +1,379 @@ +//! Macros. + +//---------------------------------------------------------------------------------------------------- define_request_and_response +/// A template for generating the RPC request and response `struct`s. +/// +/// These `struct`s automatically implement: +/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash` +/// - `serde::{Serialize, Deserialize}` +/// - `cuprate_epee_encoding::EpeeObject` +/// +/// It's best to see the output of this macro via the documentation +/// of the generated structs via `cargo doc`s to see which parts +/// generate which docs. +/// +/// See the [`crate::json`] module for example usage. +/// +/// # Macro internals +/// This macro uses: +/// - [`__define_request`] +/// - [`__define_response`] +/// - [`__define_request_and_response_doc`] +/// +/// # `__define_request` +/// This macro has 2 branches. If the caller provides +/// `Request {}`, i.e. no fields, it will generate: +/// ``` +/// pub type Request = (); +/// ``` +/// If they _did_ specify fields, it will generate: +/// ``` +/// pub struct Request {/* fields */} +/// ``` +/// This is because having a bunch of types that are all empty structs +/// means they are not compatible and it makes it cumbersome for end-users. +/// Really, they semantically are empty types, so `()` is used. +/// +/// # `__define_response` +/// This macro has 2 branches. If the caller provides `Response` +/// it will generate a normal struct with no additional fields. +/// +/// If the caller provides a base type from [`crate::base`], it will +/// flatten that into the request type automatically. +/// +/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}` +/// would trigger the different branches. +macro_rules! define_request_and_response { + ( + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. + $monero_daemon_rpc_doc_link:ident, + + // The commit hash and `$file.$extension` in which this type is defined in + // the Monero codebase in the `rpc/` directory, followed by the specific lines. + $monero_code_commit:ident => + $monero_code_filename:ident. + $monero_code_filename_extension:ident => + $monero_code_line_start:literal..= + $monero_code_line_end:literal, + + // The base `struct` name. + // Attributes added here will apply to _both_ + // request and response types. + $( #[$type_attr:meta] )* + $type_name:ident, + + // The request type (and any doc comments, derives, etc). + $( #[$request_type_attr:meta] )* + Request { + // And any fields. + $( + $( #[$request_field_attr:meta] )* // Field attribute. + $request_field:ident: $request_field_type:ty // field_name: field type + $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization + $(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value + )* + }, + + // The response type (and any doc comments, derives, etc). + $( #[$response_type_attr:meta] )* + $response_base_type:ty { + // And any fields. + $( + $( #[$response_field_attr:meta] )* + $response_field:ident: $response_field_type:ty + $(as $response_field_type_as:ty)? + $(= $response_field_type_default:expr, $response_field_type_default_string:literal)?, + )* + } + ) => { paste::paste! { + $crate::macros::__define_request! { + #[doc = $crate::macros::__define_request_and_response_doc!( + "response" => [<$type_name Response>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$request_type_attr] )* + [<$type_name Request>] { + $( + $( #[$request_field_attr] )* + $request_field: $request_field_type + $(as $request_field_type_as)? + $(= $request_field_type_default, $request_field_type_default_string)?, + )* + } + } + + $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!( + "request" => [<$type_name Request>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$response_type_attr] )* + $response_base_type => [<$type_name Response>] { + $( + $( #[$response_field_attr] )* + $response_field: $response_field_type + $(as $response_field_type_as)? + $(= $response_field_type_default, $response_field_type_default_string)?, + )* + } + } + }}; +} +pub(crate) use define_request_and_response; + +//---------------------------------------------------------------------------------------------------- define_request +/// 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 { + //------------------------------------------------------------------------------ + // This branch will generate a type alias to `()` if only given `{}` as input. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + $t:ident {} + ) => { + $( #[$attr] )* + /// + /// This request has no inputs. + pub type $t = (); + }; + + //------------------------------------------------------------------------------ + // This branch of the macro expects fields within the `{}`, + // and will generate a `struct` + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + $t:ident { + // And any fields. + $( + $( #[$field_attr:meta] )* // field attributes + // field_name: FieldType + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + // The $field_default is an optional extra token that represents + // a default value to pass to [`cuprate_epee_encoding::epee_object`], + // see it for usage. + )* + } + ) => { + #[allow(dead_code, missing_docs)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; +} +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 version of the macro expects the literal ident + // `Response` => $response_type_name. + // + // It will create a `struct` that _doesn't_ use a base from [`crate::base`], + // for example, [`crate::json::BannedResponse`] doesn't use a base, so it + // uses this branch. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + Response => $t:ident { + // And any fields. + // See [`__define_request`] for docs, this does the same thing. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; + + //------------------------------------------------------------------------------ + // This version of the macro expects a `Request` base type from [`crate::bases`]. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // 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. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + #[cfg_attr(feature = "serde", serde(flatten))] + pub base: $base, + + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + !flatten: base: $base, + } + }; +} +pub(crate) use __define_response; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_doc +/// Generate documentation for the types generated +/// 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 { + ( + // This labels the last `[request]` or `[response]` + // hyperlink in documentation. Input is either: + // - "request" + // - "response" + // + // Remember this is linking to the _other_ type, + // so if defining a `Request` type, input should + // be "response". + $request_or_response:literal => $request_or_response_type:ident, + + $monero_daemon_rpc_doc_link:ident, + $monero_code_commit:ident, + $monero_code_filename:ident, + $monero_code_filename_extension:ident, + $monero_code_line_start:literal, + $monero_code_line_end:literal, + ) => { + concat!( + "", + "[Definition](", + "https://github.com/monero-project/monero/blob/", + stringify!($monero_code_commit), + "/src/rpc/", + stringify!($monero_code_filename), + ".", + stringify!($monero_code_filename_extension), + "#L", + stringify!($monero_code_line_start), + "-L", + stringify!($monero_code_line_end), + "), [documentation](", + "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", + "#", + stringify!($monero_daemon_rpc_doc_link), + "), [", + $request_or_response, + "](", + stringify!($request_or_response_type), + ")." + ) + }; +} +pub(crate) use __define_request_and_response_doc; + +//---------------------------------------------------------------------------------------------------- Macro +/// Output a string link to `monerod` source code. +macro_rules! monero_definition_link { + ( + $commit:ident, // Git commit hash + $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` + $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` + ) => { + concat!( + "[Definition](https://github.com/monero-project/monero/blob/", + stringify!($commit), + "/src/", + $file_path, + "#L", + stringify!($start), + $( + "-L", + stringify!($end), + )? + ")." + ) + }; +} +pub(crate) use monero_definition_link; diff --git a/rpc/interface/src/request.rs b/rpc/interface/src/request.rs new file mode 100644 index 000000000..668b3b201 --- /dev/null +++ b/rpc/interface/src/request.rs @@ -0,0 +1,11 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Status + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/response.rs b/rpc/interface/src/response.rs new file mode 100644 index 000000000..668b3b201 --- /dev/null +++ b/rpc/interface/src/response.rs @@ -0,0 +1,11 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Status + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs new file mode 100644 index 000000000..35d6e0f88 --- /dev/null +++ b/rpc/interface/src/route/bin.rs @@ -0,0 +1,125 @@ +//! Binary types from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). +//! +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- 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]>, +// } +// } + +// define_request_and_response! { +// get_blocks_by_heightbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 264..=286, +// GetBlocksByHeight, +// Request { +// heights: Vec, +// }, +// AccessResponseBase { +// blocks: Vec, +// } +// } + +// define_request_and_response! { +// get_hashesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 309..=338, +// GetHashes, +// Request { +// block_ids: ByteArrayVec<32>, +// start_height: u64, +// }, +// AccessResponseBase { +// m_blocks_ids: ByteArrayVec<32>, +// start_height: u64, +// current_height: u64, +// } +// } + +// #[cfg(not(feature = "epee"))] +// define_request_and_response! { +// get_o_indexesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 487..=510, +// GetOutputIndexes, +// #[derive(Copy)] +// Request { +// txid: [u8; 32], +// }, +// AccessResponseBase { +// o_indexes: Vec, +// } +// } + +// #[cfg(feature = "epee")] +// define_request_and_response! { +// get_o_indexesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 487..=510, +// GetOutputIndexes, +// #[derive(Copy)] +// Request { +// txid: [u8; 32], +// }, +// AccessResponseBase { +// o_indexes: Vec as ContainerAsBlob, +// } +// } + +// define_request_and_response! { +// get_outsbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 512..=565, +// GetOuts, +// Request { +// outputs: Vec, +// get_txid: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// outs: Vec, +// } +// } + +// define_request_and_response! { +// get_transaction_pool_hashesbin, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1593..=1613, +// GetTransactionPoolHashes, +// Request {}, +// AccessResponseBase { +// tx_hashes: ByteArrayVec<32>, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs new file mode 100644 index 000000000..cbf452ecf --- /dev/null +++ b/rpc/interface/src/route/json.rs @@ -0,0 +1,660 @@ +//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. +//! +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Struct definitions +// // This generates 2 structs: +// // +// // - `GetBlockTemplateRequest` +// // - `GetBlockTemplateResponse` +// // +// // with some interconnected documentation. +// define_request_and_response! { +// // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. +// get_block_template, + +// // The commit hash and `$file.$extension` in which this type is defined in +// // the Monero codebase in the `rpc/` directory, followed by the specific lines. +// cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, + +// // The base type name. +// GetBlockTemplate, + +// // The request type. +// // +// // If `Request {/* fields */}` is provided, a struct is generate as-is. +// // +// // If `Request {}` is specified here, it will create a `pub type YOUR_REQUEST_TYPE = ()` +// // instead of a `struct`, see below in other macro definitions for an example. +// // +// // If there are any additional attributes (`/// docs` or `#[derive]`s) +// // for the struct, they go here, e.g.: +// // #[derive(Copy)] +// Request { +// // Within the `{}` is an infinite matching pattern of: +// // ``` +// // $ATTRIBUTES +// // $FIELD_NAME: $FIELD_TYPE, +// // ``` +// // The struct generated and all fields are `pub`. + +// // This optional expression can be placed after +// // a `field: field_type`. this indicates to the +// // macro to (de)serialize this field using this +// // default expression if it doesn't exist in epee. +// // +// // See `cuprate_epee_encoding::epee_object` for info. +// // +// // The default function must be specified twice: +// // +// // 1. As an expression +// // 2. As a string literal +// // +// // For example: `extra_nonce: String /* = default_string(), "default_string" */,` +// // +// // This is a HACK since `serde`'s default attribute only takes in +// // string literals and macros (stringify) within attributes do not work. +// extra_nonce: String /* = default_expression, "default_literal" */, + +// // Another optional expression: +// // This indicates to the macro to (de)serialize +// // this field as another type in epee. +// // +// // See `cuprate_epee_encoding::epee_object` for info. +// prev_block: String /* as Type */, + +// // Regular fields. +// reserve_size: u64, +// wallet_address: String, +// }, + +// // The response type. +// // +// // If `Response {/* fields */}` is used, +// // this will generate a struct as-is. +// // +// // If a type found in [`crate::base`] is used, +// // It acts as a "base" that gets flattened into +// // the actual request type. +// // +// // "Flatten" means the field(s) of a struct gets inlined +// // directly into the struct during (de)serialization, see: +// // . +// ResponseBase { +// // This is using [`crate::base::ResponseBase`], +// // so the type we generate will contain this field: +// // ``` +// // base: crate::base::ResponseBase, +// // ``` +// // +// // This is flattened with serde and epee, so during +// // (de)serialization, it will act as if there are 2 extra fields here: +// // ``` +// // status: crate::Status, +// // untrusted: bool, +// // ``` +// blockhashing_blob: String, +// blocktemplate_blob: String, +// difficulty_top64: u64, +// difficulty: u64, +// expected_reward: u64, +// height: u64, +// next_seed_hash: String, +// prev_hash: String, +// reserved_offset: u64, +// seed_hash: String, +// seed_height: u64, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// get_block_count, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 919..=933, +// GetBlockCount, + +// // There are no request fields specified, +// // this will cause the macro to generate a +// // type alias to `()` instead of a `struct`. +// Request {}, + +// #[derive(Copy)] +// ResponseBase { +// count: u64, +// } +// } + +// define_request_and_response! { +// on_get_block_hash, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 935..=939, +// OnGetBlockHash, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashRequest { block_height: [3] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "[3]"); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// #[derive(Copy)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_height: [u64; 1], +// }, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "\"asdf\""); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// block_hash: String, +// } +// } + +// define_request_and_response! { +// submit_block, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1114..=1128, +// SubmitBlock, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, r#"["a"]"#); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_blob: [String; 1], +// }, +// ResponseBase { +// block_id: String, +// } +// } + +// define_request_and_response! { +// generateblocks, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1130..=1161, +// GenerateBlocks, +// Request { +// amount_of_blocks: u64, +// prev_block: String, +// starting_nonce: u32, +// wallet_address: String, +// }, +// ResponseBase { +// blocks: Vec, +// height: u64, +// } +// } + +// define_request_and_response! { +// get_last_block_header, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1214..=1238, +// GetLastBlockHeader, +// #[derive(Copy)] +// Request { +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_header_by_hash, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1240..=1269, +// GetBlockHeaderByHash, +// Request { +// hash: String, +// hashes: Vec, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// block_headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block_header_by_height, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1271..=1296, +// GetBlockHeaderByHeight, +// #[derive(Copy)] +// Request { +// height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_headers_range, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1756..=1783, +// GetBlockHeadersRange, +// #[derive(Copy)] +// Request { +// start_height: u64, +// end_height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1298..=1313, +// GetBlock, +// Request { +// // `monerod` has both `hash` and `height` fields. +// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. +// // +// hash: String = default_string(), "default_string", +// height: u64 = default_height(), "default_height", +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// blob: String, +// block_header: BlockHeader, +// json: String, // TODO: this should be defined in a struct, it has many fields. +// miner_tx_hash: String, +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// get_connections, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1734..=1754, +// GetConnections, +// Request {}, +// ResponseBase { +// // FIXME: This is a `std::list` in `monerod` because...? +// connections: Vec, +// } +// } + +// define_request_and_response! { +// get_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 693..=789, +// GetInfo, +// Request {}, +// AccessResponseBase { +// adjusted_time: u64, +// alt_blocks_count: u64, +// block_size_limit: u64, +// block_size_median: u64, +// block_weight_limit: u64, +// block_weight_median: u64, +// bootstrap_daemon_address: String, +// busy_syncing: bool, +// cumulative_difficulty_top64: u64, +// cumulative_difficulty: u64, +// database_size: u64, +// difficulty_top64: u64, +// difficulty: u64, +// free_space: u64, +// grey_peerlist_size: u64, +// height: u64, +// height_without_bootstrap: u64, +// incoming_connections_count: u64, +// mainnet: bool, +// nettype: String, +// offline: bool, +// outgoing_connections_count: u64, +// restricted: bool, +// rpc_connections_count: u64, +// stagenet: bool, +// start_time: u64, +// synchronized: bool, +// target_height: u64, +// target: u64, +// testnet: bool, +// top_block_hash: String, +// tx_count: u64, +// tx_pool_size: u64, +// update_available: bool, +// version: String, +// was_bootstrap_ever_used: bool, +// white_peerlist_size: u64, +// wide_cumulative_difficulty: String, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// hard_fork_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1958..=1995, +// HardForkInfo, +// Request {}, +// AccessResponseBase { +// earliest_height: u64, +// enabled: bool, +// state: u32, +// threshold: u32, +// version: u8, +// votes: u32, +// voting: u8, +// window: u32, +// } +// } + +// define_request_and_response! { +// set_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2032..=2067, +// SetBans, +// Request { +// bans: Vec, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// get_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1997..=2030, +// GetBans, +// Request {}, +// ResponseBase { +// bans: Vec, +// } +// } + +// define_request_and_response! { +// banned, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2069..=2094, +// Banned, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// address: String, +// }, +// #[derive(Copy)] +// Response { +// banned: bool, +// seconds: u32, +// status: Status, +// } +// } + +// define_request_and_response! { +// flush_txpool, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2096..=2116, +// FlushTransactionPool, +// Request { +// txids: Vec = default_vec::(), "default_vec", +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_output_histogram, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2118..=2168, +// GetOutputHistogram, +// Request { +// amounts: Vec, +// min_count: u64, +// max_count: u64, +// unlocked: bool, +// recent_cutoff: u64, +// }, +// AccessResponseBase { +// histogram: Vec, +// } +// } + +// define_request_and_response! { +// get_coinbase_tx_sum, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2213..=2248, +// GetCoinbaseTxSum, +// Request { +// height: u64, +// count: u64, +// }, +// AccessResponseBase { +// emission_amount: u64, +// emission_amount_top64: u64, +// fee_amount: u64, +// fee_amount_top64: u64, +// wide_emission_amount: String, +// wide_fee_amount: String, +// } +// } + +// define_request_and_response! { +// get_version, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2170..=2211, +// GetVersion, +// Request {}, +// ResponseBase { +// version: u32, +// release: bool, +// #[serde(skip_serializing_if = "is_zero")] +// current_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "is_zero")] +// target_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "Vec::is_empty")] +// hard_forks: Vec = default_vec(), "default_vec", +// } +// } + +// define_request_and_response! { +// get_fee_estimate, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2250..=2277, +// GetFeeEstimate, +// Request {}, +// AccessResponseBase { +// fee: u64, +// fees: Vec, +// #[serde(skip_serializing_if = "is_one")] +// quantization_mask: u64, +// } +// } + +// define_request_and_response! { +// get_alternate_chains, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2279..=2310, +// GetAlternateChains, +// Request {}, +// ResponseBase { +// chains: Vec, +// } +// } + +// define_request_and_response! { +// relay_tx, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2361..=2381, +// RelayTx, +// Request { +// txids: Vec, +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// sync_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2383..=2443, +// SyncInfo, +// Request {}, +// AccessResponseBase { +// height: u64, +// next_needed_pruning_seed: u32, +// overview: String, +// // FIXME: This is a `std::list` in `monerod` because...? +// peers: Vec, +// // FIXME: This is a `std::list` in `monerod` because...? +// spans: Vec, +// target_height: u64, +// } +// } + +// define_request_and_response! { +// get_txpool_backlog, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1637..=1664, +// GetTransactionPoolBacklog, +// Request {}, +// ResponseBase { +// // TODO: this is a [`BinaryString`]. +// backlog: Vec, +// } +// } + +// define_request_and_response! { +// get_output_distribution, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2445..=2520, +// /// This type is also used in the (undocumented) +// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) +// /// binary endpoint. +// GetOutputDistribution, +// Request { +// amounts: Vec, +// binary: bool, +// compress: bool, +// cumulative: bool, +// from_height: u64, +// to_height: u64, +// }, +// /// TODO: this request has custom serde: +// /// +// AccessResponseBase { +// distributions: Vec, +// } +// } + +// define_request_and_response! { +// get_miner_data, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 996..=1044, +// GetMinerData, +// Request {}, +// ResponseBase { +// major_version: u8, +// height: u64, +// prev_id: String, +// seed_hash: String, +// difficulty: String, +// median_weight: u64, +// already_generated_coins: u64, +// } +// } + +// define_request_and_response! { +// prune_blockchain, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2747..=2772, +// PruneBlockchain, +// #[derive(Copy)] +// Request { +// check: bool = default_false(), "default_false", +// }, +// #[derive(Copy)] +// ResponseBase { +// pruned: bool, +// pruning_seed: u32, +// } +// } + +// define_request_and_response! { +// calc_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1046..=1066, +// CalcPow, +// Request { +// major_version: u8, +// height: u64, +// block_blob: String, +// seed_hash: String, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// pow_hash: String, +// } +// } + +// define_request_and_response! { +// flush_cache, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2774..=2796, +// FlushCache, +// #[derive(Copy)] +// Request { +// bad_txs: bool = default_false(), "default_false", +// bad_blocks: bool = default_false(), "default_false", +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// add_aux_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1068..=1112, +// AddAuxPow, +// Request { +// blocktemplate_blob: String, +// aux_pow: Vec, +// }, +// ResponseBase { +// blocktemplate_blob: String, +// blockhashing_blob: String, +// merkle_root: String, +// merkle_tree_depth: u64, +// aux_pow: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs new file mode 100644 index 000000000..9b7105eee --- /dev/null +++ b/rpc/interface/src/route/mod.rs @@ -0,0 +1,5 @@ +//! TODO + +mod bin; +mod json; +mod other; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs new file mode 100644 index 000000000..2203d75f1 --- /dev/null +++ b/rpc/interface/src/route/other.rs @@ -0,0 +1,400 @@ +//! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. +//! +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- TODO +// define_request_and_response! { +// get_height, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 138..=160, +// GetHeight, +// Request {}, +// ResponseBase { +// hash: String, +// height: u64, +// } +// } + +// define_request_and_response! { +// get_transactions, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 370..=451, +// GetTransactions, +// Request { +// txs_hashes: Vec, +// // FIXME: this is documented as optional but it isn't serialized as an optional +// // but it is set _somewhere_ to false in `monerod` +// // +// decode_as_json: bool = default_false(), "default_false", +// prune: bool = default_false(), "default_false", +// split: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// txs_as_hex: Vec, +// txs_as_json: Vec, +// missed_tx: Vec, +// txs: Vec, +// } +// } + +// define_request_and_response! { +// get_alt_blocks_hashes, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 288..=308, +// GetAltBlocksHashes, +// Request {}, +// AccessResponseBase { +// blks_hashes: Vec, +// } +// } + +// define_request_and_response! { +// is_key_image_spent, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 454..=484, +// IsKeyImageSpent, +// Request { +// key_images: Vec, +// }, +// AccessResponseBase { +// spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. +// } +// } + +// define_request_and_response! { +// send_raw_transaction, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 370..=451, +// SendRawTransaction, +// Request { +// tx_as_hex: String, +// do_not_relay: bool = default_false(), "default_false", +// do_sanity_checks: bool = default_true(), "default_true", +// }, +// AccessResponseBase { +// double_spend: bool, +// fee_too_low: bool, +// invalid_input: bool, +// invalid_output: bool, +// low_mixin: bool, +// nonzero_unlock_time: bool, +// not_relayed: bool, +// overspend: bool, +// reason: String, +// sanity_check_failed: bool, +// too_big: bool, +// too_few_outputs: bool, +// tx_extra_too_big: bool, +// } +// } + +// define_request_and_response! { +// start_mining, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 665..=691, +// StartMining, +// Request { +// miner_address: String, +// threads_count: u64, +// do_background_mining: bool, +// ignore_battery: bool, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// stop_mining, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 825..=843, +// StopMining, +// Request {}, +// ResponseBase {} +// } + +// define_request_and_response! { +// mining_status, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 846..=895, +// MiningStatus, +// Request {}, +// ResponseBase { +// active: bool, +// address: String, +// bg_idle_threshold: u8, +// bg_ignore_battery: bool, +// bg_min_idle_seconds: u8, +// bg_target: u8, +// block_reward: u64, +// block_target: u32, +// difficulty: u64, +// difficulty_top64: u64, +// is_background_mining_enabled: bool, +// pow_algorithm: String, +// speed: u64, +// threads_count: u32, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// save_bc, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 898..=916, +// SaveBc, +// Request {}, +// ResponseBase {} +// } + +// define_request_and_response! { +// get_peer_list, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1369..=1417, +// GetPeerList, +// Request { +// public_only: bool = default_true(), "default_true", +// include_blocked: bool = default_false(), "default_false", +// }, +// ResponseBase { +// white_list: Vec, +// gray_list: Vec, +// } +// } + +// define_request_and_response! { +// set_log_hash_rate, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1450..=1470, +// SetLogHashRate, +// #[derive(Copy)] +// Request { +// visible: bool, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// set_log_level, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1450..=1470, +// SetLogLevel, +// #[derive(Copy)] +// Request { +// level: u8, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// set_log_categories, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1494..=1517, +// SetLogCategories, +// Request { +// categories: String = default_string(), "default_string", +// }, +// ResponseBase { +// categories: String, +// } +// } + +// define_request_and_response! { +// set_bootstrap_daemon, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1785..=1812, +// SetBootstrapDaemon, +// Request { +// address: String, +// username: String, +// password: String, +// proxy: String, +// }, +// #[derive(Copy)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_transaction_pool, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1569..=1591, +// GetTransactionPool, +// Request {}, +// AccessResponseBase { +// transactions: Vec, +// spent_key_images: Vec, +// } +// } + +// define_request_and_response! { +// get_transaction_pool_stats, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1712..=1732, +// GetTransactionPoolStats, +// Request {}, +// AccessResponseBase { +// pool_stats: TxpoolStats, +// } +// } + +// define_request_and_response! { +// stop_daemon, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1814..=1831, +// StopDaemon, +// Request {}, +// ResponseBase { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_limit, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1852..=1874, +// GetLimit, +// Request {}, +// ResponseBase { +// limit_down: u64, +// limit_up: u64, +// } +// } + +// define_request_and_response! { +// set_limit, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1876..=1903, +// SetLimit, +// Request { +// limit_down: i64, +// limit_up: i64, +// }, +// ResponseBase { +// limit_down: i64, +// limit_up: i64, +// } +// } + +// define_request_and_response! { +// out_peers, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1876..=1903, +// OutPeers, +// Request { +// set: bool = default_true(), "default_true", +// out_peers: u32, +// }, +// ResponseBase { +// out_peers: u32, +// } +// } + +// define_request_and_response! { +// get_net_stats, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 793..=822, +// GetNetStats, +// Request {}, +// ResponseBase { +// start_time: u64, +// total_packets_in: u64, +// total_bytes_in: u64, +// total_packets_out: u64, +// total_bytes_out: u64, +// } +// } + +// define_request_and_response! { +// get_outs, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 567..=609, +// GetOuts, +// Request { +// outputs: Vec, +// get_txid: bool, +// }, +// ResponseBase { +// outs: Vec, +// } +// } + +// define_request_and_response! { +// update, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2324..=2359, +// Update, +// Request { +// command: String, +// path: String = default_string(), "default_string", +// }, +// ResponseBase { +// auto_uri: String, +// hash: String, +// path: String, +// update: bool, +// user_uri: String, +// version: String, +// } +// } + +// define_request_and_response! { +// pop_blocks, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2722..=2745, +// PopBlocks, +// Request { +// nblocks: u64, +// }, +// ResponseBase { +// height: u64, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2798..=2823, +// GetTxIdsLoose, +// Request { +// txid_template: String, +// num_matching_bits: u32, +// }, +// ResponseBase { +// txids: Vec, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1615..=1635, +// GetTransactionPoolHashes, +// Request {}, +// ResponseBase { +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// UNDOCUMENTED_ENDPOINT, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1419..=1448, +// GetPublicNodes, +// Request { +// gray: bool = default_false(), "default_false", +// white: bool = default_true(), "default_true", +// include_blocked: bool = default_false(), "default_false", +// }, +// ResponseBase { +// gray: Vec, +// white: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/state.rs b/rpc/interface/src/state.rs new file mode 100644 index 000000000..e13013872 --- /dev/null +++ b/rpc/interface/src/state.rs @@ -0,0 +1,379 @@ +//! Macros. + +//---------------------------------------------------------------------------------------------------- define_request_and_response +/// A template for generating the RPC request and response `struct`s. +/// +/// These `struct`s automatically implement: +/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash` +/// - `serde::{Serialize, Deserialize}` +/// - `cuprate_epee_encoding::EpeeObject` +/// +/// It's best to see the output of this macro via the documentation +/// of the generated structs via `cargo doc`s to see which parts +/// generate which docs. +/// +/// See the [`crate::json`] module for example usage. +/// +/// # Macro internals +/// This macro uses: +/// - [`__define_request`] +/// - [`__define_response`] +/// - [`__define_request_and_response_doc`] +/// +/// # `__define_request` +/// This macro has 2 branches. If the caller provides +/// `Request {}`, i.e. no fields, it will generate: +/// ``` +/// pub type Request = (); +/// ``` +/// If they _did_ specify fields, it will generate: +/// ``` +/// pub struct Request {/* fields */} +/// ``` +/// This is because having a bunch of types that are all empty structs +/// means they are not compatible and it makes it cumbersome for end-users. +/// Really, they semantically are empty types, so `()` is used. +/// +/// # `__define_response` +/// This macro has 2 branches. If the caller provides `Response` +/// it will generate a normal struct with no additional fields. +/// +/// If the caller provides a base type from [`crate::base`], it will +/// flatten that into the request type automatically. +/// +/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}` +/// would trigger the different branches. +macro_rules! define_request_and_response { + ( + // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. + $monero_daemon_rpc_doc_link:ident, + + // The commit hash and `$file.$extension` in which this type is defined in + // the Monero codebase in the `rpc/` directory, followed by the specific lines. + $monero_code_commit:ident => + $monero_code_filename:ident. + $monero_code_filename_extension:ident => + $monero_code_line_start:literal..= + $monero_code_line_end:literal, + + // The base `struct` name. + // Attributes added here will apply to _both_ + // request and response types. + $( #[$type_attr:meta] )* + $type_name:ident, + + // The request type (and any doc comments, derives, etc). + $( #[$request_type_attr:meta] )* + Request { + // And any fields. + $( + $( #[$request_field_attr:meta] )* // Field attribute. + $request_field:ident: $request_field_type:ty // field_name: field type + $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization + $(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value + )* + }, + + // The response type (and any doc comments, derives, etc). + $( #[$response_type_attr:meta] )* + $response_base_type:ty { + // And any fields. + $( + $( #[$response_field_attr:meta] )* + $response_field:ident: $response_field_type:ty + $(as $response_field_type_as:ty)? + $(= $response_field_type_default:expr, $response_field_type_default_string:literal)?, + )* + } + ) => { paste::paste! { + $crate::macros::__define_request! { + #[doc = $crate::macros::__define_request_and_response_doc!( + "response" => [<$type_name Response>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$request_type_attr] )* + [<$type_name Request>] { + $( + $( #[$request_field_attr] )* + $request_field: $request_field_type + $(as $request_field_type_as)? + $(= $request_field_type_default, $request_field_type_default_string)?, + )* + } + } + + $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!( + "request" => [<$type_name Request>], + $monero_daemon_rpc_doc_link, + $monero_code_commit, + $monero_code_filename, + $monero_code_filename_extension, + $monero_code_line_start, + $monero_code_line_end, + )] + /// + $( #[$type_attr] )* + /// + $( #[$response_type_attr] )* + $response_base_type => [<$type_name Response>] { + $( + $( #[$response_field_attr] )* + $response_field: $response_field_type + $(as $response_field_type_as)? + $(= $response_field_type_default, $response_field_type_default_string)?, + )* + } + } + }}; +} +pub(crate) use define_request_and_response; + +//---------------------------------------------------------------------------------------------------- define_request +/// 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 { + //------------------------------------------------------------------------------ + // This branch will generate a type alias to `()` if only given `{}` as input. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + $t:ident {} + ) => { + $( #[$attr] )* + /// + /// This request has no inputs. + pub type $t = (); + }; + + //------------------------------------------------------------------------------ + // This branch of the macro expects fields within the `{}`, + // and will generate a `struct` + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + $t:ident { + // And any fields. + $( + $( #[$field_attr:meta] )* // field attributes + // field_name: FieldType + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + // The $field_default is an optional extra token that represents + // a default value to pass to [`cuprate_epee_encoding::epee_object`], + // see it for usage. + )* + } + ) => { + #[allow(dead_code, missing_docs)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; +} +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 version of the macro expects the literal ident + // `Response` => $response_type_name. + // + // It will create a `struct` that _doesn't_ use a base from [`crate::base`], + // for example, [`crate::json::BannedResponse`] doesn't use a base, so it + // uses this branch. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // The response type. + Response => $t:ident { + // And any fields. + // See [`__define_request`] for docs, this does the same thing. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + } + }; + + //------------------------------------------------------------------------------ + // This version of the macro expects a `Request` base type from [`crate::bases`]. + ( + // Any doc comments, derives, etc. + $( #[$attr:meta] )* + // 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. + $( + $( #[$field_attr:meta] )* + $field:ident: $field_type:ty + $(as $field_as:ty)? + $(= $field_default:expr, $field_default_string:literal)?, + )* + } + ) => { + $( #[$attr] )* + pub struct $t { + #[cfg_attr(feature = "serde", serde(flatten))] + pub base: $base, + + $( + $( #[$field_attr] )* + $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? + pub $field: $field_type, + )* + } + + #[cfg(feature = "epee")] + ::cuprate_epee_encoding::epee_object! { + $t, + $( + $field: $field_type + $(as $field_as)? + $(= $field_default)?, + )* + !flatten: base: $base, + } + }; +} +pub(crate) use __define_response; + +//---------------------------------------------------------------------------------------------------- define_request_and_response_doc +/// Generate documentation for the types generated +/// 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 { + ( + // This labels the last `[request]` or `[response]` + // hyperlink in documentation. Input is either: + // - "request" + // - "response" + // + // Remember this is linking to the _other_ type, + // so if defining a `Request` type, input should + // be "response". + $request_or_response:literal => $request_or_response_type:ident, + + $monero_daemon_rpc_doc_link:ident, + $monero_code_commit:ident, + $monero_code_filename:ident, + $monero_code_filename_extension:ident, + $monero_code_line_start:literal, + $monero_code_line_end:literal, + ) => { + concat!( + "", + "[Definition](", + "https://github.com/monero-project/monero/blob/", + stringify!($monero_code_commit), + "/src/rpc/", + stringify!($monero_code_filename), + ".", + stringify!($monero_code_filename_extension), + "#L", + stringify!($monero_code_line_start), + "-L", + stringify!($monero_code_line_end), + "), [documentation](", + "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", + "#", + stringify!($monero_daemon_rpc_doc_link), + "), [", + $request_or_response, + "](", + stringify!($request_or_response_type), + ")." + ) + }; +} +pub(crate) use __define_request_and_response_doc; + +//---------------------------------------------------------------------------------------------------- Macro +/// Output a string link to `monerod` source code. +macro_rules! monero_definition_link { + ( + $commit:ident, // Git commit hash + $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` + $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` + ) => { + concat!( + "[Definition](https://github.com/monero-project/monero/blob/", + stringify!($commit), + "/src/", + $file_path, + "#L", + stringify!($start), + $( + "-L", + stringify!($end), + )? + ")." + ) + }; +} +pub(crate) use monero_definition_link; From 73c11a4cdf3ac741a48754f133f6e61e81e889cb Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 21 Jul 2024 20:24:35 -0400 Subject: [PATCH 55/93] traits --- Cargo.lock | 113 ++++++ rpc/interface/Cargo.toml | 4 + rpc/interface/README.md | 84 +--- rpc/interface/src/error.rs | 4 +- rpc/interface/src/free.rs | 77 +++- rpc/interface/src/lib.rs | 14 +- rpc/interface/src/macros.rs | 378 +---------------- rpc/interface/src/method.rs | 31 ++ rpc/interface/src/request.rs | 4 +- rpc/interface/src/response.rs | 2 + .../src/route/{json.rs => json_rpc.rs} | 17 + rpc/interface/src/route/mod.rs | 4 +- rpc/interface/src/rpc_handler.rs | 49 +++ rpc/interface/src/rpc_state.rs | 30 ++ rpc/interface/src/state.rs | 379 ------------------ 15 files changed, 335 insertions(+), 855 deletions(-) create mode 100644 rpc/interface/src/method.rs rename rpc/interface/src/route/{json.rs => json_rpc.rs} (98%) create mode 100644 rpc/interface/src/rpc_handler.rs create mode 100644 rpc/interface/src/rpc_state.rs delete mode 100644 rpc/interface/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index c6e3ccc07..d9801529a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,61 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -758,8 +813,12 @@ dependencies = [ name = "cuprate-rpc-interface" version = "0.0.0" dependencies = [ + "axum", "cuprate-epee-encoding", + "cuprate-json-rpc", + "cuprate-rpc-types", "serde", + "tower", ] [[package]] @@ -1309,6 +1368,12 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.3.1" @@ -1321,6 +1386,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1630,6 +1696,12 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -1658,6 +1730,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -2371,6 +2449,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2506,6 +2606,18 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synchronoise" version = "1.0.1" @@ -2733,6 +2845,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 06635ca63..92076a2ae 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -13,7 +13,11 @@ default = [] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding" } +cuprate-json-rpc = { path = "../json-rpc" } +cuprate-rpc-types = { path = "../types" } +axum = { version = "0.7.5" } serde = { workspace = true } +tower = { workspace = true } [dev-dependencies] diff --git a/rpc/interface/README.md b/rpc/interface/README.md index eb8da0134..7517c6d5a 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -1,89 +1,11 @@ -Monero RPC types. - -# What -This crate ports the types used in Monero's RPC interface, including: -- JSON types -- Binary (epee) types -- Mixed types -- Other commonly used RPC types - -# Modules -This crate's types are split in the following manner: - -| Module | Purpose | -|--------|---------| -| The root module | Miscellaneous items, e.g. constants. -| [`json`] | Contains JSON request/response (some mixed with binary) that all share the common `/json_rpc` endpoint. | -| [`bin`] | Contains request/response types that are expected to be fully in binary (`cuprate_epee_encoding`) in `monerod` and `cuprated`'s RPC interface. These are called at a custom endpoint instead of `/json_rpc`, e.g. `/get_blocks.bin`. | -| [`other`] | Contains request/response types that are JSON, but aren't called at `/json_rpc` (e.g. [`crate::other::GetHeightRequest`]). | -| [`misc`] | Contains miscellaneous types, e.g. [`crate::misc::Status`]. Many of types here are found and used in request/response types, for example, [`crate::misc::BlockHeader`] is used in [`crate::json::GetLastBlockHeaderResponse`]. | -| [`base`] | Contains base types flattened into many request/response types. - -Each type in `{json,bin,other}` come in pairs and have identical names, but are suffixed with either `Request` or `Response`. e.g. [`GetBlockCountRequest`](crate::json::GetBlockCountRequest) & [`GetBlockCountResponse`](crate::json::GetBlockCountResponse). - -# Documentation -The documentation for types within `{json,bin,other}` are omitted, as they can be found in [Monero's RPC documentation](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html). - -However, each type will document: -- **Definition**: the exact type definition location in `monerod` -- **Documentation**: the Monero RPC documentation link -- **Request/response**: the other side of this type, either the request or response - -# Naming -The naming for types within `{json,bin,other}` follow the following scheme: -1. Convert the endpoint or method name into `UpperCamelCase` -1. Remove any suffix extension -1. Add `Request/Response` suffix - -For example: - -| Endpoint/method | Crate location and name | -|-----------------|-------------------------| -| [`get_block_count`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_count) | [`json::GetBlockCountRequest`] & [`json::GetBlockCountResponse`] -| [`/get_blocks.bin`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blockbin) | [`bin::GetBlocksRequest`] & [`bin::GetBlocksResponse`] -| [`/get_height`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_height) | [`other::GetHeightRequest`] & [`other::GetHeightResponse`] - -# Mixed types -Note that some types mix JSON & binary together, i.e., the message overall is JSON, -however some fields contain binary values inside JSON strings, for example: - -```json -{ - "string": "", - "float": 30.0, - "integer": 30, - "binary": "" -} -``` - -`binary` here is (de)serialized as a normal [`String`]. In order to be clear on which fields contain binary data, the struct fields that have them will use [`crate::misc::BinaryString`] instead of [`String`]. - -These mixed types are: -- [`crate::json::GetTransactionPoolBacklogResponse`] -- [`crate::json::GetOutputDistributionResponse`] - -TODO: we need to figure out a type that (de)serializes correctly, `String` errors with `serde_json` - -# Fixed byte containers TODO - +# What # Feature flags -List of feature flags for `cuprate-rpc-types`. +List of feature flags for `cuprate-rpc-interface`. All are enabled by default. | Feature flag | Does what | -|--------------|-----------| -| `serde` | Implements `serde` on all types -| `epee` | Implements `cuprate_epee_encoding` on all types \ No newline at end of file +|--------------|-----------| \ No newline at end of file diff --git a/rpc/interface/src/error.rs b/rpc/interface/src/error.rs index 668b3b201..fc816590e 100644 --- a/rpc/interface/src/error.rs +++ b/rpc/interface/src/error.rs @@ -2,7 +2,9 @@ //---------------------------------------------------------------------------------------------------- Import -//---------------------------------------------------------------------------------------------------- Status +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +pub enum Error {} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 043a52093..90f76670e 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -1,18 +1,69 @@ //! Free functions. -//---------------------------------------------------------------------------------------------------- Serde -// These are functions used for conditionally (de)serialization. +//---------------------------------------------------------------------------------------------------- Use +use std::{future::Future, marker::PhantomData}; -/// Returns `true` if the input `u` is equal to `0`. -#[inline] -#[allow(clippy::trivially_copy_pass_by_ref)] // serde needs `&` -pub(crate) const fn is_zero(u: &u64) -> bool { - *u == 0 -} +use axum::{routing::method_routing::get, Router}; +use tower::Service; + +use crate::{ + error::Error, request::Request, response::Response, route::json_rpc, rpc_handler::RpcHandler, + RpcState, +}; -/// Returns `true` the input `u` is equal to `1`. -#[inline] -#[allow(clippy::trivially_copy_pass_by_ref)] // serde needs `&` -pub(crate) const fn is_one(u: &u64) -> bool { - *u == 1 +//---------------------------------------------------------------------------------------------------- Router +/// TODO +#[allow(clippy::needless_pass_by_value)] +pub fn create_router() -> Router { + // List of `monerod` routes: + // + Router::new() + // JSON-RPC route. + .route("/json_rpc", get(json_rpc)) + // Other JSON routes. + .route("/get_height", todo!()) + .route("/getheight", todo!()) + .route("/get_transactions", todo!()) + .route("/gettransactions", todo!()) + .route("/get_alt_blocks_hashes", todo!()) + .route("/is_key_image_spent", todo!()) + .route("/send_raw_transaction", todo!()) + .route("/sendrawtransaction", todo!()) + .route("/start_mining", todo!()) + .route("/stop_mining", todo!()) + .route("/mining_status", todo!()) + .route("/save_bc", todo!()) + .route("/get_peer_list", todo!()) + .route("/get_public_nodes", todo!()) + .route("/set_log_hash_rate", todo!()) + .route("/set_log_level", todo!()) + .route("/set_log_categories", todo!()) + .route("/get_transaction_pool", todo!()) + .route("/get_transaction_pool_hashes", todo!()) + .route("/get_transaction_pool_stats", todo!()) + .route("/set_bootstrap_daemon", todo!()) + .route("/stop_daemon", todo!()) + .route("/get_info", todo!()) + .route("/getinfo", todo!()) + .route("/get_net_stats", todo!()) + .route("/get_limit", todo!()) + .route("/set_limit", todo!()) + .route("/out_peers", todo!()) + .route("/in_peers", todo!()) + .route("/get_outs", todo!()) + .route("/update", todo!()) + .route("/pop_blocks", todo!()) + // Binary routes. + .route("/get_blocks.bin", todo!()) + .route("/getblocks.bin", todo!()) + .route("/get_blocks_by_height.bin", todo!()) + .route("/getblocks_by_height.bin", todo!()) + .route("/get_hashes.bin", todo!()) + .route("/gethashes.bin", todo!()) + .route("/get_o_indexes.bin", todo!()) + .route("/get_outs.bin", todo!()) + .route("/get_transaction_pool_hashes.bin", todo!()) + .route("/get_output_distribution.bin", todo!()) + // Unknown route. + .route("/*", todo!()) } diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 489a16532..d2c91ae7b 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -102,14 +102,24 @@ ) )] // TODO: remove me after finishing impl -#![allow(dead_code)] +#![allow(dead_code, unreachable_code)] //---------------------------------------------------------------------------------------------------- Mod mod constants; mod error; mod free; mod macros; +mod method; mod request; mod response; mod route; -mod state; +mod rpc_handler; +mod rpc_state; + +pub use error::Error; +pub use free::create_router; +pub use method::Method; +pub use request::Request; +pub use response::Response; +pub use rpc_handler::{ConcreteRpcHandler, RpcHandler}; +pub use rpc_state::{ConcreteRpcState, RpcState}; diff --git a/rpc/interface/src/macros.rs b/rpc/interface/src/macros.rs index e13013872..0c939696b 100644 --- a/rpc/interface/src/macros.rs +++ b/rpc/interface/src/macros.rs @@ -1,379 +1,3 @@ //! Macros. -//---------------------------------------------------------------------------------------------------- define_request_and_response -/// A template for generating the RPC request and response `struct`s. -/// -/// These `struct`s automatically implement: -/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash` -/// - `serde::{Serialize, Deserialize}` -/// - `cuprate_epee_encoding::EpeeObject` -/// -/// It's best to see the output of this macro via the documentation -/// of the generated structs via `cargo doc`s to see which parts -/// generate which docs. -/// -/// See the [`crate::json`] module for example usage. -/// -/// # Macro internals -/// This macro uses: -/// - [`__define_request`] -/// - [`__define_response`] -/// - [`__define_request_and_response_doc`] -/// -/// # `__define_request` -/// This macro has 2 branches. If the caller provides -/// `Request {}`, i.e. no fields, it will generate: -/// ``` -/// pub type Request = (); -/// ``` -/// If they _did_ specify fields, it will generate: -/// ``` -/// pub struct Request {/* fields */} -/// ``` -/// This is because having a bunch of types that are all empty structs -/// means they are not compatible and it makes it cumbersome for end-users. -/// Really, they semantically are empty types, so `()` is used. -/// -/// # `__define_response` -/// This macro has 2 branches. If the caller provides `Response` -/// it will generate a normal struct with no additional fields. -/// -/// If the caller provides a base type from [`crate::base`], it will -/// flatten that into the request type automatically. -/// -/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}` -/// would trigger the different branches. -macro_rules! define_request_and_response { - ( - // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. - $monero_daemon_rpc_doc_link:ident, - - // The commit hash and `$file.$extension` in which this type is defined in - // the Monero codebase in the `rpc/` directory, followed by the specific lines. - $monero_code_commit:ident => - $monero_code_filename:ident. - $monero_code_filename_extension:ident => - $monero_code_line_start:literal..= - $monero_code_line_end:literal, - - // The base `struct` name. - // Attributes added here will apply to _both_ - // request and response types. - $( #[$type_attr:meta] )* - $type_name:ident, - - // The request type (and any doc comments, derives, etc). - $( #[$request_type_attr:meta] )* - Request { - // And any fields. - $( - $( #[$request_field_attr:meta] )* // Field attribute. - $request_field:ident: $request_field_type:ty // field_name: field type - $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization - $(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value - )* - }, - - // The response type (and any doc comments, derives, etc). - $( #[$response_type_attr:meta] )* - $response_base_type:ty { - // And any fields. - $( - $( #[$response_field_attr:meta] )* - $response_field:ident: $response_field_type:ty - $(as $response_field_type_as:ty)? - $(= $response_field_type_default:expr, $response_field_type_default_string:literal)?, - )* - } - ) => { paste::paste! { - $crate::macros::__define_request! { - #[doc = $crate::macros::__define_request_and_response_doc!( - "response" => [<$type_name Response>], - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - )] - /// - $( #[$type_attr] )* - /// - $( #[$request_type_attr] )* - [<$type_name Request>] { - $( - $( #[$request_field_attr] )* - $request_field: $request_field_type - $(as $request_field_type_as)? - $(= $request_field_type_default, $request_field_type_default_string)?, - )* - } - } - - $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!( - "request" => [<$type_name Request>], - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - )] - /// - $( #[$type_attr] )* - /// - $( #[$response_type_attr] )* - $response_base_type => [<$type_name Response>] { - $( - $( #[$response_field_attr] )* - $response_field: $response_field_type - $(as $response_field_type_as)? - $(= $response_field_type_default, $response_field_type_default_string)?, - )* - } - } - }}; -} -pub(crate) use define_request_and_response; - -//---------------------------------------------------------------------------------------------------- define_request -/// 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 { - //------------------------------------------------------------------------------ - // This branch will generate a type alias to `()` if only given `{}` as input. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - $t:ident {} - ) => { - $( #[$attr] )* - /// - /// This request has no inputs. - pub type $t = (); - }; - - //------------------------------------------------------------------------------ - // This branch of the macro expects fields within the `{}`, - // and will generate a `struct` - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - $t:ident { - // And any fields. - $( - $( #[$field_attr:meta] )* // field attributes - // field_name: FieldType - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - // The $field_default is an optional extra token that represents - // a default value to pass to [`cuprate_epee_encoding::epee_object`], - // see it for usage. - )* - } - ) => { - #[allow(dead_code, missing_docs)] - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - $( #[$attr] )* - pub struct $t { - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - } - }; -} -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 version of the macro expects the literal ident - // `Response` => $response_type_name. - // - // It will create a `struct` that _doesn't_ use a base from [`crate::base`], - // for example, [`crate::json::BannedResponse`] doesn't use a base, so it - // uses this branch. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - Response => $t:ident { - // And any fields. - // See [`__define_request`] for docs, this does the same thing. - $( - $( #[$field_attr:meta] )* - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - )* - } - ) => { - $( #[$attr] )* - pub struct $t { - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - } - }; - - //------------------------------------------------------------------------------ - // This version of the macro expects a `Request` base type from [`crate::bases`]. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // 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. - $( - $( #[$field_attr:meta] )* - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - )* - } - ) => { - $( #[$attr] )* - pub struct $t { - #[cfg_attr(feature = "serde", serde(flatten))] - pub base: $base, - - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - !flatten: base: $base, - } - }; -} -pub(crate) use __define_response; - -//---------------------------------------------------------------------------------------------------- define_request_and_response_doc -/// Generate documentation for the types generated -/// 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 { - ( - // This labels the last `[request]` or `[response]` - // hyperlink in documentation. Input is either: - // - "request" - // - "response" - // - // Remember this is linking to the _other_ type, - // so if defining a `Request` type, input should - // be "response". - $request_or_response:literal => $request_or_response_type:ident, - - $monero_daemon_rpc_doc_link:ident, - $monero_code_commit:ident, - $monero_code_filename:ident, - $monero_code_filename_extension:ident, - $monero_code_line_start:literal, - $monero_code_line_end:literal, - ) => { - concat!( - "", - "[Definition](", - "https://github.com/monero-project/monero/blob/", - stringify!($monero_code_commit), - "/src/rpc/", - stringify!($monero_code_filename), - ".", - stringify!($monero_code_filename_extension), - "#L", - stringify!($monero_code_line_start), - "-L", - stringify!($monero_code_line_end), - "), [documentation](", - "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", - "#", - stringify!($monero_daemon_rpc_doc_link), - "), [", - $request_or_response, - "](", - stringify!($request_or_response_type), - ")." - ) - }; -} -pub(crate) use __define_request_and_response_doc; - -//---------------------------------------------------------------------------------------------------- Macro -/// Output a string link to `monerod` source code. -macro_rules! monero_definition_link { - ( - $commit:ident, // Git commit hash - $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` - $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` - ) => { - concat!( - "[Definition](https://github.com/monero-project/monero/blob/", - stringify!($commit), - "/src/", - $file_path, - "#L", - stringify!($start), - $( - "-L", - stringify!($end), - )? - ")." - ) - }; -} -pub(crate) use monero_definition_link; +//---------------------------------------------------------------------------------------------------- TODO diff --git a/rpc/interface/src/method.rs b/rpc/interface/src/method.rs new file mode 100644 index 000000000..b06ad9660 --- /dev/null +++ b/rpc/interface/src/method.rs @@ -0,0 +1,31 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import +use serde::{Deserialize, Serialize}; + +use cuprate_rpc_types::json::GetBlockRequest; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +#[derive(Deserialize, Serialize)] +#[serde(tag = "method", content = "params")] +#[serde(rename_all = "snake_case")] +pub enum Method { + /// TODO + GetBlock(GetBlockRequest), +} + +impl Method { + /// TODO + pub const fn is_restricted(&self) -> bool { + match self { + Self::GetBlock(_) => false, + } + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/request.rs b/rpc/interface/src/request.rs index 668b3b201..6223f2819 100644 --- a/rpc/interface/src/request.rs +++ b/rpc/interface/src/request.rs @@ -2,7 +2,9 @@ //---------------------------------------------------------------------------------------------------- Import -//---------------------------------------------------------------------------------------------------- Status +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +pub enum Request {} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/response.rs b/rpc/interface/src/response.rs index 668b3b201..acbdb7c7d 100644 --- a/rpc/interface/src/response.rs +++ b/rpc/interface/src/response.rs @@ -3,6 +3,8 @@ //---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Status +/// TODO +pub enum Response {} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json_rpc.rs similarity index 98% rename from rpc/interface/src/route/json.rs rename to rpc/interface/src/route/json_rpc.rs index cbf452ecf..8eb3d4657 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -3,8 +3,25 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +use std::{future::Future, sync::Arc}; + +use axum::Json; +use tower::Service; + +use crate::{ + error::Error, method::Method, request::Request, response::Response, rpc_handler::RpcHandler, +}; //---------------------------------------------------------------------------------------------------- Struct definitions +/// TODO +// pub(crate) async fn json_rpc( +pub(crate) async fn json_rpc( + // handler: Arc, + Json(request): Json>, +) { + todo!() +} + // // This generates 2 structs: // // // // - `GetBlockTemplateRequest` diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 9b7105eee..09c2afc93 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,5 +1,7 @@ //! TODO mod bin; -mod json; +mod json_rpc; mod other; + +pub(crate) use json_rpc::json_rpc; diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs new file mode 100644 index 000000000..969926c14 --- /dev/null +++ b/rpc/interface/src/rpc_handler.rs @@ -0,0 +1,49 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +use std::{future::Future, marker::PhantomData, sync::Arc}; + +use tower::Service; + +use crate::{ + error::Error, request::Request, response::Response, rpc_state::ConcreteRpcState, RpcState, +}; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +pub trait RpcHandler: Send + Sync + 'static { + /// TODO + type RpcState: RpcState; + + /// TODO + type Handler: Send + Sync + 'static + Service; + // where + // >::Response: Into, + // >::Error: Into, + // >::Future: Future>; + + /// TODO + fn state(&self) -> Self::RpcState; +} + +/// TODO +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ConcreteRpcHandler { + state: ConcreteRpcState, + _handler: PhantomData, +} + +impl RpcHandler for ConcreteRpcHandler +where + H: Send + Sync + 'static + Service, + >::Response: Into, + >::Error: Into, + >::Future: Future>, +{ + type RpcState = ConcreteRpcState; + type Handler = H; + + fn state(&self) -> Self::RpcState { + self.state + } +} diff --git a/rpc/interface/src/rpc_state.rs b/rpc/interface/src/rpc_state.rs new file mode 100644 index 000000000..86ab73e80 --- /dev/null +++ b/rpc/interface/src/rpc_state.rs @@ -0,0 +1,30 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +use std::{future::Future, marker::PhantomData, sync::Arc}; + +use tower::Service; + +use crate::{error::Error, request::Request, response::Response}; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +pub trait RpcState +where + Self: Clone + Send + Sync + 'static, +{ + /// TODO + fn restricted(&self) -> bool; +} + +/// TODO +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ConcreteRpcState { + restricted: bool, +} + +impl RpcState for ConcreteRpcState { + fn restricted(&self) -> bool { + self.restricted + } +} diff --git a/rpc/interface/src/state.rs b/rpc/interface/src/state.rs deleted file mode 100644 index e13013872..000000000 --- a/rpc/interface/src/state.rs +++ /dev/null @@ -1,379 +0,0 @@ -//! Macros. - -//---------------------------------------------------------------------------------------------------- define_request_and_response -/// A template for generating the RPC request and response `struct`s. -/// -/// These `struct`s automatically implement: -/// - `Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash` -/// - `serde::{Serialize, Deserialize}` -/// - `cuprate_epee_encoding::EpeeObject` -/// -/// It's best to see the output of this macro via the documentation -/// of the generated structs via `cargo doc`s to see which parts -/// generate which docs. -/// -/// See the [`crate::json`] module for example usage. -/// -/// # Macro internals -/// This macro uses: -/// - [`__define_request`] -/// - [`__define_response`] -/// - [`__define_request_and_response_doc`] -/// -/// # `__define_request` -/// This macro has 2 branches. If the caller provides -/// `Request {}`, i.e. no fields, it will generate: -/// ``` -/// pub type Request = (); -/// ``` -/// If they _did_ specify fields, it will generate: -/// ``` -/// pub struct Request {/* fields */} -/// ``` -/// This is because having a bunch of types that are all empty structs -/// means they are not compatible and it makes it cumbersome for end-users. -/// Really, they semantically are empty types, so `()` is used. -/// -/// # `__define_response` -/// This macro has 2 branches. If the caller provides `Response` -/// it will generate a normal struct with no additional fields. -/// -/// If the caller provides a base type from [`crate::base`], it will -/// flatten that into the request type automatically. -/// -/// E.g. `Response {/*...*/}` and `ResponseBase {/*...*/}` -/// would trigger the different branches. -macro_rules! define_request_and_response { - ( - // The markdown tag for Monero daemon RPC documentation. Not necessarily the endpoint. - $monero_daemon_rpc_doc_link:ident, - - // The commit hash and `$file.$extension` in which this type is defined in - // the Monero codebase in the `rpc/` directory, followed by the specific lines. - $monero_code_commit:ident => - $monero_code_filename:ident. - $monero_code_filename_extension:ident => - $monero_code_line_start:literal..= - $monero_code_line_end:literal, - - // The base `struct` name. - // Attributes added here will apply to _both_ - // request and response types. - $( #[$type_attr:meta] )* - $type_name:ident, - - // The request type (and any doc comments, derives, etc). - $( #[$request_type_attr:meta] )* - Request { - // And any fields. - $( - $( #[$request_field_attr:meta] )* // Field attribute. - $request_field:ident: $request_field_type:ty // field_name: field type - $(as $request_field_type_as:ty)? // (optional) alternative type (de)serialization - $(= $request_field_type_default:expr, $request_field_type_default_string:literal)?, // (optional) default value - )* - }, - - // The response type (and any doc comments, derives, etc). - $( #[$response_type_attr:meta] )* - $response_base_type:ty { - // And any fields. - $( - $( #[$response_field_attr:meta] )* - $response_field:ident: $response_field_type:ty - $(as $response_field_type_as:ty)? - $(= $response_field_type_default:expr, $response_field_type_default_string:literal)?, - )* - } - ) => { paste::paste! { - $crate::macros::__define_request! { - #[doc = $crate::macros::__define_request_and_response_doc!( - "response" => [<$type_name Response>], - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - )] - /// - $( #[$type_attr] )* - /// - $( #[$request_type_attr] )* - [<$type_name Request>] { - $( - $( #[$request_field_attr] )* - $request_field: $request_field_type - $(as $request_field_type_as)? - $(= $request_field_type_default, $request_field_type_default_string)?, - )* - } - } - - $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!( - "request" => [<$type_name Request>], - $monero_daemon_rpc_doc_link, - $monero_code_commit, - $monero_code_filename, - $monero_code_filename_extension, - $monero_code_line_start, - $monero_code_line_end, - )] - /// - $( #[$type_attr] )* - /// - $( #[$response_type_attr] )* - $response_base_type => [<$type_name Response>] { - $( - $( #[$response_field_attr] )* - $response_field: $response_field_type - $(as $response_field_type_as)? - $(= $response_field_type_default, $response_field_type_default_string)?, - )* - } - } - }}; -} -pub(crate) use define_request_and_response; - -//---------------------------------------------------------------------------------------------------- define_request -/// 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 { - //------------------------------------------------------------------------------ - // This branch will generate a type alias to `()` if only given `{}` as input. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - $t:ident {} - ) => { - $( #[$attr] )* - /// - /// This request has no inputs. - pub type $t = (); - }; - - //------------------------------------------------------------------------------ - // This branch of the macro expects fields within the `{}`, - // and will generate a `struct` - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - $t:ident { - // And any fields. - $( - $( #[$field_attr:meta] )* // field attributes - // field_name: FieldType - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - // The $field_default is an optional extra token that represents - // a default value to pass to [`cuprate_epee_encoding::epee_object`], - // see it for usage. - )* - } - ) => { - #[allow(dead_code, missing_docs)] - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] - $( #[$attr] )* - pub struct $t { - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - } - }; -} -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 version of the macro expects the literal ident - // `Response` => $response_type_name. - // - // It will create a `struct` that _doesn't_ use a base from [`crate::base`], - // for example, [`crate::json::BannedResponse`] doesn't use a base, so it - // uses this branch. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - Response => $t:ident { - // And any fields. - // See [`__define_request`] for docs, this does the same thing. - $( - $( #[$field_attr:meta] )* - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - )* - } - ) => { - $( #[$attr] )* - pub struct $t { - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - } - }; - - //------------------------------------------------------------------------------ - // This version of the macro expects a `Request` base type from [`crate::bases`]. - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // 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. - $( - $( #[$field_attr:meta] )* - $field:ident: $field_type:ty - $(as $field_as:ty)? - $(= $field_default:expr, $field_default_string:literal)?, - )* - } - ) => { - $( #[$attr] )* - pub struct $t { - #[cfg_attr(feature = "serde", serde(flatten))] - pub base: $base, - - $( - $( #[$field_attr] )* - $(#[cfg_attr(feature = "serde", serde(default = $field_default_string))])? - pub $field: $field_type, - )* - } - - #[cfg(feature = "epee")] - ::cuprate_epee_encoding::epee_object! { - $t, - $( - $field: $field_type - $(as $field_as)? - $(= $field_default)?, - )* - !flatten: base: $base, - } - }; -} -pub(crate) use __define_response; - -//---------------------------------------------------------------------------------------------------- define_request_and_response_doc -/// Generate documentation for the types generated -/// 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 { - ( - // This labels the last `[request]` or `[response]` - // hyperlink in documentation. Input is either: - // - "request" - // - "response" - // - // Remember this is linking to the _other_ type, - // so if defining a `Request` type, input should - // be "response". - $request_or_response:literal => $request_or_response_type:ident, - - $monero_daemon_rpc_doc_link:ident, - $monero_code_commit:ident, - $monero_code_filename:ident, - $monero_code_filename_extension:ident, - $monero_code_line_start:literal, - $monero_code_line_end:literal, - ) => { - concat!( - "", - "[Definition](", - "https://github.com/monero-project/monero/blob/", - stringify!($monero_code_commit), - "/src/rpc/", - stringify!($monero_code_filename), - ".", - stringify!($monero_code_filename_extension), - "#L", - stringify!($monero_code_line_start), - "-L", - stringify!($monero_code_line_end), - "), [documentation](", - "https://www.getmonero.org/resources/developer-guides/daemon-rpc.html", - "#", - stringify!($monero_daemon_rpc_doc_link), - "), [", - $request_or_response, - "](", - stringify!($request_or_response_type), - ")." - ) - }; -} -pub(crate) use __define_request_and_response_doc; - -//---------------------------------------------------------------------------------------------------- Macro -/// Output a string link to `monerod` source code. -macro_rules! monero_definition_link { - ( - $commit:ident, // Git commit hash - $file_path:literal, // File path within `monerod`'s `src/`, e.g. `rpc/core_rpc_server_commands_defs.h` - $start:literal$(..=$end:literal)? // File lines, e.g. `0..=123` or `0` - ) => { - concat!( - "[Definition](https://github.com/monero-project/monero/blob/", - stringify!($commit), - "/src/", - $file_path, - "#L", - stringify!($start), - $( - "-L", - stringify!($end), - )? - ")." - ) - }; -} -pub(crate) use monero_definition_link; From 35907c5182dbe72c33dc132c855256a24da26ab7 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 21 Jul 2024 20:51:39 -0400 Subject: [PATCH 56/93] json_rpc_method: add `.is_restricted()` --- rpc/interface/src/json_rpc_method.rs | 117 +++++++++++++++++++++++++++ rpc/interface/src/lib.rs | 4 +- rpc/interface/src/method.rs | 31 ------- rpc/interface/src/route/json_rpc.rs | 5 +- 4 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 rpc/interface/src/json_rpc_method.rs delete mode 100644 rpc/interface/src/method.rs diff --git a/rpc/interface/src/json_rpc_method.rs b/rpc/interface/src/json_rpc_method.rs new file mode 100644 index 000000000..095a3d1c6 --- /dev/null +++ b/rpc/interface/src/json_rpc_method.rs @@ -0,0 +1,117 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import +use serde::{Deserialize, Serialize}; + +use cuprate_rpc_types::json::{ + AddAuxPowRequest, BannedRequest, CalcPowRequest, FlushCacheRequest, + FlushTransactionPoolRequest, GenerateBlocksRequest, GetAlternateChainsRequest, GetBansRequest, + GetBlockCountRequest, GetBlockHeaderByHashRequest, GetBlockHeaderByHeightRequest, + GetBlockHeadersRangeRequest, GetBlockRequest, GetCoinbaseTxSumRequest, GetConnectionsRequest, + GetFeeEstimateRequest, GetInfoRequest, GetLastBlockHeaderRequest, GetMinerDataRequest, + GetOutputHistogramRequest, GetTransactionPoolBacklogRequest, GetVersionRequest, + HardForkInfoRequest, OnGetBlockHashRequest, PruneBlockchainRequest, RelayTxRequest, + SetBansRequest, SubmitBlockRequest, SyncInfoRequest, +}; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +#[derive(Deserialize, Serialize)] +#[serde(tag = "method", content = "params")] +#[serde(rename_all = "snake_case")] +#[allow(missing_docs)] +pub enum JsonRpcMethod { + GetBlockCount(GetBlockCountRequest), + OnGetBlockHash(OnGetBlockHashRequest), + SubmitBlock(SubmitBlockRequest), + GenerateBlocks(GenerateBlocksRequest), + GetLastBlockHeader(GetLastBlockHeaderRequest), + GetBlockHeaderByHash(GetBlockHeaderByHashRequest), + GetBlockHeaderByHeight(GetBlockHeaderByHeightRequest), + GetBlockHeadersRange(GetBlockHeadersRangeRequest), + GetBlock(GetBlockRequest), + GetConnections(GetConnectionsRequest), + GetInfo(GetInfoRequest), + HardForkInfo(HardForkInfoRequest), + SetBans(SetBansRequest), + GetBans(GetBansRequest), + Banned(BannedRequest), + FlushTransactionPool(FlushTransactionPoolRequest), + GetOutputHistogram(GetOutputHistogramRequest), + GetCoinbaseTxSum(GetCoinbaseTxSumRequest), + GetVersion(GetVersionRequest), + GetFeeEstimate(GetFeeEstimateRequest), + GetAlternateChains(GetAlternateChainsRequest), + RelayTx(RelayTxRequest), + SyncInfo(SyncInfoRequest), + GetTransactionPoolBacklog(GetTransactionPoolBacklogRequest), + GetMinerData(GetMinerDataRequest), + PruneBlockchain(PruneBlockchainRequest), + CalcPow(CalcPowRequest), + FlushCache(FlushCacheRequest), + AddAuxPow(AddAuxPowRequest), +} + +impl JsonRpcMethod { + /// Returns `true` if this method should + /// only be allowed on local servers. + /// + /// If this returns `false`, it should be + /// okay to execute the method even on restricted + /// RPC servers. + /// + /// ```rust + /// use cuprate_rpc_interface::JsonRpcMethod; + /// + /// // Allowed method, even on restricted RPC servers (18089). + /// assert_eq!(JsonRpcMethod::GetBlockCount(()).is_restricted(), false); + /// + /// // Restricted methods, only allowed + /// // for unrestricted RPC servers (18081). + /// assert_eq!(JsonRpcMethod::GetConnections(()).is_restricted(), true); + /// ``` + pub const fn is_restricted(&self) -> bool { + match self { + // Normal methods. These are allowed + // even on restricted RPC servers (18089). + Self::GetBlockCount(()) + | Self::OnGetBlockHash(_) + | Self::SubmitBlock(_) + | Self::GetLastBlockHeader(_) + | Self::GetBlockHeaderByHash(_) + | Self::GetBlockHeaderByHeight(_) + | Self::GetBlockHeadersRange(_) + | Self::GetBlock(_) + | Self::GetInfo(()) + | Self::HardForkInfo(()) + | Self::GetOutputHistogram(_) + | Self::GetVersion(()) + | Self::GetFeeEstimate(()) + | Self::GetTransactionPoolBacklog(()) + | Self::GetMinerData(()) + | Self::AddAuxPow(_) => false, + + // Restricted methods. These are only allowed + // for unrestricted RPC servers (18081). + Self::GenerateBlocks(_) + | Self::GetConnections(()) + | Self::SetBans(_) + | Self::GetBans(()) + | Self::Banned(_) + | Self::FlushTransactionPool(_) + | Self::GetCoinbaseTxSum(_) + | Self::GetAlternateChains(()) + | Self::RelayTx(_) + | Self::SyncInfo(()) + | Self::PruneBlockchain(_) + | Self::CalcPow(_) + | Self::FlushCache(_) => true, + } + } +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index d2c91ae7b..d2fbda5cd 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -108,8 +108,8 @@ mod constants; mod error; mod free; +mod json_rpc_method; mod macros; -mod method; mod request; mod response; mod route; @@ -118,7 +118,7 @@ mod rpc_state; pub use error::Error; pub use free::create_router; -pub use method::Method; +pub use json_rpc_method::JsonRpcMethod; pub use request::Request; pub use response::Response; pub use rpc_handler::{ConcreteRpcHandler, RpcHandler}; diff --git a/rpc/interface/src/method.rs b/rpc/interface/src/method.rs deleted file mode 100644 index b06ad9660..000000000 --- a/rpc/interface/src/method.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import -use serde::{Deserialize, Serialize}; - -use cuprate_rpc_types::json::GetBlockRequest; - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -#[derive(Deserialize, Serialize)] -#[serde(tag = "method", content = "params")] -#[serde(rename_all = "snake_case")] -pub enum Method { - /// TODO - GetBlock(GetBlockRequest), -} - -impl Method { - /// TODO - pub const fn is_restricted(&self) -> bool { - match self { - Self::GetBlock(_) => false, - } - } -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs index 8eb3d4657..576a60600 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -9,7 +9,8 @@ use axum::Json; use tower::Service; use crate::{ - error::Error, method::Method, request::Request, response::Response, rpc_handler::RpcHandler, + error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, + rpc_handler::RpcHandler, }; //---------------------------------------------------------------------------------------------------- Struct definitions @@ -17,7 +18,7 @@ use crate::{ // pub(crate) async fn json_rpc( pub(crate) async fn json_rpc( // handler: Arc, - Json(request): Json>, + Json(request): Json>, ) { todo!() } From aaf99b86b120f0d3f8c8db5752a46847a96c05b1 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 22 Jul 2024 20:52:11 -0400 Subject: [PATCH 57/93] add route fn signatures --- Cargo.lock | 21 +- rpc/interface/Cargo.toml | 2 +- rpc/interface/src/free.rs | 98 +++-- rpc/interface/src/request.rs | 5 +- rpc/interface/src/response.rs | 5 +- rpc/interface/src/route/bin.rs | 183 ++++---- rpc/interface/src/route/json_rpc.rs | 23 +- rpc/interface/src/route/mod.rs | 4 +- rpc/interface/src/route/other.rs | 657 ++++++++++++---------------- rpc/interface/src/rpc_handler.rs | 22 +- 10 files changed, 459 insertions(+), 561 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9801529a..d437e7e54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,7 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", + "axum-macros", "bytes", "futures-util", "http", @@ -161,6 +162,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -389,7 +402,7 @@ version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.66", @@ -1257,6 +1270,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 92076a2ae..e8ae5b282 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -16,7 +16,7 @@ cuprate-epee-encoding = { path = "../../net/epee-encoding" } cuprate-json-rpc = { path = "../json-rpc" } cuprate-rpc-types = { path = "../types" } -axum = { version = "0.7.5" } +axum = { version = "0.7.5", features = ["macros"] } serde = { workspace = true } tower = { workspace = true } diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 90f76670e..28700d12a 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -3,67 +3,73 @@ //---------------------------------------------------------------------------------------------------- Use use std::{future::Future, marker::PhantomData}; -use axum::{routing::method_routing::get, Router}; +use axum::{extract::State, routing::method_routing::get, Router}; use tower::Service; use crate::{ - error::Error, request::Request, response::Response, route::json_rpc, rpc_handler::RpcHandler, + error::Error, + request::Request, + response::Response, + route::json_rpc, + route::{bin, other}, + rpc_handler::RpcHandler, RpcState, }; //---------------------------------------------------------------------------------------------------- Router /// TODO +#[rustfmt::skip] // 1 line per route. #[allow(clippy::needless_pass_by_value)] -pub fn create_router() -> Router { +pub fn create_router() -> Router { // List of `monerod` routes: // Router::new() // JSON-RPC route. - .route("/json_rpc", get(json_rpc)) + .route("/json_rpc", get(json_rpc::)) // Other JSON routes. - .route("/get_height", todo!()) - .route("/getheight", todo!()) - .route("/get_transactions", todo!()) - .route("/gettransactions", todo!()) - .route("/get_alt_blocks_hashes", todo!()) - .route("/is_key_image_spent", todo!()) - .route("/send_raw_transaction", todo!()) - .route("/sendrawtransaction", todo!()) - .route("/start_mining", todo!()) - .route("/stop_mining", todo!()) - .route("/mining_status", todo!()) - .route("/save_bc", todo!()) - .route("/get_peer_list", todo!()) - .route("/get_public_nodes", todo!()) - .route("/set_log_hash_rate", todo!()) - .route("/set_log_level", todo!()) - .route("/set_log_categories", todo!()) - .route("/get_transaction_pool", todo!()) - .route("/get_transaction_pool_hashes", todo!()) - .route("/get_transaction_pool_stats", todo!()) - .route("/set_bootstrap_daemon", todo!()) - .route("/stop_daemon", todo!()) - .route("/get_info", todo!()) - .route("/getinfo", todo!()) - .route("/get_net_stats", todo!()) - .route("/get_limit", todo!()) - .route("/set_limit", todo!()) - .route("/out_peers", todo!()) - .route("/in_peers", todo!()) - .route("/get_outs", todo!()) - .route("/update", todo!()) - .route("/pop_blocks", todo!()) + .route("/get_height", get(other::get_height::)) + .route("/getheight", get(other::get_height::)) + .route("/get_transactions", get(other::get_transactions::)) + .route("/gettransactions", get(other::get_transactions::)) + .route("/get_alt_blocks_hashes", get(other::get_alt_blocks_hashes::)) + .route("/is_key_image_spent", get(other::is_key_image_spent::)) + .route("/send_raw_transaction", get(other::send_raw_transaction::)) + .route("/sendrawtransaction", get(other::send_raw_transaction::)) + .route("/start_mining", get(other::start_mining::)) + .route("/stop_mining", get(other::stop_mining::)) + .route("/mining_status", get(other::mining_status::)) + .route("/save_bc", get(other::save_bc::)) + .route("/get_peer_list", get(other::get_peer_list::)) + .route("/get_public_nodes", get(other::get_public_nodes::)) + .route("/set_log_hash_rate", get(other::set_log_hash_rate::)) + .route("/set_log_level", get(other::set_log_level::)) + .route("/set_log_categories", get(other::set_log_categories::)) + .route("/get_transaction_pool", get(other::get_transaction_pool::)) + .route("/get_transaction_pool_hashes", get(other::get_transaction_pool_hashes::)) + .route("/get_transaction_pool_stats", get(other::get_transaction_pool_stats::)) + .route("/set_bootstrap_daemon", get(other::set_bootstrap_daemon::)) + .route("/stop_daemon", get(other::stop_daemon::)) + .route("/get_info", get(other::get_info::)) + .route("/getinfo", get(other::get_info::)) + .route("/get_net_stats", get(other::get_net_stats::)) + .route("/get_limit", get(other::get_limit::)) + .route("/set_limit", get(other::set_limit::)) + .route("/out_peers", get(other::out_peers::)) + .route("/in_peers", get(other::in_peers::)) + .route("/get_outs", get(other::get_outs::)) + .route("/update", get(other::update::)) + .route("/pop_blocks", get(other::pop_blocks::)) // Binary routes. - .route("/get_blocks.bin", todo!()) - .route("/getblocks.bin", todo!()) - .route("/get_blocks_by_height.bin", todo!()) - .route("/getblocks_by_height.bin", todo!()) - .route("/get_hashes.bin", todo!()) - .route("/gethashes.bin", todo!()) - .route("/get_o_indexes.bin", todo!()) - .route("/get_outs.bin", todo!()) - .route("/get_transaction_pool_hashes.bin", todo!()) - .route("/get_output_distribution.bin", todo!()) + .route("/get_blocks.bin", get(bin::get_blocks::)) + .route("/getblocks.bin", get(bin::get_blocks::)) + .route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::)) + .route("/getblocks_by_height.bin", get(bin::get_blocks_by_height::)) + .route("/get_hashes.bin", get(bin::get_hashes::)) + .route("/gethashes.bin", get(bin::get_hashes::)) + .route("/get_o_indexes.bin", get(bin::get_o_indexes::)) + .route("/get_outs.bin", get(bin::get_outs::)) + .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) + .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) // Unknown route. .route("/*", todo!()) } diff --git a/rpc/interface/src/request.rs b/rpc/interface/src/request.rs index 6223f2819..dbb9fd1bd 100644 --- a/rpc/interface/src/request.rs +++ b/rpc/interface/src/request.rs @@ -4,7 +4,10 @@ //---------------------------------------------------------------------------------------------------- TODO /// TODO -pub enum Request {} +pub enum Request { + /// TODO + Todo, +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/response.rs b/rpc/interface/src/response.rs index acbdb7c7d..2ab68343a 100644 --- a/rpc/interface/src/response.rs +++ b/rpc/interface/src/response.rs @@ -4,7 +4,10 @@ //---------------------------------------------------------------------------------------------------- Status /// TODO -pub enum Response {} +pub enum Response { + /// TODO + Todo, +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 35d6e0f88..1c1a4235b 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -3,120 +3,89 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +use std::{future::Future, sync::Arc}; -//---------------------------------------------------------------------------------------------------- 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]>, -// } -// } +use axum::{extract::State, http::StatusCode, Json}; +use tower::{Service, ServiceExt}; -// define_request_and_response! { -// get_blocks_by_heightbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 264..=286, -// GetBlocksByHeight, -// Request { -// heights: Vec, -// }, -// AccessResponseBase { -// blocks: Vec, -// } -// } +use crate::{ + error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, + rpc_handler::RpcHandler, rpc_state::RpcState, +}; -// define_request_and_response! { -// get_hashesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 309..=338, -// GetHashes, -// Request { -// block_ids: ByteArrayVec<32>, -// start_height: u64, -// }, -// AccessResponseBase { -// m_blocks_ids: ByteArrayVec<32>, -// start_height: u64, -// current_height: u64, -// } -// } +//---------------------------------------------------------------------------------------------------- Macro +/// TODO +macro_rules! return_if_restricted { + ($handler:ident) => { + if $handler.state().restricted() { + return Ok("TODO"); + } + }; +} + +//---------------------------------------------------------------------------------------------------- Struct definitions +/// TODO +pub(crate) async fn get_blocks( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} -// #[cfg(not(feature = "epee"))] -// define_request_and_response! { -// get_o_indexesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 487..=510, -// GetOutputIndexes, -// #[derive(Copy)] -// Request { -// txid: [u8; 32], -// }, -// AccessResponseBase { -// o_indexes: Vec, -// } -// } +/// TODO +pub(crate) async fn get_blocks_by_height( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} -// #[cfg(feature = "epee")] -// define_request_and_response! { -// get_o_indexesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 487..=510, -// GetOutputIndexes, -// #[derive(Copy)] -// Request { -// txid: [u8; 32], -// }, -// AccessResponseBase { -// o_indexes: Vec as ContainerAsBlob, -// } -// } +/// TODO +pub(crate) async fn get_hashes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} -// define_request_and_response! { -// get_outsbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 512..=565, -// GetOuts, -// Request { -// outputs: Vec, -// get_txid: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// outs: Vec, -// } -// } +/// TODO +pub(crate) async fn get_o_indexes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} -// define_request_and_response! { -// get_transaction_pool_hashesbin, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1593..=1613, -// GetTransactionPoolHashes, -// Request {}, -// AccessResponseBase { -// tx_hashes: ByteArrayVec<32>, -// } -// } +/// TODO +pub(crate) async fn get_outs( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_transaction_pool_hashes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_output_distribution( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs index 576a60600..20a9c0c24 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -5,22 +5,29 @@ //---------------------------------------------------------------------------------------------------- Import use std::{future::Future, sync::Arc}; -use axum::Json; -use tower::Service; +use axum::{extract::State, http::StatusCode, Json}; +use tower::{Service, ServiceExt}; use crate::{ error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, - rpc_handler::RpcHandler, + rpc_handler::RpcHandler, rpc_state::RpcState, }; //---------------------------------------------------------------------------------------------------- Struct definitions /// TODO -// pub(crate) async fn json_rpc( -pub(crate) async fn json_rpc( - // handler: Arc, +pub(crate) async fn json_rpc( + State(handler): State, Json(request): Json>, -) { - todo!() +) -> Result, StatusCode> { + if handler.state().restricted() && request.body.is_restricted() { + // const RESTRICTED: Response = Response::Todo; + const RESTRICTED: &str = "TODO"; + return Ok(Json(RESTRICTED)); + } + + /* call handler */ + + Ok(Json("TODO")) } // // This generates 2 structs: diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 09c2afc93..e270d2592 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,7 +1,7 @@ //! TODO -mod bin; +pub(crate) mod bin; mod json_rpc; -mod other; +pub(crate) mod other; pub(crate) use json_rpc::json_rpc; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 2203d75f1..fd3bad32d 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -3,395 +3,278 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +use std::{future::Future, sync::Arc}; + +use axum::{extract::State, http::StatusCode, Json}; +use tower::{Service, ServiceExt}; + +use crate::{ + error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, + rpc_handler::RpcHandler, rpc_state::RpcState, +}; + +//---------------------------------------------------------------------------------------------------- Macro +/// TODO +macro_rules! return_if_restricted { + ($handler:ident) => { + if $handler.state().restricted() { + return Ok("TODO"); + } + }; +} //---------------------------------------------------------------------------------------------------- TODO -// define_request_and_response! { -// get_height, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 138..=160, -// GetHeight, -// Request {}, -// ResponseBase { -// hash: String, -// height: u64, -// } -// } - -// define_request_and_response! { -// get_transactions, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 370..=451, -// GetTransactions, -// Request { -// txs_hashes: Vec, -// // FIXME: this is documented as optional but it isn't serialized as an optional -// // but it is set _somewhere_ to false in `monerod` -// // -// decode_as_json: bool = default_false(), "default_false", -// prune: bool = default_false(), "default_false", -// split: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// txs_as_hex: Vec, -// txs_as_json: Vec, -// missed_tx: Vec, -// txs: Vec, -// } -// } - -// define_request_and_response! { -// get_alt_blocks_hashes, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 288..=308, -// GetAltBlocksHashes, -// Request {}, -// AccessResponseBase { -// blks_hashes: Vec, -// } -// } - -// define_request_and_response! { -// is_key_image_spent, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 454..=484, -// IsKeyImageSpent, -// Request { -// key_images: Vec, -// }, -// AccessResponseBase { -// spent_status: Vec, // TODO: should be `KeyImageSpentStatus`. -// } -// } - -// define_request_and_response! { -// send_raw_transaction, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 370..=451, -// SendRawTransaction, -// Request { -// tx_as_hex: String, -// do_not_relay: bool = default_false(), "default_false", -// do_sanity_checks: bool = default_true(), "default_true", -// }, -// AccessResponseBase { -// double_spend: bool, -// fee_too_low: bool, -// invalid_input: bool, -// invalid_output: bool, -// low_mixin: bool, -// nonzero_unlock_time: bool, -// not_relayed: bool, -// overspend: bool, -// reason: String, -// sanity_check_failed: bool, -// too_big: bool, -// too_few_outputs: bool, -// tx_extra_too_big: bool, -// } -// } - -// define_request_and_response! { -// start_mining, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 665..=691, -// StartMining, -// Request { -// miner_address: String, -// threads_count: u64, -// do_background_mining: bool, -// ignore_battery: bool, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// stop_mining, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 825..=843, -// StopMining, -// Request {}, -// ResponseBase {} -// } - -// define_request_and_response! { -// mining_status, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 846..=895, -// MiningStatus, -// Request {}, -// ResponseBase { -// active: bool, -// address: String, -// bg_idle_threshold: u8, -// bg_ignore_battery: bool, -// bg_min_idle_seconds: u8, -// bg_target: u8, -// block_reward: u64, -// block_target: u32, -// difficulty: u64, -// difficulty_top64: u64, -// is_background_mining_enabled: bool, -// pow_algorithm: String, -// speed: u64, -// threads_count: u32, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// save_bc, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 898..=916, -// SaveBc, -// Request {}, -// ResponseBase {} -// } - -// define_request_and_response! { -// get_peer_list, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1369..=1417, -// GetPeerList, -// Request { -// public_only: bool = default_true(), "default_true", -// include_blocked: bool = default_false(), "default_false", -// }, -// ResponseBase { -// white_list: Vec, -// gray_list: Vec, -// } -// } - -// define_request_and_response! { -// set_log_hash_rate, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1450..=1470, -// SetLogHashRate, -// #[derive(Copy)] -// Request { -// visible: bool, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// set_log_level, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1450..=1470, -// SetLogLevel, -// #[derive(Copy)] -// Request { -// level: u8, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// set_log_categories, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1494..=1517, -// SetLogCategories, -// Request { -// categories: String = default_string(), "default_string", -// }, -// ResponseBase { -// categories: String, -// } -// } - -// define_request_and_response! { -// set_bootstrap_daemon, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1785..=1812, -// SetBootstrapDaemon, -// Request { -// address: String, -// username: String, -// password: String, -// proxy: String, -// }, -// #[derive(Copy)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_transaction_pool, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1569..=1591, -// GetTransactionPool, -// Request {}, -// AccessResponseBase { -// transactions: Vec, -// spent_key_images: Vec, -// } -// } - -// define_request_and_response! { -// get_transaction_pool_stats, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1712..=1732, -// GetTransactionPoolStats, -// Request {}, -// AccessResponseBase { -// pool_stats: TxpoolStats, -// } -// } - -// define_request_and_response! { -// stop_daemon, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1814..=1831, -// StopDaemon, -// Request {}, -// ResponseBase { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_limit, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1852..=1874, -// GetLimit, -// Request {}, -// ResponseBase { -// limit_down: u64, -// limit_up: u64, -// } -// } - -// define_request_and_response! { -// set_limit, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1876..=1903, -// SetLimit, -// Request { -// limit_down: i64, -// limit_up: i64, -// }, -// ResponseBase { -// limit_down: i64, -// limit_up: i64, -// } -// } - -// define_request_and_response! { -// out_peers, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1876..=1903, -// OutPeers, -// Request { -// set: bool = default_true(), "default_true", -// out_peers: u32, -// }, -// ResponseBase { -// out_peers: u32, -// } -// } - -// define_request_and_response! { -// get_net_stats, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 793..=822, -// GetNetStats, -// Request {}, -// ResponseBase { -// start_time: u64, -// total_packets_in: u64, -// total_bytes_in: u64, -// total_packets_out: u64, -// total_bytes_out: u64, -// } -// } - -// define_request_and_response! { -// get_outs, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 567..=609, -// GetOuts, -// Request { -// outputs: Vec, -// get_txid: bool, -// }, -// ResponseBase { -// outs: Vec, -// } -// } - -// define_request_and_response! { -// update, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2324..=2359, -// Update, -// Request { -// command: String, -// path: String = default_string(), "default_string", -// }, -// ResponseBase { -// auto_uri: String, -// hash: String, -// path: String, -// update: bool, -// user_uri: String, -// version: String, -// } -// } - -// define_request_and_response! { -// pop_blocks, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2722..=2745, -// PopBlocks, -// Request { -// nblocks: u64, -// }, -// ResponseBase { -// height: u64, -// } -// } - -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2798..=2823, -// GetTxIdsLoose, -// Request { -// txid_template: String, -// num_matching_bits: u32, -// }, -// ResponseBase { -// txids: Vec, -// } -// } - -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1615..=1635, -// GetTransactionPoolHashes, -// Request {}, -// ResponseBase { -// tx_hashes: Vec, -// } -// } - -// define_request_and_response! { -// UNDOCUMENTED_ENDPOINT, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1419..=1448, -// GetPublicNodes, -// Request { -// gray: bool = default_false(), "default_false", -// white: bool = default_true(), "default_true", -// include_blocked: bool = default_false(), "default_false", -// }, -// ResponseBase { -// gray: Vec, -// white: Vec, -// } -// } +/// TODO +pub(crate) async fn get_height( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_transactions( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_alt_blocks_hashes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn is_key_image_spent( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn send_raw_transaction( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn start_mining( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn stop_mining( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn mining_status( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn save_bc( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_peer_list( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_public_nodes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn set_log_hash_rate( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn set_log_level( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn set_log_categories( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_transaction_pool( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_transaction_pool_hashes( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_transaction_pool_stats( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn set_bootstrap_daemon( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn stop_daemon( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_info( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_net_stats( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_limit( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn set_limit( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn out_peers( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn in_peers( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn get_outs( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn update( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} + +/// TODO +pub(crate) async fn pop_blocks( + State(handler): State, +) -> Result<&'static str, StatusCode> { + return_if_restricted!(handler); + /* call handler */ + Ok("TODO") +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 969926c14..a50ef21e4 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -3,6 +3,7 @@ //---------------------------------------------------------------------------------------------------- Use use std::{future::Future, marker::PhantomData, sync::Arc}; +use axum::extract::State; use tower::Service; use crate::{ @@ -11,9 +12,9 @@ use crate::{ //---------------------------------------------------------------------------------------------------- TODO /// TODO -pub trait RpcHandler: Send + Sync + 'static { +pub trait RpcHandler: Clone + Send + Sync + 'static { /// TODO - type RpcState: RpcState; + type State: RpcState; /// TODO type Handler: Send + Sync + 'static + Service; @@ -23,7 +24,10 @@ pub trait RpcHandler: Send + Sync + 'static { // >::Future: Future>; /// TODO - fn state(&self) -> Self::RpcState; + fn state(&self) -> &Self::State; + + /// TODO + fn handler(&self) -> Self::Handler; } /// TODO @@ -35,15 +39,19 @@ pub struct ConcreteRpcHandler { impl RpcHandler for ConcreteRpcHandler where - H: Send + Sync + 'static + Service, + H: Clone + Send + Sync + 'static + Service, >::Response: Into, >::Error: Into, >::Future: Future>, { - type RpcState = ConcreteRpcState; + type State = ConcreteRpcState; type Handler = H; - fn state(&self) -> Self::RpcState { - self.state + fn state(&self) -> &Self::State { + &self.state + } + + fn handler(&self) -> Self::Handler { + todo!() } } From e4097e5a651eb4437a8f335cbc96b4cb9c78507b Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 23 Jul 2024 17:30:49 -0400 Subject: [PATCH 58/93] types: add rpc enums --- rpc/types/src/bin.rs | 47 ++++++++++++++ rpc/types/src/json.rs | 119 +++++++++++++++++++++++++++++++++++ rpc/types/src/lib.rs | 2 + rpc/types/src/other.rs | 114 +++++++++++++++++++++++++++++++++ rpc/types/src/rpc_request.rs | 32 ++++++++++ 5 files changed, 314 insertions(+) create mode 100644 rpc/types/src/rpc_request.rs diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 3dcfb9674..6d716f0b2 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -5,6 +5,9 @@ //---------------------------------------------------------------------------------------------------- Import use cuprate_fixed_bytes::ByteArrayVec; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[cfg(feature = "epee")] use cuprate_epee_encoding::container_as_blob::ContainerAsBlob; @@ -18,6 +21,7 @@ use crate::{ GetBan, GetOutputsOut, HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, + RpcRequest, }; //---------------------------------------------------------------------------------------------------- TODO @@ -134,6 +138,49 @@ define_request_and_response! { } } +//---------------------------------------------------------------------------------------------------- Request +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum BinRequest { + GetBlocks(GetBlocksRequest), + GetBlocksByHeight(GetBlocksByHeightRequest), + GetHashes(GetHashesRequest), + GetOutputIndexes(GetOutputIndexesRequest), + GetOuts(GetOutsRequest), + GetTransactionPoolHashes(GetTransactionPoolHashesRequest), +} + +impl RpcRequest for BinRequest { + /// All binary methods are un-restricted, i.e. + // all of them will return `false`. + fn is_restricted(&self) -> bool { + match self { + Self::GetBlocks(_) + | Self::GetBlocksByHeight(_) + | Self::GetHashes(_) + | Self::GetOutputIndexes(_) + | Self::GetOuts(_) + | Self::GetTransactionPoolHashes(()) => false, + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum JsonRpcResponse { + GetBlocks(GetBlocksResponse), + GetBlocksByHeight(GetBlocksByHeightResponse), + GetHashes(GetHashesResponse), + GetOutputIndexes(GetOutputIndexesResponse), + GetOuts(GetOutsResponse), + GetTransactionPoolHashes(GetTransactionPoolHashesResponse), +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 2e7aa82ac..fdb2a0358 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -3,6 +3,9 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::{ base::{AccessResponseBase, ResponseBase}, defaults::{default_false, default_height, default_string, default_vec, default_zero}, @@ -12,6 +15,7 @@ use crate::{ AuxPow, BlockHeader, ChainInfo, ConnectionInfo, GetBan, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, }, + RpcRequest, }; //---------------------------------------------------------------------------------------------------- Struct definitions @@ -663,6 +667,121 @@ define_request_and_response! { } } +//---------------------------------------------------------------------------------------------------- Request +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum JsonRpcRequest { + GetBlockCount(GetBlockCountRequest), + OnGetBlockHash(OnGetBlockHashRequest), + SubmitBlock(SubmitBlockRequest), + GenerateBlocks(GenerateBlocksRequest), + GetLastBlockHeader(GetLastBlockHeaderRequest), + GetBlockHeaderByHash(GetBlockHeaderByHashRequest), + GetBlockHeaderByHeight(GetBlockHeaderByHeightRequest), + GetBlockHeadersRange(GetBlockHeadersRangeRequest), + GetBlock(GetBlockRequest), + GetConnections(GetConnectionsRequest), + GetInfo(GetInfoRequest), + HardForkInfo(HardForkInfoRequest), + SetBans(SetBansRequest), + GetBans(GetBansRequest), + Banned(BannedRequest), + FlushTransactionPool(FlushTransactionPoolRequest), + GetOutputHistogram(GetOutputHistogramRequest), + GetCoinbaseTxSum(GetCoinbaseTxSumRequest), + GetVersion(GetVersionRequest), + GetFeeEstimate(GetFeeEstimateRequest), + GetAlternateChains(GetAlternateChainsRequest), + RelayTx(RelayTxRequest), + SyncInfo(SyncInfoRequest), + GetTransactionPoolBacklog(GetTransactionPoolBacklogRequest), + GetMinerData(GetMinerDataRequest), + PruneBlockchain(PruneBlockchainRequest), + CalcPow(CalcPowRequest), + FlushCache(FlushCacheRequest), + AddAuxPow(AddAuxPowRequest), +} + +impl RpcRequest for JsonRpcRequest { + fn is_restricted(&self) -> bool { + match self { + // Normal methods. These are allowed + // even on restricted RPC servers (18089). + Self::GetBlockCount(()) + | Self::OnGetBlockHash(_) + | Self::SubmitBlock(_) + | Self::GetLastBlockHeader(_) + | Self::GetBlockHeaderByHash(_) + | Self::GetBlockHeaderByHeight(_) + | Self::GetBlockHeadersRange(_) + | Self::GetBlock(_) + | Self::GetInfo(()) + | Self::HardForkInfo(()) + | Self::GetOutputHistogram(_) + | Self::GetVersion(()) + | Self::GetFeeEstimate(()) + | Self::GetTransactionPoolBacklog(()) + | Self::GetMinerData(()) + | Self::AddAuxPow(_) => false, + + // Restricted methods. These are only allowed + // for unrestricted RPC servers (18081). + Self::GenerateBlocks(_) + | Self::GetConnections(()) + | Self::SetBans(_) + | Self::GetBans(()) + | Self::Banned(_) + | Self::FlushTransactionPool(_) + | Self::GetCoinbaseTxSum(_) + | Self::GetAlternateChains(()) + | Self::RelayTx(_) + | Self::SyncInfo(()) + | Self::PruneBlockchain(_) + | Self::CalcPow(_) + | Self::FlushCache(_) => true, + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum JsonRpcResponse { + GetBlockCount(GetBlockCountResponse), + OnGetBlockHash(OnGetBlockHashResponse), + SubmitBlock(SubmitBlockResponse), + GenerateBlocks(GenerateBlocksResponse), + GetLastBlockHeader(GetLastBlockHeaderResponse), + GetBlockHeaderByHash(GetBlockHeaderByHashResponse), + GetBlockHeaderByHeight(GetBlockHeaderByHeightResponse), + GetBlockHeadersRange(GetBlockHeadersRangeResponse), + GetBlock(GetBlockResponse), + GetConnections(GetConnectionsResponse), + GetInfo(GetInfoResponse), + HardForkInfo(HardForkInfoResponse), + SetBans(SetBansResponse), + GetBans(GetBansResponse), + Banned(BannedResponse), + FlushTransactionPool(FlushTransactionPoolResponse), + GetOutputHistogram(GetOutputHistogramResponse), + GetCoinbaseTxSum(GetCoinbaseTxSumResponse), + GetVersion(GetVersionResponse), + GetFeeEstimate(GetFeeEstimateResponse), + GetAlternateChains(GetAlternateChainsResponse), + RelayTx(RelayTxResponse), + SyncInfo(SyncInfoResponse), + GetTransactionPoolBacklog(GetTransactionPoolBacklogResponse), + GetMinerData(GetMinerDataResponse), + PruneBlockchain(PruneBlockchainResponse), + CalcPow(CalcPowResponse), + FlushCache(FlushCacheResponse), + AddAuxPow(AddAuxPowResponse), +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 45cca69c7..a8337154f 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -112,6 +112,7 @@ mod constants; mod defaults; mod free; mod macros; +mod rpc_request; pub mod base; pub mod bin; @@ -124,3 +125,4 @@ pub use constants::{ CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_STATUS_UNKNOWN, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR, }; +pub use rpc_request::RpcRequest; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 5ad2caacd..3e9742806 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -3,6 +3,9 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::{ base::{AccessResponseBase, ResponseBase}, defaults::{default_false, default_string, default_true}, @@ -11,6 +14,7 @@ use crate::{ GetOutputsOut, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, TxpoolStats, }, + RpcRequest, }; //---------------------------------------------------------------------------------------------------- TODO @@ -402,6 +406,116 @@ define_request_and_response! { } } +//---------------------------------------------------------------------------------------------------- Request +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum OtherRequest { + GetHeight(GetHeightRequest), + GetTransactions(GetTransactionsRequest), + GetAltBlocksHashes(GetAltBlocksHashesRequest), + IsKeyImageSpent(IsKeyImageSpentRequest), + SendRawTransaction(SendRawTransactionRequest), + StartMining(StartMiningRequest), + StopMining(StopMiningRequest), + MiningStatus(MiningStatusRequest), + SaveBc(SaveBcRequest), + GetPeerList(GetPeerListRequest), + SetLogHashRate(SetLogHashRateRequest), + SetLogLevel(SetLogLevelRequest), + SetLogCategories(SetLogCategoriesRequest), + SetBootstrapDaemon(SetBootstrapDaemonRequest), + GetTransactionPool(GetTransactionPoolRequest), + GetTransactionPoolStats(GetTransactionPoolStatsRequest), + StopDaemon(StopDaemonRequest), + GetLimit(GetLimitRequest), + SetLimit(SetLimitRequest), + OutPeers(OutPeersRequest), + GetNetStats(GetNetStatsRequest), + GetOuts(GetOutsRequest), + Update(UpdateRequest), + PopBlocks(PopBlocksRequest), + GetTxIdsLoose(GetTxIdsLooseRequest), + GetTransactionPoolHashes(GetTransactionPoolHashesRequest), + GetPublicNodes(GetPublicNodesRequest), +} + +impl RpcRequest for OtherRequest { + fn is_restricted(&self) -> bool { + match self { + // Normal methods. These are allowed + // even on restricted RPC servers (18089). + Self::GetHeight(()) + | Self::GetTransactions(_) + | Self::GetAltBlocksHashes(()) + | Self::IsKeyImageSpent(_) + | Self::SendRawTransaction(_) + | Self::GetTransactionPool(()) + | Self::GetTransactionPoolStats(()) + | Self::GetLimit(()) + | Self::GetOuts(_) + | Self::GetTxIdsLoose(_) + | Self::GetTransactionPoolHashes(()) => false, + + // Restricted methods. These are only allowed + // for unrestricted RPC servers (18081). + // TODO + Self::StartMining(_) + | Self::StopMining(()) + | Self::MiningStatus(()) + | Self::SaveBc(()) + | Self::GetPeerList(_) + | Self::SetLogHashRate(_) + | Self::SetLogLevel(_) + | Self::SetLogCategories(_) + | Self::SetBootstrapDaemon(_) + | Self::GetNetStats(()) + | Self::SetLimit(_) + | Self::StopDaemon(()) + | Self::OutPeers(_) + | Self::Update(_) + | Self::PopBlocks(_) + | Self::GetPublicNodes(_) => true, + } + } +} + +//---------------------------------------------------------------------------------------------------- Response +/// TODO +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] +#[allow(missing_docs)] +pub enum OtherResponse { + GetHeight(GetHeightResponse), + GetTransactions(GetTransactionsResponse), + GetAltBlocksHashes(GetAltBlocksHashesResponse), + IsKeyImageSpent(IsKeyImageSpentResponse), + SendRawTransaction(SendRawTransactionResponse), + StartMining(StartMiningResponse), + StopMining(StopMiningResponse), + MiningStatus(MiningStatusResponse), + SaveBc(SaveBcResponse), + GetPeerList(GetPeerListResponse), + SetLogHashRate(SetLogHashRateResponse), + SetLogLevel(SetLogLevelResponse), + SetLogCategories(SetLogCategoriesResponse), + SetBootstrapDaemon(SetBootstrapDaemonResponse), + GetTransactionPool(GetTransactionPoolResponse), + GetTransactionPoolStats(GetTransactionPoolStatsResponse), + StopDaemon(StopDaemonResponse), + GetLimit(GetLimitResponse), + SetLimit(SetLimitResponse), + OutPeers(OutPeersResponse), + GetNetStats(GetNetStatsResponse), + GetOuts(GetOutsResponse), + Update(UpdateResponse), + PopBlocks(PopBlocksResponse), + GetTxIdsLoose(GetTxIdsLooseResponse), + GetTransactionPoolHashes(GetTransactionPoolHashesResponse), + GetPublicNodes(GetPublicNodesResponse), +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/rpc_request.rs b/rpc/types/src/rpc_request.rs new file mode 100644 index 000000000..6ec596a22 --- /dev/null +++ b/rpc/types/src/rpc_request.rs @@ -0,0 +1,32 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Import + +//---------------------------------------------------------------------------------------------------- Struct definitions +/// TODO +pub trait RpcRequest { + /// Returns `true` if this method should + /// only be allowed on local servers. + /// + /// If this returns `false`, it should be + /// okay to execute the method even on restricted + /// RPC servers. + /// + /// ```rust + /// use cuprate_rpc_types::JsonRpcRequest; + /// + /// // Allowed method, even on restricted RPC servers (18089). + /// assert_eq!(JsonRpcRequest::GetBlockCount(()).is_restricted(), false); + /// + /// // Restricted methods, only allowed + /// // for unrestricted RPC servers (18081). + /// assert_eq!(JsonRpcRequest::GetConnections(()).is_restricted(), true); + /// ``` + fn is_restricted(&self) -> bool; +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} From 2c3ebfebfee72db106425b4d6985f916772e7a49 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 23 Jul 2024 17:51:44 -0400 Subject: [PATCH 59/93] interface: routes, types --- rpc/interface/src/json_rpc_method.rs | 117 ----- rpc/interface/src/lib.rs | 4 +- rpc/interface/src/request.rs | 7 +- rpc/interface/src/response.rs | 7 +- rpc/interface/src/route/bin.rs | 129 +++-- rpc/interface/src/route/json_rpc.rs | 692 ++------------------------- rpc/interface/src/route/mod.rs | 2 + rpc/interface/src/route/other.rs | 407 +++++++++++----- rpc/interface/src/route/unknown.rs | 674 ++++++++++++++++++++++++++ rpc/types/src/bin.rs | 2 +- 10 files changed, 1111 insertions(+), 930 deletions(-) delete mode 100644 rpc/interface/src/json_rpc_method.rs create mode 100644 rpc/interface/src/route/unknown.rs diff --git a/rpc/interface/src/json_rpc_method.rs b/rpc/interface/src/json_rpc_method.rs deleted file mode 100644 index 095a3d1c6..000000000 --- a/rpc/interface/src/json_rpc_method.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import -use serde::{Deserialize, Serialize}; - -use cuprate_rpc_types::json::{ - AddAuxPowRequest, BannedRequest, CalcPowRequest, FlushCacheRequest, - FlushTransactionPoolRequest, GenerateBlocksRequest, GetAlternateChainsRequest, GetBansRequest, - GetBlockCountRequest, GetBlockHeaderByHashRequest, GetBlockHeaderByHeightRequest, - GetBlockHeadersRangeRequest, GetBlockRequest, GetCoinbaseTxSumRequest, GetConnectionsRequest, - GetFeeEstimateRequest, GetInfoRequest, GetLastBlockHeaderRequest, GetMinerDataRequest, - GetOutputHistogramRequest, GetTransactionPoolBacklogRequest, GetVersionRequest, - HardForkInfoRequest, OnGetBlockHashRequest, PruneBlockchainRequest, RelayTxRequest, - SetBansRequest, SubmitBlockRequest, SyncInfoRequest, -}; - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -#[derive(Deserialize, Serialize)] -#[serde(tag = "method", content = "params")] -#[serde(rename_all = "snake_case")] -#[allow(missing_docs)] -pub enum JsonRpcMethod { - GetBlockCount(GetBlockCountRequest), - OnGetBlockHash(OnGetBlockHashRequest), - SubmitBlock(SubmitBlockRequest), - GenerateBlocks(GenerateBlocksRequest), - GetLastBlockHeader(GetLastBlockHeaderRequest), - GetBlockHeaderByHash(GetBlockHeaderByHashRequest), - GetBlockHeaderByHeight(GetBlockHeaderByHeightRequest), - GetBlockHeadersRange(GetBlockHeadersRangeRequest), - GetBlock(GetBlockRequest), - GetConnections(GetConnectionsRequest), - GetInfo(GetInfoRequest), - HardForkInfo(HardForkInfoRequest), - SetBans(SetBansRequest), - GetBans(GetBansRequest), - Banned(BannedRequest), - FlushTransactionPool(FlushTransactionPoolRequest), - GetOutputHistogram(GetOutputHistogramRequest), - GetCoinbaseTxSum(GetCoinbaseTxSumRequest), - GetVersion(GetVersionRequest), - GetFeeEstimate(GetFeeEstimateRequest), - GetAlternateChains(GetAlternateChainsRequest), - RelayTx(RelayTxRequest), - SyncInfo(SyncInfoRequest), - GetTransactionPoolBacklog(GetTransactionPoolBacklogRequest), - GetMinerData(GetMinerDataRequest), - PruneBlockchain(PruneBlockchainRequest), - CalcPow(CalcPowRequest), - FlushCache(FlushCacheRequest), - AddAuxPow(AddAuxPowRequest), -} - -impl JsonRpcMethod { - /// Returns `true` if this method should - /// only be allowed on local servers. - /// - /// If this returns `false`, it should be - /// okay to execute the method even on restricted - /// RPC servers. - /// - /// ```rust - /// use cuprate_rpc_interface::JsonRpcMethod; - /// - /// // Allowed method, even on restricted RPC servers (18089). - /// assert_eq!(JsonRpcMethod::GetBlockCount(()).is_restricted(), false); - /// - /// // Restricted methods, only allowed - /// // for unrestricted RPC servers (18081). - /// assert_eq!(JsonRpcMethod::GetConnections(()).is_restricted(), true); - /// ``` - pub const fn is_restricted(&self) -> bool { - match self { - // Normal methods. These are allowed - // even on restricted RPC servers (18089). - Self::GetBlockCount(()) - | Self::OnGetBlockHash(_) - | Self::SubmitBlock(_) - | Self::GetLastBlockHeader(_) - | Self::GetBlockHeaderByHash(_) - | Self::GetBlockHeaderByHeight(_) - | Self::GetBlockHeadersRange(_) - | Self::GetBlock(_) - | Self::GetInfo(()) - | Self::HardForkInfo(()) - | Self::GetOutputHistogram(_) - | Self::GetVersion(()) - | Self::GetFeeEstimate(()) - | Self::GetTransactionPoolBacklog(()) - | Self::GetMinerData(()) - | Self::AddAuxPow(_) => false, - - // Restricted methods. These are only allowed - // for unrestricted RPC servers (18081). - Self::GenerateBlocks(_) - | Self::GetConnections(()) - | Self::SetBans(_) - | Self::GetBans(()) - | Self::Banned(_) - | Self::FlushTransactionPool(_) - | Self::GetCoinbaseTxSum(_) - | Self::GetAlternateChains(()) - | Self::RelayTx(_) - | Self::SyncInfo(()) - | Self::PruneBlockchain(_) - | Self::CalcPow(_) - | Self::FlushCache(_) => true, - } - } -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index d2fbda5cd..d962ae4d0 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -102,13 +102,12 @@ ) )] // TODO: remove me after finishing impl -#![allow(dead_code, unreachable_code)] +#![allow(dead_code, unreachable_code, clippy::diverging_sub_expression)] //---------------------------------------------------------------------------------------------------- Mod mod constants; mod error; mod free; -mod json_rpc_method; mod macros; mod request; mod response; @@ -118,7 +117,6 @@ mod rpc_state; pub use error::Error; pub use free::create_router; -pub use json_rpc_method::JsonRpcMethod; pub use request::Request; pub use response::Response; pub use rpc_handler::{ConcreteRpcHandler, RpcHandler}; diff --git a/rpc/interface/src/request.rs b/rpc/interface/src/request.rs index dbb9fd1bd..65b404c2c 100644 --- a/rpc/interface/src/request.rs +++ b/rpc/interface/src/request.rs @@ -1,12 +1,17 @@ //! TODO //---------------------------------------------------------------------------------------------------- Import +use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest}; //---------------------------------------------------------------------------------------------------- TODO /// TODO pub enum Request { /// TODO - Todo, + JsonRpc(cuprate_json_rpc::Request), + /// TODO + Binary(BinRequest), + /// TODO + Other(OtherRequest), } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/interface/src/response.rs b/rpc/interface/src/response.rs index 2ab68343a..f37e41bea 100644 --- a/rpc/interface/src/response.rs +++ b/rpc/interface/src/response.rs @@ -1,12 +1,17 @@ //! TODO //---------------------------------------------------------------------------------------------------- Import +use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse}; //---------------------------------------------------------------------------------------------------- Status /// TODO pub enum Response { /// TODO - Todo, + JsonRpc(cuprate_json_rpc::Response), + /// TODO + Binary(BinResponse), + /// TODO + Other(OtherResponse), } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 1c1a4235b..41c439f0a 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -5,20 +5,27 @@ //---------------------------------------------------------------------------------------------------- Import use std::{future::Future, sync::Arc}; -use axum::{extract::State, http::StatusCode, Json}; +use axum::{body::Bytes, extract::State, http::StatusCode, Json}; use tower::{Service, ServiceExt}; +use cuprate_rpc_types::{ + bin::{BinRequest, BinResponse}, + RpcRequest, +}; + use crate::{ - error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, - rpc_handler::RpcHandler, rpc_state::RpcState, + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, }; //---------------------------------------------------------------------------------------------------- Macro /// TODO macro_rules! return_if_restricted { - ($handler:ident) => { + ($handler:ident, $request:ident) => { + // TODO + // if $handler.state().restricted() && $request.is_restricted() { if $handler.state().restricted() { - return Ok("TODO"); + return Err(StatusCode::NOT_FOUND); } }; } @@ -27,64 +34,120 @@ macro_rules! return_if_restricted { /// TODO pub(crate) async fn get_blocks( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, // TODO: BinRequest +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_blocks_by_height( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_hashes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_o_indexes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_outs( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_transaction_pool_hashes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } /// TODO pub(crate) async fn get_output_distribution( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + request: Bytes, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs index 20a9c0c24..c907b5e54 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -3,680 +3,58 @@ //! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). //---------------------------------------------------------------------------------------------------- Import -use std::{future::Future, sync::Arc}; +use std::{borrow::Cow, future::Future, sync::Arc}; use axum::{extract::State, http::StatusCode, Json}; +use cuprate_json_rpc::{ + error::{ErrorCode, ErrorObject}, + Id, +}; use tower::{Service, ServiceExt}; +use cuprate_rpc_types::{ + json::{JsonRpcRequest, JsonRpcResponse}, + RpcRequest, +}; + use crate::{ - error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, - rpc_handler::RpcHandler, rpc_state::RpcState, + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, }; //---------------------------------------------------------------------------------------------------- Struct definitions /// TODO pub(crate) async fn json_rpc( State(handler): State, - Json(request): Json>, -) -> Result, StatusCode> { + Json(request): Json>, +) -> Result>, StatusCode> { + // Return early if this RPC server is restricted and + // the requested method is only for non-restricted RPC. if handler.state().restricted() && request.body.is_restricted() { - // const RESTRICTED: Response = Response::Todo; - const RESTRICTED: &str = "TODO"; - return Ok(Json(RESTRICTED)); - } - - /* call handler */ - - Ok(Json("TODO")) -} - -// // This generates 2 structs: -// // -// // - `GetBlockTemplateRequest` -// // - `GetBlockTemplateResponse` -// // -// // with some interconnected documentation. -// define_request_and_response! { -// // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. -// get_block_template, - -// // The commit hash and `$file.$extension` in which this type is defined in -// // the Monero codebase in the `rpc/` directory, followed by the specific lines. -// cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, - -// // The base type name. -// GetBlockTemplate, - -// // The request type. -// // -// // If `Request {/* fields */}` is provided, a struct is generate as-is. -// // -// // If `Request {}` is specified here, it will create a `pub type YOUR_REQUEST_TYPE = ()` -// // instead of a `struct`, see below in other macro definitions for an example. -// // -// // If there are any additional attributes (`/// docs` or `#[derive]`s) -// // for the struct, they go here, e.g.: -// // #[derive(Copy)] -// Request { -// // Within the `{}` is an infinite matching pattern of: -// // ``` -// // $ATTRIBUTES -// // $FIELD_NAME: $FIELD_TYPE, -// // ``` -// // The struct generated and all fields are `pub`. - -// // This optional expression can be placed after -// // a `field: field_type`. this indicates to the -// // macro to (de)serialize this field using this -// // default expression if it doesn't exist in epee. -// // -// // See `cuprate_epee_encoding::epee_object` for info. -// // -// // The default function must be specified twice: -// // -// // 1. As an expression -// // 2. As a string literal -// // -// // For example: `extra_nonce: String /* = default_string(), "default_string" */,` -// // -// // This is a HACK since `serde`'s default attribute only takes in -// // string literals and macros (stringify) within attributes do not work. -// extra_nonce: String /* = default_expression, "default_literal" */, - -// // Another optional expression: -// // This indicates to the macro to (de)serialize -// // this field as another type in epee. -// // -// // See `cuprate_epee_encoding::epee_object` for info. -// prev_block: String /* as Type */, - -// // Regular fields. -// reserve_size: u64, -// wallet_address: String, -// }, - -// // The response type. -// // -// // If `Response {/* fields */}` is used, -// // this will generate a struct as-is. -// // -// // If a type found in [`crate::base`] is used, -// // It acts as a "base" that gets flattened into -// // the actual request type. -// // -// // "Flatten" means the field(s) of a struct gets inlined -// // directly into the struct during (de)serialization, see: -// // . -// ResponseBase { -// // This is using [`crate::base::ResponseBase`], -// // so the type we generate will contain this field: -// // ``` -// // base: crate::base::ResponseBase, -// // ``` -// // -// // This is flattened with serde and epee, so during -// // (de)serialization, it will act as if there are 2 extra fields here: -// // ``` -// // status: crate::Status, -// // untrusted: bool, -// // ``` -// blockhashing_blob: String, -// blocktemplate_blob: String, -// difficulty_top64: u64, -// difficulty: u64, -// expected_reward: u64, -// height: u64, -// next_seed_hash: String, -// prev_hash: String, -// reserved_offset: u64, -// seed_hash: String, -// seed_height: u64, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// get_block_count, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 919..=933, -// GetBlockCount, - -// // There are no request fields specified, -// // this will cause the macro to generate a -// // type alias to `()` instead of a `struct`. -// Request {}, - -// #[derive(Copy)] -// ResponseBase { -// count: u64, -// } -// } - -// define_request_and_response! { -// on_get_block_hash, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 935..=939, -// OnGetBlockHash, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashRequest { block_height: [3] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "[3]"); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// #[derive(Copy)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_height: [u64; 1], -// }, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "\"asdf\""); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// block_hash: String, -// } -// } - -// define_request_and_response! { -// submit_block, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1114..=1128, -// SubmitBlock, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, r#"["a"]"#); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_blob: [String; 1], -// }, -// ResponseBase { -// block_id: String, -// } -// } - -// define_request_and_response! { -// generateblocks, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1130..=1161, -// GenerateBlocks, -// Request { -// amount_of_blocks: u64, -// prev_block: String, -// starting_nonce: u32, -// wallet_address: String, -// }, -// ResponseBase { -// blocks: Vec, -// height: u64, -// } -// } - -// define_request_and_response! { -// get_last_block_header, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1214..=1238, -// GetLastBlockHeader, -// #[derive(Copy)] -// Request { -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } + let error_object = ErrorObject { + code: ErrorCode::ServerError(-1 /* TODO */), + message: Cow::Borrowed("Restricted. TODO"), + data: None, + }; -// define_request_and_response! { -// get_block_header_by_hash, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1240..=1269, -// GetBlockHeaderByHash, -// Request { -// hash: String, -// hashes: Vec, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// block_headers: Vec, -// } -// } + // JSON-RPC 2.0 rule: + // If there was an error in detecting the `Request`'s ID, + // the `Response` must contain an `Id::Null` + let id = request.id.unwrap_or(Id::Null); -// define_request_and_response! { -// get_block_header_by_height, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1271..=1296, -// GetBlockHeaderByHeight, -// #[derive(Copy)] -// Request { -// height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } + let response = cuprate_json_rpc::Response::err(id, error_object); -// define_request_and_response! { -// get_block_headers_range, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1756..=1783, -// GetBlockHeadersRange, -// #[derive(Copy)] -// Request { -// start_height: u64, -// end_height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1298..=1313, -// GetBlock, -// Request { -// // `monerod` has both `hash` and `height` fields. -// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. -// // -// hash: String = default_string(), "default_string", -// height: u64 = default_height(), "default_height", -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// blob: String, -// block_header: BlockHeader, -// json: String, // TODO: this should be defined in a struct, it has many fields. -// miner_tx_hash: String, -// tx_hashes: Vec, -// } -// } - -// define_request_and_response! { -// get_connections, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1734..=1754, -// GetConnections, -// Request {}, -// ResponseBase { -// // FIXME: This is a `std::list` in `monerod` because...? -// connections: Vec, -// } -// } - -// define_request_and_response! { -// get_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 693..=789, -// GetInfo, -// Request {}, -// AccessResponseBase { -// adjusted_time: u64, -// alt_blocks_count: u64, -// block_size_limit: u64, -// block_size_median: u64, -// block_weight_limit: u64, -// block_weight_median: u64, -// bootstrap_daemon_address: String, -// busy_syncing: bool, -// cumulative_difficulty_top64: u64, -// cumulative_difficulty: u64, -// database_size: u64, -// difficulty_top64: u64, -// difficulty: u64, -// free_space: u64, -// grey_peerlist_size: u64, -// height: u64, -// height_without_bootstrap: u64, -// incoming_connections_count: u64, -// mainnet: bool, -// nettype: String, -// offline: bool, -// outgoing_connections_count: u64, -// restricted: bool, -// rpc_connections_count: u64, -// stagenet: bool, -// start_time: u64, -// synchronized: bool, -// target_height: u64, -// target: u64, -// testnet: bool, -// top_block_hash: String, -// tx_count: u64, -// tx_pool_size: u64, -// update_available: bool, -// version: String, -// was_bootstrap_ever_used: bool, -// white_peerlist_size: u64, -// wide_cumulative_difficulty: String, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// hard_fork_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1958..=1995, -// HardForkInfo, -// Request {}, -// AccessResponseBase { -// earliest_height: u64, -// enabled: bool, -// state: u32, -// threshold: u32, -// version: u8, -// votes: u32, -// voting: u8, -// window: u32, -// } -// } - -// define_request_and_response! { -// set_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2032..=2067, -// SetBans, -// Request { -// bans: Vec, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// get_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1997..=2030, -// GetBans, -// Request {}, -// ResponseBase { -// bans: Vec, -// } -// } - -// define_request_and_response! { -// banned, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2069..=2094, -// Banned, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// address: String, -// }, -// #[derive(Copy)] -// Response { -// banned: bool, -// seconds: u32, -// status: Status, -// } -// } - -// define_request_and_response! { -// flush_txpool, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2096..=2116, -// FlushTransactionPool, -// Request { -// txids: Vec = default_vec::(), "default_vec", -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_output_histogram, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2118..=2168, -// GetOutputHistogram, -// Request { -// amounts: Vec, -// min_count: u64, -// max_count: u64, -// unlocked: bool, -// recent_cutoff: u64, -// }, -// AccessResponseBase { -// histogram: Vec, -// } -// } - -// define_request_and_response! { -// get_coinbase_tx_sum, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2213..=2248, -// GetCoinbaseTxSum, -// Request { -// height: u64, -// count: u64, -// }, -// AccessResponseBase { -// emission_amount: u64, -// emission_amount_top64: u64, -// fee_amount: u64, -// fee_amount_top64: u64, -// wide_emission_amount: String, -// wide_fee_amount: String, -// } -// } - -// define_request_and_response! { -// get_version, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2170..=2211, -// GetVersion, -// Request {}, -// ResponseBase { -// version: u32, -// release: bool, -// #[serde(skip_serializing_if = "is_zero")] -// current_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "is_zero")] -// target_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "Vec::is_empty")] -// hard_forks: Vec = default_vec(), "default_vec", -// } -// } - -// define_request_and_response! { -// get_fee_estimate, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2250..=2277, -// GetFeeEstimate, -// Request {}, -// AccessResponseBase { -// fee: u64, -// fees: Vec, -// #[serde(skip_serializing_if = "is_one")] -// quantization_mask: u64, -// } -// } - -// define_request_and_response! { -// get_alternate_chains, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2279..=2310, -// GetAlternateChains, -// Request {}, -// ResponseBase { -// chains: Vec, -// } -// } - -// define_request_and_response! { -// relay_tx, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2361..=2381, -// RelayTx, -// Request { -// txids: Vec, -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// sync_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2383..=2443, -// SyncInfo, -// Request {}, -// AccessResponseBase { -// height: u64, -// next_needed_pruning_seed: u32, -// overview: String, -// // FIXME: This is a `std::list` in `monerod` because...? -// peers: Vec, -// // FIXME: This is a `std::list` in `monerod` because...? -// spans: Vec, -// target_height: u64, -// } -// } - -// define_request_and_response! { -// get_txpool_backlog, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1637..=1664, -// GetTransactionPoolBacklog, -// Request {}, -// ResponseBase { -// // TODO: this is a [`BinaryString`]. -// backlog: Vec, -// } -// } - -// define_request_and_response! { -// get_output_distribution, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2445..=2520, -// /// This type is also used in the (undocumented) -// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) -// /// binary endpoint. -// GetOutputDistribution, -// Request { -// amounts: Vec, -// binary: bool, -// compress: bool, -// cumulative: bool, -// from_height: u64, -// to_height: u64, -// }, -// /// TODO: this request has custom serde: -// /// -// AccessResponseBase { -// distributions: Vec, -// } -// } - -// define_request_and_response! { -// get_miner_data, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 996..=1044, -// GetMinerData, -// Request {}, -// ResponseBase { -// major_version: u8, -// height: u64, -// prev_id: String, -// seed_hash: String, -// difficulty: String, -// median_weight: u64, -// already_generated_coins: u64, -// } -// } - -// define_request_and_response! { -// prune_blockchain, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2747..=2772, -// PruneBlockchain, -// #[derive(Copy)] -// Request { -// check: bool = default_false(), "default_false", -// }, -// #[derive(Copy)] -// ResponseBase { -// pruned: bool, -// pruning_seed: u32, -// } -// } - -// define_request_and_response! { -// calc_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1046..=1066, -// CalcPow, -// Request { -// major_version: u8, -// height: u64, -// block_blob: String, -// seed_hash: String, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// pow_hash: String, -// } -// } + // TODO + return Ok(Json(response)); + } -// define_request_and_response! { -// flush_cache, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2774..=2796, -// FlushCache, -// #[derive(Copy)] -// Request { -// bad_txs: bool = default_false(), "default_false", -// bad_blocks: bool = default_false(), "default_false", -// }, -// ResponseBase {} -// } + // TODO: call handler + let Response::JsonRpc(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; -// define_request_and_response! { -// add_aux_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1068..=1112, -// AddAuxPow, -// Request { -// blocktemplate_blob: String, -// aux_pow: Vec, -// }, -// ResponseBase { -// blocktemplate_blob: String, -// blockhashing_blob: String, -// merkle_root: String, -// merkle_tree_depth: u64, -// aux_pow: Vec, -// } -// } + Ok(Json(response)) +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index e270d2592..462469373 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -3,5 +3,7 @@ pub(crate) mod bin; mod json_rpc; pub(crate) mod other; +mod unknown; pub(crate) use json_rpc::json_rpc; +pub(crate) use unknown::unknown; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index fd3bad32d..20f18c9ac 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -8,17 +8,22 @@ use std::{future::Future, sync::Arc}; use axum::{extract::State, http::StatusCode, Json}; use tower::{Service, ServiceExt}; +use cuprate_rpc_types::{ + other::{OtherRequest, OtherResponse}, + RpcRequest, +}; + use crate::{ - error::Error, json_rpc_method::JsonRpcMethod, request::Request, response::Response, - rpc_handler::RpcHandler, rpc_state::RpcState, + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, }; //---------------------------------------------------------------------------------------------------- Macro /// TODO macro_rules! return_if_restricted { - ($handler:ident) => { - if $handler.state().restricted() { - return Ok("TODO"); + ($handler:ident, $request:ident) => { + if $handler.state().restricted() && $request.is_restricted() { + return Err(StatusCode::NOT_FOUND); } }; } @@ -27,253 +32,421 @@ macro_rules! return_if_restricted { /// TODO pub(crate) async fn get_height( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_transactions( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_alt_blocks_hashes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn is_key_image_spent( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn send_raw_transaction( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn start_mining( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn stop_mining( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn mining_status( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn save_bc( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_peer_list( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_public_nodes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn set_log_hash_rate( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn set_log_level( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn set_log_categories( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_transaction_pool( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_transaction_pool_hashes( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_transaction_pool_stats( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn set_bootstrap_daemon( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn stop_daemon( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_info( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_net_stats( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_limit( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn set_limit( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn out_peers( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn in_peers( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn get_outs( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn update( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } /// TODO pub(crate) async fn pop_blocks( State(handler): State, -) -> Result<&'static str, StatusCode> { - return_if_restricted!(handler); - /* call handler */ - Ok("TODO") + Json(request): Json, +) -> Result, StatusCode> { + return_if_restricted!(handler, request); + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/unknown.rs new file mode 100644 index 000000000..a132a7c8b --- /dev/null +++ b/rpc/interface/src/route/unknown.rs @@ -0,0 +1,674 @@ +//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. +//! +//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). + +//---------------------------------------------------------------------------------------------------- Import +use std::{future::Future, sync::Arc}; + +use axum::{extract::State, http::StatusCode, Json}; +use tower::{Service, ServiceExt}; + +use crate::{ + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, +}; + +//---------------------------------------------------------------------------------------------------- Struct definitions +/// TODO +pub(crate) async fn unknown() -> StatusCode { + StatusCode::NOT_FOUND +} + +// // This generates 2 structs: +// // +// // - `GetBlockTemplateRequest` +// // - `GetBlockTemplateResponse` +// // +// // with some interconnected documentation. +// define_request_and_response! { +// // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. +// get_block_template, + +// // The commit hash and `$file.$extension` in which this type is defined in +// // the Monero codebase in the `rpc/` directory, followed by the specific lines. +// cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, + +// // The base type name. +// GetBlockTemplate, + +// // The request type. +// // +// // If `Request {/* fields */}` is provided, a struct is generate as-is. +// // +// // If `Request {}` is specified here, it will create a `pub type YOUR_REQUEST_TYPE = ()` +// // instead of a `struct`, see below in other macro definitions for an example. +// // +// // If there are any additional attributes (`/// docs` or `#[derive]`s) +// // for the struct, they go here, e.g.: +// // #[derive(Copy)] +// Request { +// // Within the `{}` is an infinite matching pattern of: +// // ``` +// // $ATTRIBUTES +// // $FIELD_NAME: $FIELD_TYPE, +// // ``` +// // The struct generated and all fields are `pub`. + +// // This optional expression can be placed after +// // a `field: field_type`. this indicates to the +// // macro to (de)serialize this field using this +// // default expression if it doesn't exist in epee. +// // +// // See `cuprate_epee_encoding::epee_object` for info. +// // +// // The default function must be specified twice: +// // +// // 1. As an expression +// // 2. As a string literal +// // +// // For example: `extra_nonce: String /* = default_string(), "default_string" */,` +// // +// // This is a HACK since `serde`'s default attribute only takes in +// // string literals and macros (stringify) within attributes do not work. +// extra_nonce: String /* = default_expression, "default_literal" */, + +// // Another optional expression: +// // This indicates to the macro to (de)serialize +// // this field as another type in epee. +// // +// // See `cuprate_epee_encoding::epee_object` for info. +// prev_block: String /* as Type */, + +// // Regular fields. +// reserve_size: u64, +// wallet_address: String, +// }, + +// // The response type. +// // +// // If `Response {/* fields */}` is used, +// // this will generate a struct as-is. +// // +// // If a type found in [`crate::base`] is used, +// // It acts as a "base" that gets flattened into +// // the actual request type. +// // +// // "Flatten" means the field(s) of a struct gets inlined +// // directly into the struct during (de)serialization, see: +// // . +// ResponseBase { +// // This is using [`crate::base::ResponseBase`], +// // so the type we generate will contain this field: +// // ``` +// // base: crate::base::ResponseBase, +// // ``` +// // +// // This is flattened with serde and epee, so during +// // (de)serialization, it will act as if there are 2 extra fields here: +// // ``` +// // status: crate::Status, +// // untrusted: bool, +// // ``` +// blockhashing_blob: String, +// blocktemplate_blob: String, +// difficulty_top64: u64, +// difficulty: u64, +// expected_reward: u64, +// height: u64, +// next_seed_hash: String, +// prev_hash: String, +// reserved_offset: u64, +// seed_hash: String, +// seed_height: u64, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// get_block_count, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 919..=933, +// GetBlockCount, + +// // There are no request fields specified, +// // this will cause the macro to generate a +// // type alias to `()` instead of a `struct`. +// Request {}, + +// #[derive(Copy)] +// ResponseBase { +// count: u64, +// } +// } + +// define_request_and_response! { +// on_get_block_hash, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 935..=939, +// OnGetBlockHash, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashRequest { block_height: [3] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "[3]"); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// #[derive(Copy)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_height: [u64; 1], +// }, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, "\"asdf\""); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// block_hash: String, +// } +// } + +// define_request_and_response! { +// submit_block, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1114..=1128, +// SubmitBlock, +// /// ```rust +// /// use serde_json::*; +// /// use cuprate_rpc_types::json::*; +// /// +// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; +// /// let x = to_string(&x).unwrap(); +// /// assert_eq!(x, r#"["a"]"#); +// /// ``` +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// // This is `std::vector` in `monerod` but +// // it must be a 1 length array or else it will error. +// block_blob: [String; 1], +// }, +// ResponseBase { +// block_id: String, +// } +// } + +// define_request_and_response! { +// generateblocks, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1130..=1161, +// GenerateBlocks, +// Request { +// amount_of_blocks: u64, +// prev_block: String, +// starting_nonce: u32, +// wallet_address: String, +// }, +// ResponseBase { +// blocks: Vec, +// height: u64, +// } +// } + +// define_request_and_response! { +// get_last_block_header, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1214..=1238, +// GetLastBlockHeader, +// #[derive(Copy)] +// Request { +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_header_by_hash, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1240..=1269, +// GetBlockHeaderByHash, +// Request { +// hash: String, +// hashes: Vec, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// block_headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block_header_by_height, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1271..=1296, +// GetBlockHeaderByHeight, +// #[derive(Copy)] +// Request { +// height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// block_header: BlockHeader, +// } +// } + +// define_request_and_response! { +// get_block_headers_range, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1756..=1783, +// GetBlockHeadersRange, +// #[derive(Copy)] +// Request { +// start_height: u64, +// end_height: u64, +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// headers: Vec, +// } +// } + +// define_request_and_response! { +// get_block, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1298..=1313, +// GetBlock, +// Request { +// // `monerod` has both `hash` and `height` fields. +// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. +// // +// hash: String = default_string(), "default_string", +// height: u64 = default_height(), "default_height", +// fill_pow_hash: bool = default_false(), "default_false", +// }, +// AccessResponseBase { +// blob: String, +// block_header: BlockHeader, +// json: String, // TODO: this should be defined in a struct, it has many fields. +// miner_tx_hash: String, +// tx_hashes: Vec, +// } +// } + +// define_request_and_response! { +// get_connections, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1734..=1754, +// GetConnections, +// Request {}, +// ResponseBase { +// // FIXME: This is a `std::list` in `monerod` because...? +// connections: Vec, +// } +// } + +// define_request_and_response! { +// get_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 693..=789, +// GetInfo, +// Request {}, +// AccessResponseBase { +// adjusted_time: u64, +// alt_blocks_count: u64, +// block_size_limit: u64, +// block_size_median: u64, +// block_weight_limit: u64, +// block_weight_median: u64, +// bootstrap_daemon_address: String, +// busy_syncing: bool, +// cumulative_difficulty_top64: u64, +// cumulative_difficulty: u64, +// database_size: u64, +// difficulty_top64: u64, +// difficulty: u64, +// free_space: u64, +// grey_peerlist_size: u64, +// height: u64, +// height_without_bootstrap: u64, +// incoming_connections_count: u64, +// mainnet: bool, +// nettype: String, +// offline: bool, +// outgoing_connections_count: u64, +// restricted: bool, +// rpc_connections_count: u64, +// stagenet: bool, +// start_time: u64, +// synchronized: bool, +// target_height: u64, +// target: u64, +// testnet: bool, +// top_block_hash: String, +// tx_count: u64, +// tx_pool_size: u64, +// update_available: bool, +// version: String, +// was_bootstrap_ever_used: bool, +// white_peerlist_size: u64, +// wide_cumulative_difficulty: String, +// wide_difficulty: String, +// } +// } + +// define_request_and_response! { +// hard_fork_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1958..=1995, +// HardForkInfo, +// Request {}, +// AccessResponseBase { +// earliest_height: u64, +// enabled: bool, +// state: u32, +// threshold: u32, +// version: u8, +// votes: u32, +// voting: u8, +// window: u32, +// } +// } + +// define_request_and_response! { +// set_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2032..=2067, +// SetBans, +// Request { +// bans: Vec, +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// get_bans, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1997..=2030, +// GetBans, +// Request {}, +// ResponseBase { +// bans: Vec, +// } +// } + +// define_request_and_response! { +// banned, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2069..=2094, +// Banned, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Request { +// address: String, +// }, +// #[derive(Copy)] +// Response { +// banned: bool, +// seconds: u32, +// status: Status, +// } +// } + +// define_request_and_response! { +// flush_txpool, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2096..=2116, +// FlushTransactionPool, +// Request { +// txids: Vec = default_vec::(), "default_vec", +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// get_output_histogram, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2118..=2168, +// GetOutputHistogram, +// Request { +// amounts: Vec, +// min_count: u64, +// max_count: u64, +// unlocked: bool, +// recent_cutoff: u64, +// }, +// AccessResponseBase { +// histogram: Vec, +// } +// } + +// define_request_and_response! { +// get_coinbase_tx_sum, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2213..=2248, +// GetCoinbaseTxSum, +// Request { +// height: u64, +// count: u64, +// }, +// AccessResponseBase { +// emission_amount: u64, +// emission_amount_top64: u64, +// fee_amount: u64, +// fee_amount_top64: u64, +// wide_emission_amount: String, +// wide_fee_amount: String, +// } +// } + +// define_request_and_response! { +// get_version, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2170..=2211, +// GetVersion, +// Request {}, +// ResponseBase { +// version: u32, +// release: bool, +// #[serde(skip_serializing_if = "is_zero")] +// current_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "is_zero")] +// target_height: u64 = default_zero(), "default_zero", +// #[serde(skip_serializing_if = "Vec::is_empty")] +// hard_forks: Vec = default_vec(), "default_vec", +// } +// } + +// define_request_and_response! { +// get_fee_estimate, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2250..=2277, +// GetFeeEstimate, +// Request {}, +// AccessResponseBase { +// fee: u64, +// fees: Vec, +// #[serde(skip_serializing_if = "is_one")] +// quantization_mask: u64, +// } +// } + +// define_request_and_response! { +// get_alternate_chains, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2279..=2310, +// GetAlternateChains, +// Request {}, +// ResponseBase { +// chains: Vec, +// } +// } + +// define_request_and_response! { +// relay_tx, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2361..=2381, +// RelayTx, +// Request { +// txids: Vec, +// }, +// #[derive(Copy)] +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// status: Status, +// } +// } + +// define_request_and_response! { +// sync_info, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2383..=2443, +// SyncInfo, +// Request {}, +// AccessResponseBase { +// height: u64, +// next_needed_pruning_seed: u32, +// overview: String, +// // FIXME: This is a `std::list` in `monerod` because...? +// peers: Vec, +// // FIXME: This is a `std::list` in `monerod` because...? +// spans: Vec, +// target_height: u64, +// } +// } + +// define_request_and_response! { +// get_txpool_backlog, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1637..=1664, +// GetTransactionPoolBacklog, +// Request {}, +// ResponseBase { +// // TODO: this is a [`BinaryString`]. +// backlog: Vec, +// } +// } + +// define_request_and_response! { +// get_output_distribution, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2445..=2520, +// /// This type is also used in the (undocumented) +// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) +// /// binary endpoint. +// GetOutputDistribution, +// Request { +// amounts: Vec, +// binary: bool, +// compress: bool, +// cumulative: bool, +// from_height: u64, +// to_height: u64, +// }, +// /// TODO: this request has custom serde: +// /// +// AccessResponseBase { +// distributions: Vec, +// } +// } + +// define_request_and_response! { +// get_miner_data, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 996..=1044, +// GetMinerData, +// Request {}, +// ResponseBase { +// major_version: u8, +// height: u64, +// prev_id: String, +// seed_hash: String, +// difficulty: String, +// median_weight: u64, +// already_generated_coins: u64, +// } +// } + +// define_request_and_response! { +// prune_blockchain, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2747..=2772, +// PruneBlockchain, +// #[derive(Copy)] +// Request { +// check: bool = default_false(), "default_false", +// }, +// #[derive(Copy)] +// ResponseBase { +// pruned: bool, +// pruning_seed: u32, +// } +// } + +// define_request_and_response! { +// calc_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1046..=1066, +// CalcPow, +// Request { +// major_version: u8, +// height: u64, +// block_blob: String, +// seed_hash: String, +// }, +// #[cfg_attr(feature = "serde", serde(transparent))] +// #[repr(transparent)] +// Response { +// pow_hash: String, +// } +// } + +// define_request_and_response! { +// flush_cache, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 2774..=2796, +// FlushCache, +// #[derive(Copy)] +// Request { +// bad_txs: bool = default_false(), "default_false", +// bad_blocks: bool = default_false(), "default_false", +// }, +// ResponseBase {} +// } + +// define_request_and_response! { +// add_aux_pow, +// cc73fe71162d564ffda8e549b79a350bca53c454 => +// core_rpc_server_commands_defs.h => 1068..=1112, +// AddAuxPow, +// Request { +// blocktemplate_blob: String, +// aux_pow: Vec, +// }, +// ResponseBase { +// blocktemplate_blob: String, +// blockhashing_blob: String, +// merkle_root: String, +// merkle_tree_depth: u64, +// aux_pow: Vec, +// } +// } + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 6d716f0b2..7f09dc306 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -172,7 +172,7 @@ impl RpcRequest for BinRequest { #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] -pub enum JsonRpcResponse { +pub enum BinResponse { GetBlocks(GetBlocksResponse), GetBlocksByHeight(GetBlocksByHeightResponse), GetHashes(GetHashesResponse), From 61ab466a730c6ce7e92a5aa484ce9bf81e4be0f5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 23 Jul 2024 18:02:38 -0400 Subject: [PATCH 60/93] interface: simplify routes --- rpc/interface/src/free.rs | 122 ++-- .../src/{route/json_rpc.rs => route.rs} | 52 +- rpc/interface/src/route/bin.rs | 157 ---- rpc/interface/src/route/mod.rs | 9 - rpc/interface/src/route/other.rs | 456 ------------ rpc/interface/src/route/unknown.rs | 674 ------------------ 6 files changed, 112 insertions(+), 1358 deletions(-) rename rpc/interface/src/{route/json_rpc.rs => route.rs} (59%) delete mode 100644 rpc/interface/src/route/bin.rs delete mode 100644 rpc/interface/src/route/mod.rs delete mode 100644 rpc/interface/src/route/other.rs delete mode 100644 rpc/interface/src/route/unknown.rs diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 28700d12a..a49fae244 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -7,69 +7,77 @@ use axum::{extract::State, routing::method_routing::get, Router}; use tower::Service; use crate::{ - error::Error, - request::Request, - response::Response, - route::json_rpc, - route::{bin, other}, - rpc_handler::RpcHandler, - RpcState, + error::Error, request::Request, response::Response, route, rpc_handler::RpcHandler, RpcState, }; //---------------------------------------------------------------------------------------------------- Router /// TODO -#[rustfmt::skip] // 1 line per route. #[allow(clippy::needless_pass_by_value)] pub fn create_router() -> Router { // List of `monerod` routes: // - Router::new() - // JSON-RPC route. - .route("/json_rpc", get(json_rpc::)) - // Other JSON routes. - .route("/get_height", get(other::get_height::)) - .route("/getheight", get(other::get_height::)) - .route("/get_transactions", get(other::get_transactions::)) - .route("/gettransactions", get(other::get_transactions::)) - .route("/get_alt_blocks_hashes", get(other::get_alt_blocks_hashes::)) - .route("/is_key_image_spent", get(other::is_key_image_spent::)) - .route("/send_raw_transaction", get(other::send_raw_transaction::)) - .route("/sendrawtransaction", get(other::send_raw_transaction::)) - .route("/start_mining", get(other::start_mining::)) - .route("/stop_mining", get(other::stop_mining::)) - .route("/mining_status", get(other::mining_status::)) - .route("/save_bc", get(other::save_bc::)) - .route("/get_peer_list", get(other::get_peer_list::)) - .route("/get_public_nodes", get(other::get_public_nodes::)) - .route("/set_log_hash_rate", get(other::set_log_hash_rate::)) - .route("/set_log_level", get(other::set_log_level::)) - .route("/set_log_categories", get(other::set_log_categories::)) - .route("/get_transaction_pool", get(other::get_transaction_pool::)) - .route("/get_transaction_pool_hashes", get(other::get_transaction_pool_hashes::)) - .route("/get_transaction_pool_stats", get(other::get_transaction_pool_stats::)) - .route("/set_bootstrap_daemon", get(other::set_bootstrap_daemon::)) - .route("/stop_daemon", get(other::stop_daemon::)) - .route("/get_info", get(other::get_info::)) - .route("/getinfo", get(other::get_info::)) - .route("/get_net_stats", get(other::get_net_stats::)) - .route("/get_limit", get(other::get_limit::)) - .route("/set_limit", get(other::set_limit::)) - .route("/out_peers", get(other::out_peers::)) - .route("/in_peers", get(other::in_peers::)) - .route("/get_outs", get(other::get_outs::)) - .route("/update", get(other::update::)) - .route("/pop_blocks", get(other::pop_blocks::)) - // Binary routes. - .route("/get_blocks.bin", get(bin::get_blocks::)) - .route("/getblocks.bin", get(bin::get_blocks::)) - .route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::)) - .route("/getblocks_by_height.bin", get(bin::get_blocks_by_height::)) - .route("/get_hashes.bin", get(bin::get_hashes::)) - .route("/gethashes.bin", get(bin::get_hashes::)) - .route("/get_o_indexes.bin", get(bin::get_o_indexes::)) - .route("/get_outs.bin", get(bin::get_outs::)) - .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) - .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) - // Unknown route. - .route("/*", todo!()) + + let mut router = Router::new(); + + // JSON-RPC route. + router = router.route("/json_rpc", get(route::json_rpc::)); + + // Other JSON routes. + for other_route in [ + "/get_height", + "/getheight", + "/get_transactions", + "/gettransactions", + "/get_alt_blocks_hashes", + "/is_key_image_spent", + "/send_raw_transaction", + "/sendrawtransaction", + "/start_mining", + "/stop_mining", + "/mining_status", + "/save_bc", + "/get_peer_list", + "/get_public_nodes", + "/set_log_hash_rate", + "/set_log_level", + "/set_log_categories", + "/get_transaction_pool", + "/get_transaction_pool_hashes", + "/get_transaction_pool_stats", + "/set_bootstrap_daemon", + "/stop_daemon", + "/get_info", + "/getinfo", + "/get_net_stats", + "/get_limit", + "/set_limit", + "/out_peers", + "/in_peers", + "/get_outs", + "/update", + "/pop_blocks", + ] { + router = router.route(other_route, get(route::other::)); + } + + // Binary routes. + for binary_route in [ + "/get_blocks.bin", + "/getblocks.bin", + "/get_blocks_by_height.bin", + "/getblocks_by_height.bin", + "/get_hashes.bin", + "/gethashes.bin", + "/get_o_indexes.bin", + "/get_outs.bin", + "/get_transaction_pool_hashes.bin", + "/get_output_distribution.bin", + ] { + router = router.route(binary_route, get(route::bin::)); + } + + // Unknown route. + router = router.route("/*", get(route::unknown)); + + router } diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route.rs similarity index 59% rename from rpc/interface/src/route/json_rpc.rs rename to rpc/interface/src/route.rs index c907b5e54..cd01e380d 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route.rs @@ -1,11 +1,9 @@ -//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. -//! -//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). +//! TODO //---------------------------------------------------------------------------------------------------- Import use std::{borrow::Cow, future::Future, sync::Arc}; -use axum::{extract::State, http::StatusCode, Json}; +use axum::{body::Bytes, extract::State, http::StatusCode, Json}; use cuprate_json_rpc::{ error::{ErrorCode, ErrorObject}, Id, @@ -14,6 +12,7 @@ use tower::{Service, ServiceExt}; use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, RpcRequest, }; @@ -22,7 +21,7 @@ use crate::{ rpc_state::RpcState, }; -//---------------------------------------------------------------------------------------------------- Struct definitions +//---------------------------------------------------------------------------------------------------- Routes /// TODO pub(crate) async fn json_rpc( State(handler): State, @@ -56,6 +55,49 @@ pub(crate) async fn json_rpc( Ok(Json(response)) } +/// TODO +pub(crate) async fn bin( + State(handler): State, + request: Bytes, // TODO: BinRequest +) -> Result, StatusCode> { + // TODO + // if handler.state().restricted() && request.is_restricted() { + if handler.state().restricted() { + return Err(StatusCode::NOT_FOUND); + } + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + let binary: Vec = todo!(); // TODO: serialize response. + + Ok(binary) +} + +/// TODO +pub(crate) async fn other( + State(handler): State, + Json(request): Json, +) -> Result, StatusCode> { + if handler.state().restricted() && request.is_restricted() { + todo!(); + } + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) +} + +/// TODO +pub(crate) async fn unknown() -> StatusCode { + StatusCode::NOT_FOUND +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs deleted file mode 100644 index 41c439f0a..000000000 --- a/rpc/interface/src/route/bin.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Binary types from [`.bin` endpoints](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#get_blocksbin). -//! -//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). - -//---------------------------------------------------------------------------------------------------- Import -use std::{future::Future, sync::Arc}; - -use axum::{body::Bytes, extract::State, http::StatusCode, Json}; -use tower::{Service, ServiceExt}; - -use cuprate_rpc_types::{ - bin::{BinRequest, BinResponse}, - RpcRequest, -}; - -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; - -//---------------------------------------------------------------------------------------------------- Macro -/// TODO -macro_rules! return_if_restricted { - ($handler:ident, $request:ident) => { - // TODO - // if $handler.state().restricted() && $request.is_restricted() { - if $handler.state().restricted() { - return Err(StatusCode::NOT_FOUND); - } - }; -} - -//---------------------------------------------------------------------------------------------------- Struct definitions -/// TODO -pub(crate) async fn get_blocks( - State(handler): State, - request: Bytes, // TODO: BinRequest -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_blocks_by_height( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_hashes( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_o_indexes( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_outs( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_transaction_pool_hashes( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn get_output_distribution( - State(handler): State, - request: Bytes, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs deleted file mode 100644 index 462469373..000000000 --- a/rpc/interface/src/route/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! TODO - -pub(crate) mod bin; -mod json_rpc; -pub(crate) mod other; -mod unknown; - -pub(crate) use json_rpc::json_rpc; -pub(crate) use unknown::unknown; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs deleted file mode 100644 index 20f18c9ac..000000000 --- a/rpc/interface/src/route/other.rs +++ /dev/null @@ -1,456 +0,0 @@ -//! JSON types from the [`other`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#other-daemon-rpc-calls) endpoints. -//! -//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). - -//---------------------------------------------------------------------------------------------------- Import -use std::{future::Future, sync::Arc}; - -use axum::{extract::State, http::StatusCode, Json}; -use tower::{Service, ServiceExt}; - -use cuprate_rpc_types::{ - other::{OtherRequest, OtherResponse}, - RpcRequest, -}; - -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; - -//---------------------------------------------------------------------------------------------------- Macro -/// TODO -macro_rules! return_if_restricted { - ($handler:ident, $request:ident) => { - if $handler.state().restricted() && $request.is_restricted() { - return Err(StatusCode::NOT_FOUND); - } - }; -} - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -pub(crate) async fn get_height( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_transactions( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_alt_blocks_hashes( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn is_key_image_spent( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn send_raw_transaction( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn start_mining( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn stop_mining( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn mining_status( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn save_bc( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_peer_list( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_public_nodes( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn set_log_hash_rate( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn set_log_level( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn set_log_categories( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_transaction_pool( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_transaction_pool_hashes( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_transaction_pool_stats( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn set_bootstrap_daemon( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn stop_daemon( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_info( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_net_stats( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_limit( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn set_limit( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn out_peers( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn in_peers( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn get_outs( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn update( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn pop_blocks( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - return_if_restricted!(handler, request); - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/unknown.rs deleted file mode 100644 index a132a7c8b..000000000 --- a/rpc/interface/src/route/unknown.rs +++ /dev/null @@ -1,674 +0,0 @@ -//! JSON types from the [`/json_rpc`](https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#json-rpc-methods) endpoint. -//! -//! All types are originally defined in [`rpc/core_rpc_server_commands_defs.h`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h). - -//---------------------------------------------------------------------------------------------------- Import -use std::{future::Future, sync::Arc}; - -use axum::{extract::State, http::StatusCode, Json}; -use tower::{Service, ServiceExt}; - -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; - -//---------------------------------------------------------------------------------------------------- Struct definitions -/// TODO -pub(crate) async fn unknown() -> StatusCode { - StatusCode::NOT_FOUND -} - -// // This generates 2 structs: -// // -// // - `GetBlockTemplateRequest` -// // - `GetBlockTemplateResponse` -// // -// // with some interconnected documentation. -// define_request_and_response! { -// // The markdown tag for Monero RPC documentation. Not necessarily the endpoint. -// get_block_template, - -// // The commit hash and `$file.$extension` in which this type is defined in -// // the Monero codebase in the `rpc/` directory, followed by the specific lines. -// cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, - -// // The base type name. -// GetBlockTemplate, - -// // The request type. -// // -// // If `Request {/* fields */}` is provided, a struct is generate as-is. -// // -// // If `Request {}` is specified here, it will create a `pub type YOUR_REQUEST_TYPE = ()` -// // instead of a `struct`, see below in other macro definitions for an example. -// // -// // If there are any additional attributes (`/// docs` or `#[derive]`s) -// // for the struct, they go here, e.g.: -// // #[derive(Copy)] -// Request { -// // Within the `{}` is an infinite matching pattern of: -// // ``` -// // $ATTRIBUTES -// // $FIELD_NAME: $FIELD_TYPE, -// // ``` -// // The struct generated and all fields are `pub`. - -// // This optional expression can be placed after -// // a `field: field_type`. this indicates to the -// // macro to (de)serialize this field using this -// // default expression if it doesn't exist in epee. -// // -// // See `cuprate_epee_encoding::epee_object` for info. -// // -// // The default function must be specified twice: -// // -// // 1. As an expression -// // 2. As a string literal -// // -// // For example: `extra_nonce: String /* = default_string(), "default_string" */,` -// // -// // This is a HACK since `serde`'s default attribute only takes in -// // string literals and macros (stringify) within attributes do not work. -// extra_nonce: String /* = default_expression, "default_literal" */, - -// // Another optional expression: -// // This indicates to the macro to (de)serialize -// // this field as another type in epee. -// // -// // See `cuprate_epee_encoding::epee_object` for info. -// prev_block: String /* as Type */, - -// // Regular fields. -// reserve_size: u64, -// wallet_address: String, -// }, - -// // The response type. -// // -// // If `Response {/* fields */}` is used, -// // this will generate a struct as-is. -// // -// // If a type found in [`crate::base`] is used, -// // It acts as a "base" that gets flattened into -// // the actual request type. -// // -// // "Flatten" means the field(s) of a struct gets inlined -// // directly into the struct during (de)serialization, see: -// // . -// ResponseBase { -// // This is using [`crate::base::ResponseBase`], -// // so the type we generate will contain this field: -// // ``` -// // base: crate::base::ResponseBase, -// // ``` -// // -// // This is flattened with serde and epee, so during -// // (de)serialization, it will act as if there are 2 extra fields here: -// // ``` -// // status: crate::Status, -// // untrusted: bool, -// // ``` -// blockhashing_blob: String, -// blocktemplate_blob: String, -// difficulty_top64: u64, -// difficulty: u64, -// expected_reward: u64, -// height: u64, -// next_seed_hash: String, -// prev_hash: String, -// reserved_offset: u64, -// seed_hash: String, -// seed_height: u64, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// get_block_count, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 919..=933, -// GetBlockCount, - -// // There are no request fields specified, -// // this will cause the macro to generate a -// // type alias to `()` instead of a `struct`. -// Request {}, - -// #[derive(Copy)] -// ResponseBase { -// count: u64, -// } -// } - -// define_request_and_response! { -// on_get_block_hash, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 935..=939, -// OnGetBlockHash, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashRequest { block_height: [3] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "[3]"); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// #[derive(Copy)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_height: [u64; 1], -// }, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = OnGetBlockHashResponse { block_hash: String::from("asdf") }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, "\"asdf\""); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// block_hash: String, -// } -// } - -// define_request_and_response! { -// submit_block, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1114..=1128, -// SubmitBlock, -// /// ```rust -// /// use serde_json::*; -// /// use cuprate_rpc_types::json::*; -// /// -// /// let x = SubmitBlockRequest { block_blob: ["a".into()] }; -// /// let x = to_string(&x).unwrap(); -// /// assert_eq!(x, r#"["a"]"#); -// /// ``` -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// // This is `std::vector` in `monerod` but -// // it must be a 1 length array or else it will error. -// block_blob: [String; 1], -// }, -// ResponseBase { -// block_id: String, -// } -// } - -// define_request_and_response! { -// generateblocks, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1130..=1161, -// GenerateBlocks, -// Request { -// amount_of_blocks: u64, -// prev_block: String, -// starting_nonce: u32, -// wallet_address: String, -// }, -// ResponseBase { -// blocks: Vec, -// height: u64, -// } -// } - -// define_request_and_response! { -// get_last_block_header, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1214..=1238, -// GetLastBlockHeader, -// #[derive(Copy)] -// Request { -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } - -// define_request_and_response! { -// get_block_header_by_hash, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1240..=1269, -// GetBlockHeaderByHash, -// Request { -// hash: String, -// hashes: Vec, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// block_headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block_header_by_height, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1271..=1296, -// GetBlockHeaderByHeight, -// #[derive(Copy)] -// Request { -// height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// block_header: BlockHeader, -// } -// } - -// define_request_and_response! { -// get_block_headers_range, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1756..=1783, -// GetBlockHeadersRange, -// #[derive(Copy)] -// Request { -// start_height: u64, -// end_height: u64, -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// headers: Vec, -// } -// } - -// define_request_and_response! { -// get_block, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1298..=1313, -// GetBlock, -// Request { -// // `monerod` has both `hash` and `height` fields. -// // In the RPC handler, if `hash.is_empty()`, it will use it, else, it uses `height`. -// // -// hash: String = default_string(), "default_string", -// height: u64 = default_height(), "default_height", -// fill_pow_hash: bool = default_false(), "default_false", -// }, -// AccessResponseBase { -// blob: String, -// block_header: BlockHeader, -// json: String, // TODO: this should be defined in a struct, it has many fields. -// miner_tx_hash: String, -// tx_hashes: Vec, -// } -// } - -// define_request_and_response! { -// get_connections, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1734..=1754, -// GetConnections, -// Request {}, -// ResponseBase { -// // FIXME: This is a `std::list` in `monerod` because...? -// connections: Vec, -// } -// } - -// define_request_and_response! { -// get_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 693..=789, -// GetInfo, -// Request {}, -// AccessResponseBase { -// adjusted_time: u64, -// alt_blocks_count: u64, -// block_size_limit: u64, -// block_size_median: u64, -// block_weight_limit: u64, -// block_weight_median: u64, -// bootstrap_daemon_address: String, -// busy_syncing: bool, -// cumulative_difficulty_top64: u64, -// cumulative_difficulty: u64, -// database_size: u64, -// difficulty_top64: u64, -// difficulty: u64, -// free_space: u64, -// grey_peerlist_size: u64, -// height: u64, -// height_without_bootstrap: u64, -// incoming_connections_count: u64, -// mainnet: bool, -// nettype: String, -// offline: bool, -// outgoing_connections_count: u64, -// restricted: bool, -// rpc_connections_count: u64, -// stagenet: bool, -// start_time: u64, -// synchronized: bool, -// target_height: u64, -// target: u64, -// testnet: bool, -// top_block_hash: String, -// tx_count: u64, -// tx_pool_size: u64, -// update_available: bool, -// version: String, -// was_bootstrap_ever_used: bool, -// white_peerlist_size: u64, -// wide_cumulative_difficulty: String, -// wide_difficulty: String, -// } -// } - -// define_request_and_response! { -// hard_fork_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1958..=1995, -// HardForkInfo, -// Request {}, -// AccessResponseBase { -// earliest_height: u64, -// enabled: bool, -// state: u32, -// threshold: u32, -// version: u8, -// votes: u32, -// voting: u8, -// window: u32, -// } -// } - -// define_request_and_response! { -// set_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2032..=2067, -// SetBans, -// Request { -// bans: Vec, -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// get_bans, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1997..=2030, -// GetBans, -// Request {}, -// ResponseBase { -// bans: Vec, -// } -// } - -// define_request_and_response! { -// banned, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2069..=2094, -// Banned, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Request { -// address: String, -// }, -// #[derive(Copy)] -// Response { -// banned: bool, -// seconds: u32, -// status: Status, -// } -// } - -// define_request_and_response! { -// flush_txpool, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2096..=2116, -// FlushTransactionPool, -// Request { -// txids: Vec = default_vec::(), "default_vec", -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// get_output_histogram, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2118..=2168, -// GetOutputHistogram, -// Request { -// amounts: Vec, -// min_count: u64, -// max_count: u64, -// unlocked: bool, -// recent_cutoff: u64, -// }, -// AccessResponseBase { -// histogram: Vec, -// } -// } - -// define_request_and_response! { -// get_coinbase_tx_sum, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2213..=2248, -// GetCoinbaseTxSum, -// Request { -// height: u64, -// count: u64, -// }, -// AccessResponseBase { -// emission_amount: u64, -// emission_amount_top64: u64, -// fee_amount: u64, -// fee_amount_top64: u64, -// wide_emission_amount: String, -// wide_fee_amount: String, -// } -// } - -// define_request_and_response! { -// get_version, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2170..=2211, -// GetVersion, -// Request {}, -// ResponseBase { -// version: u32, -// release: bool, -// #[serde(skip_serializing_if = "is_zero")] -// current_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "is_zero")] -// target_height: u64 = default_zero(), "default_zero", -// #[serde(skip_serializing_if = "Vec::is_empty")] -// hard_forks: Vec = default_vec(), "default_vec", -// } -// } - -// define_request_and_response! { -// get_fee_estimate, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2250..=2277, -// GetFeeEstimate, -// Request {}, -// AccessResponseBase { -// fee: u64, -// fees: Vec, -// #[serde(skip_serializing_if = "is_one")] -// quantization_mask: u64, -// } -// } - -// define_request_and_response! { -// get_alternate_chains, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2279..=2310, -// GetAlternateChains, -// Request {}, -// ResponseBase { -// chains: Vec, -// } -// } - -// define_request_and_response! { -// relay_tx, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2361..=2381, -// RelayTx, -// Request { -// txids: Vec, -// }, -// #[derive(Copy)] -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// status: Status, -// } -// } - -// define_request_and_response! { -// sync_info, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2383..=2443, -// SyncInfo, -// Request {}, -// AccessResponseBase { -// height: u64, -// next_needed_pruning_seed: u32, -// overview: String, -// // FIXME: This is a `std::list` in `monerod` because...? -// peers: Vec, -// // FIXME: This is a `std::list` in `monerod` because...? -// spans: Vec, -// target_height: u64, -// } -// } - -// define_request_and_response! { -// get_txpool_backlog, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1637..=1664, -// GetTransactionPoolBacklog, -// Request {}, -// ResponseBase { -// // TODO: this is a [`BinaryString`]. -// backlog: Vec, -// } -// } - -// define_request_and_response! { -// get_output_distribution, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2445..=2520, -// /// This type is also used in the (undocumented) -// /// [`/get_output_distribution.bin`](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L138) -// /// binary endpoint. -// GetOutputDistribution, -// Request { -// amounts: Vec, -// binary: bool, -// compress: bool, -// cumulative: bool, -// from_height: u64, -// to_height: u64, -// }, -// /// TODO: this request has custom serde: -// /// -// AccessResponseBase { -// distributions: Vec, -// } -// } - -// define_request_and_response! { -// get_miner_data, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 996..=1044, -// GetMinerData, -// Request {}, -// ResponseBase { -// major_version: u8, -// height: u64, -// prev_id: String, -// seed_hash: String, -// difficulty: String, -// median_weight: u64, -// already_generated_coins: u64, -// } -// } - -// define_request_and_response! { -// prune_blockchain, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2747..=2772, -// PruneBlockchain, -// #[derive(Copy)] -// Request { -// check: bool = default_false(), "default_false", -// }, -// #[derive(Copy)] -// ResponseBase { -// pruned: bool, -// pruning_seed: u32, -// } -// } - -// define_request_and_response! { -// calc_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1046..=1066, -// CalcPow, -// Request { -// major_version: u8, -// height: u64, -// block_blob: String, -// seed_hash: String, -// }, -// #[cfg_attr(feature = "serde", serde(transparent))] -// #[repr(transparent)] -// Response { -// pow_hash: String, -// } -// } - -// define_request_and_response! { -// flush_cache, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 2774..=2796, -// FlushCache, -// #[derive(Copy)] -// Request { -// bad_txs: bool = default_false(), "default_false", -// bad_blocks: bool = default_false(), "default_false", -// }, -// ResponseBase {} -// } - -// define_request_and_response! { -// add_aux_pow, -// cc73fe71162d564ffda8e549b79a350bca53c454 => -// core_rpc_server_commands_defs.h => 1068..=1112, -// AddAuxPow, -// Request { -// blocktemplate_blob: String, -// aux_pow: Vec, -// }, -// ResponseBase { -// blocktemplate_blob: String, -// blockhashing_blob: String, -// merkle_root: String, -// merkle_tree_depth: u64, -// aux_pow: Vec, -// } -// } - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} From b8966761e3cb57203e6242cc87cc698367620be9 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 23 Jul 2024 21:04:05 -0400 Subject: [PATCH 61/93] rewrite interface fns --- Cargo.lock | 49 +------ Cargo.toml | 1 + rpc/interface/Cargo.toml | 3 +- rpc/interface/src/free.rs | 119 +++++++-------- rpc/interface/src/route/bin.rs | 97 +++++++++++++ rpc/interface/src/{route.rs => route/json.rs} | 49 +------ rpc/interface/src/route/mod.rs | 6 + rpc/interface/src/route/other.rs | 135 ++++++++++++++++++ rpc/interface/src/route/unknown.rs | 40 ++++++ rpc/types/Cargo.toml | 3 +- rpc/types/src/bin.rs | 44 +++++- rpc/types/src/json.rs | 4 +- 12 files changed, 396 insertions(+), 154 deletions(-) create mode 100644 rpc/interface/src/route/bin.rs rename rpc/interface/src/{route.rs => route/json.rs} (65%) create mode 100644 rpc/interface/src/route/mod.rs create mode 100644 rpc/interface/src/route/other.rs create mode 100644 rpc/interface/src/route/unknown.rs diff --git a/Cargo.lock b/Cargo.lock index d437e7e54..d9bc94fb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,14 +114,11 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "axum-macros", "bytes", "futures-util", "http", "http-body", "http-body-util", - "hyper", - "hyper-util", "itoa", "matchit", "memchr", @@ -132,13 +129,10 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "serde_urlencoded", "sync_wrapper 1.0.1", - "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -159,19 +153,6 @@ dependencies = [ "sync_wrapper 0.1.2", "tower-layer", "tower-service", - "tracing", -] - -[[package]] -name = "axum-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.66", ] [[package]] @@ -402,7 +383,7 @@ version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.66", @@ -830,6 +811,7 @@ dependencies = [ "cuprate-epee-encoding", "cuprate-json-rpc", "cuprate-rpc-types", + "paste", "serde", "tower", ] @@ -838,6 +820,7 @@ dependencies = [ name = "cuprate-rpc-types" version = "0.0.0" dependencies = [ + "axum", "cuprate-epee-encoding", "cuprate-fixed-bytes", "monero-serai", @@ -1270,12 +1253,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1387,12 +1364,6 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.3.1" @@ -1405,7 +1376,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2478,18 +2448,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.8" @@ -2864,7 +2822,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/Cargo.toml b/Cargo.toml index 9b090ba60..ffc089306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ opt-level = 1 opt-level = 3 [workspace.dependencies] +axum = { version = "0.7.5", default-features = false } async-trait = { version = "0.1.74", default-features = false } bitflags = { version = "2.4.2", default-features = false } borsh = { version = "1.2.1", default-features = false } diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index e8ae5b282..5c5848fd2 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -16,8 +16,9 @@ cuprate-epee-encoding = { path = "../../net/epee-encoding" } cuprate-json-rpc = { path = "../json-rpc" } cuprate-rpc-types = { path = "../types" } -axum = { version = "0.7.5", features = ["macros"] } +axum = { workspace = true, features = ["json"] } serde = { workspace = true } tower = { workspace = true } +paste = { workspace = true } [dev-dependencies] diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index a49fae244..3e1781f04 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -7,77 +7,80 @@ use axum::{extract::State, routing::method_routing::get, Router}; use tower::Service; use crate::{ - error::Error, request::Request, response::Response, route, rpc_handler::RpcHandler, RpcState, + error::Error, + request::Request, + response::Response, + route::{self, bin}, + rpc_handler::RpcHandler, + RpcState, }; //---------------------------------------------------------------------------------------------------- Router /// TODO #[allow(clippy::needless_pass_by_value)] +#[rustfmt::skip] pub fn create_router() -> Router { // List of `monerod` routes: // - let mut router = Router::new(); + let router = Router::new(); - // JSON-RPC route. - router = router.route("/json_rpc", get(route::json_rpc::)); + // // JSON-RPC route. + // router = router.route("/json_rpc", get(route::json_rpc::)); - // Other JSON routes. - for other_route in [ - "/get_height", - "/getheight", - "/get_transactions", - "/gettransactions", - "/get_alt_blocks_hashes", - "/is_key_image_spent", - "/send_raw_transaction", - "/sendrawtransaction", - "/start_mining", - "/stop_mining", - "/mining_status", - "/save_bc", - "/get_peer_list", - "/get_public_nodes", - "/set_log_hash_rate", - "/set_log_level", - "/set_log_categories", - "/get_transaction_pool", - "/get_transaction_pool_hashes", - "/get_transaction_pool_stats", - "/set_bootstrap_daemon", - "/stop_daemon", - "/get_info", - "/getinfo", - "/get_net_stats", - "/get_limit", - "/set_limit", - "/out_peers", - "/in_peers", - "/get_outs", - "/update", - "/pop_blocks", - ] { - router = router.route(other_route, get(route::other::)); - } + // // Other JSON routes. + // for other_route in [ + // "/get_height", + // "/getheight", + // "/get_transactions", + // "/gettransactions", + // "/get_alt_blocks_hashes", + // "/is_key_image_spent", + // "/send_raw_transaction", + // "/sendrawtransaction", + // "/start_mining", + // "/stop_mining", + // "/mining_status", + // "/save_bc", + // "/get_peer_list", + // "/get_public_nodes", + // "/set_log_hash_rate", + // "/set_log_level", + // "/set_log_categories", + // "/get_transaction_pool", + // "/get_transaction_pool_hashes", + // "/get_transaction_pool_stats", + // "/set_bootstrap_daemon", + // "/stop_daemon", + // "/get_info", + // "/getinfo", + // "/get_net_stats", + // "/get_limit", + // "/set_limit", + // "/out_peers", + // "/in_peers", + // "/get_outs", + // "/update", + // "/pop_blocks", + // ] { + // router = router.route(other_route, get(route::other::)); + // } // Binary routes. - for binary_route in [ - "/get_blocks.bin", - "/getblocks.bin", - "/get_blocks_by_height.bin", - "/getblocks_by_height.bin", - "/get_hashes.bin", - "/gethashes.bin", - "/get_o_indexes.bin", - "/get_outs.bin", - "/get_transaction_pool_hashes.bin", - "/get_output_distribution.bin", - ] { - router = router.route(binary_route, get(route::bin::)); - } + router + .route("/get_blocks.bin", get(bin::get_blocks::)) + .route("/getblocks.bin", get(bin::get_blocks::)) + .route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::)) + .route("/getblocks_by_height.bin", get(bin::get_blocks_by_height::)) + .route("/get_hashes.bin", get(bin::get_hashes::)) + .route("/gethashes.bin", get(bin::get_hashes::)) + .route("/get_o_indexes.bin", get(bin::get_o_indexes::)) + .route("/get_outs.bin", get(bin::get_outs::)) + .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) + .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) - // Unknown route. - router = router.route("/*", get(route::unknown)); + // // Unknown route. + // router = router.route("/*", get(route::unknown)); - router + // router } diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs new file mode 100644 index 000000000..c3f649544 --- /dev/null +++ b/rpc/interface/src/route/bin.rs @@ -0,0 +1,97 @@ +//! TODO +#![allow(clippy::unused_async)] // TODO: remove after impl + +//---------------------------------------------------------------------------------------------------- Import +use std::{borrow::Cow, future::Future, sync::Arc}; + +use axum::{body::Bytes, extract::State, http::StatusCode, response::IntoResponse, Json}; +use cuprate_json_rpc::{ + error::{ErrorCode, ErrorObject}, + Id, +}; +use tower::{Service, ServiceExt}; + +use cuprate_epee_encoding::from_bytes; +use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksByHeightResponse, + GetBlocksRequest, GetBlocksResponse, GetHashesRequest, GetHashesResponse, + GetOutputIndexesRequest, GetOutputIndexesResponse, GetOutsRequest, GetOutsResponse, + GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, + }, + json::{ + GetOutputDistributionRequest, GetOutputDistributionResponse, JsonRpcRequest, + JsonRpcResponse, + }, + other::{OtherRequest, OtherResponse}, + RpcRequest, +}; + +use crate::{ + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, +}; + +//---------------------------------------------------------------------------------------------------- Routes +/// TODO +macro_rules! serialize_binary_request { + ($variant:ident, $request:ident) => { + BinRequest::$variant( + from_bytes(&mut $request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?, + ) + }; + ($variant:ident, $request:ident $(=> $constructor:expr)?) => { + BinRequest::$variant(()) + }; +} + +/// TODO +macro_rules! generate_binary_endpoints { + ($( + $endpoint:ident => $variant:ident $(=> $constructor:expr)? + ),*) => { paste::paste! { + $( + /// TODO + #[allow(unused_mut)] + pub(crate) async fn $endpoint( + State(handler): State, + mut request: Bytes, + ) -> Result { + // Serialize into the request type. + let request = serialize_binary_request!($variant, request $(=> $constructor)?); + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler did not return a binary response"); + }; + + // Assert the response from the inner handler is correct. + let BinResponse::$variant(response) = response else { + panic!("RPC handler returned incorrect response"); + }; + + // Serialize to bytes and respond. + match cuprate_epee_encoding::to_bytes(response) { + Ok(bytes) => Ok(bytes.freeze()), + Err(e) => Err(StatusCode::INTERNAL_SERVER_ERROR), + } + } + )* + }}; +} + +generate_binary_endpoints! { + get_blocks => GetBlocks, + get_blocks_by_height => GetBlocksByHeight, + get_hashes => GetHashes, + get_o_indexes => GetOutputIndexes, + get_outs => GetOuts, + get_transaction_pool_hashes => GetTransactionPoolHashes => (), + get_output_distribution => GetOutputDistribution +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route.rs b/rpc/interface/src/route/json.rs similarity index 65% rename from rpc/interface/src/route.rs rename to rpc/interface/src/route/json.rs index cd01e380d..ed885fbe3 100644 --- a/rpc/interface/src/route.rs +++ b/rpc/interface/src/route/json.rs @@ -1,4 +1,5 @@ //! TODO +#![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import use std::{borrow::Cow, future::Future, sync::Arc}; @@ -10,7 +11,12 @@ use cuprate_json_rpc::{ }; use tower::{Service, ServiceExt}; +use cuprate_epee_encoding::from_bytes; use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, + GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, + }, json::{JsonRpcRequest, JsonRpcResponse}, other::{OtherRequest, OtherResponse}, RpcRequest, @@ -55,49 +61,6 @@ pub(crate) async fn json_rpc( Ok(Json(response)) } -/// TODO -pub(crate) async fn bin( - State(handler): State, - request: Bytes, // TODO: BinRequest -) -> Result, StatusCode> { - // TODO - // if handler.state().restricted() && request.is_restricted() { - if handler.state().restricted() { - return Err(StatusCode::NOT_FOUND); - } - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - let binary: Vec = todo!(); // TODO: serialize response. - - Ok(binary) -} - -/// TODO -pub(crate) async fn other( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - if handler.state().restricted() && request.is_restricted() { - todo!(); - } - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn unknown() -> StatusCode { - StatusCode::NOT_FOUND -} - //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs new file mode 100644 index 000000000..40beadad5 --- /dev/null +++ b/rpc/interface/src/route/mod.rs @@ -0,0 +1,6 @@ +//! TODO + +pub(crate) mod bin; +mod json; +pub(crate) mod other; +mod unknown; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs new file mode 100644 index 000000000..2e082acfa --- /dev/null +++ b/rpc/interface/src/route/other.rs @@ -0,0 +1,135 @@ +//! TODO +#![allow(clippy::unused_async)] // TODO: remove after impl + +//---------------------------------------------------------------------------------------------------- Import +use std::{borrow::Cow, future::Future, sync::Arc}; + +use axum::{body::Bytes, extract::State, http::StatusCode, Json}; +use cuprate_json_rpc::{ + error::{ErrorCode, ErrorObject}, + Id, +}; +use tower::{Service, ServiceExt}; + +use cuprate_epee_encoding::from_bytes; +use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, + GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, + }, + json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, + RpcRequest, +}; + +use crate::{ + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, +}; + +//---------------------------------------------------------------------------------------------------- Routes +/// TODO +pub(crate) async fn json_rpc( + State(handler): State, + Json(request): Json>, +) -> Result>, StatusCode> { + // Return early if this RPC server is restricted and + // the requested method is only for non-restricted RPC. + if handler.state().restricted() && request.body.is_restricted() { + let error_object = ErrorObject { + code: ErrorCode::ServerError(-1 /* TODO */), + message: Cow::Borrowed("Restricted. TODO"), + data: None, + }; + + // JSON-RPC 2.0 rule: + // If there was an error in detecting the `Request`'s ID, + // the `Response` must contain an `Id::Null` + let id = request.id.unwrap_or(Id::Null); + + let response = cuprate_json_rpc::Response::err(id, error_object); + + // TODO + return Ok(Json(response)); + } + + // TODO: call handler + let Response::JsonRpc(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) +} + +/// TODO +pub(crate) async fn binary( + State(handler): State, + endpoint: &'static str, + mut request: Bytes, // TODO: BinRequest +) -> Result { + let error = |_| StatusCode::INTERNAL_SERVER_ERROR; + + let request = match endpoint { + "/get_blocks.bin" | "/getblocks.bin" => { + BinRequest::GetBlocks(from_bytes(&mut request).map_err(error)?) + } + "/get_blocks_by_height.bin" | "/getblocks_by_height.bin" => { + BinRequest::GetBlocksByHeight(from_bytes(&mut request).map_err(error)?) + } + "/get_hashes.bin" | "/gethashes.bin" => { + BinRequest::GetHashes(from_bytes(&mut request).map_err(error)?) + } + "/get_o_indexes.bin" => { + BinRequest::GetOutputIndexes(from_bytes(&mut request).map_err(error)?) + } + "/get_outs.bin" => BinRequest::GetOuts(from_bytes(&mut request).map_err(error)?), + "/get_transaction_pool_hashes.bin" => BinRequest::GetTransactionPoolHashes(()), + "/get_output_distribution.bin" => { + BinRequest::GetOutputDistribution(from_bytes(&mut request).map_err(error)?) + } + + // INVARIANT: + // The `create_router` function only passes the above endpoints. + _ => unreachable!(), + }; + + // TODO + if handler.state().restricted() && request.is_restricted() { + return Err(StatusCode::NOT_FOUND); + } + + // TODO: call handler + let Response::Binary(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(response) +} + +/// TODO +pub(crate) async fn other( + State(handler): State, + Json(request): Json, +) -> Result, StatusCode> { + if handler.state().restricted() && request.is_restricted() { + todo!(); + } + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler returned incorrect response"); + }; + + Ok(Json(response)) +} + +/// TODO +pub(crate) async fn unknown() -> StatusCode { + StatusCode::NOT_FOUND +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/unknown.rs new file mode 100644 index 000000000..84c68e5fa --- /dev/null +++ b/rpc/interface/src/route/unknown.rs @@ -0,0 +1,40 @@ +//! TODO +#![allow(clippy::unused_async)] // TODO: remove after impl + +//---------------------------------------------------------------------------------------------------- Import +use std::{borrow::Cow, future::Future, sync::Arc}; + +use axum::{body::Bytes, extract::State, http::StatusCode, Json}; +use cuprate_json_rpc::{ + error::{ErrorCode, ErrorObject}, + Id, +}; +use tower::{Service, ServiceExt}; + +use cuprate_epee_encoding::from_bytes; +use cuprate_rpc_types::{ + bin::{ + BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, + GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, + }, + json::{JsonRpcRequest, JsonRpcResponse}, + other::{OtherRequest, OtherResponse}, + RpcRequest, +}; + +use crate::{ + error::Error, request::Request, response::Response, rpc_handler::RpcHandler, + rpc_state::RpcState, +}; + +//---------------------------------------------------------------------------------------------------- Routes +/// TODO +pub(crate) async fn unknown() -> StatusCode { + StatusCode::NOT_FOUND +} + +//---------------------------------------------------------------------------------------------------- Tests +#[cfg(test)] +mod test { + // use super::*; +} diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 1176526ad..22df6590d 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/types" keywords = ["cuprate", "rpc", "types", "monero"] [features] -default = ["serde", "epee"] +default = ["serde", "epee", "axum"] serde = ["dep:serde", "cuprate-fixed-bytes/serde"] epee = ["dep:cuprate-epee-encoding"] @@ -17,6 +17,7 @@ epee = ["dep:cuprate-epee-encoding"] cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true } cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } +axum = { workspace = true, optional = true } monero-serai = { workspace = true } paste = { workspace = true } serde = { workspace = true, optional = true } diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 7f09dc306..157c60103 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -5,6 +5,15 @@ //---------------------------------------------------------------------------------------------------- Import use cuprate_fixed_bytes::ByteArrayVec; +#[cfg(feature = "axum")] +use axum::{ + async_trait, + body::{Body, Bytes}, + extract::{FromRequest, Request}, + http::StatusCode, + response::{IntoResponse, Response}, +}; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -31,13 +40,13 @@ define_request_and_response! { core_rpc_server_commands_defs.h => 162..=262, GetBlocks, Request { - requested_info: u8 = default_zero(), "default_zero", + 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", + pool_info_since: u64 = default_zero::(), "default_zero", }, // TODO: this has custom epee (de)serialization. // @@ -150,6 +159,7 @@ pub enum BinRequest { GetOutputIndexes(GetOutputIndexesRequest), GetOuts(GetOutsRequest), GetTransactionPoolHashes(GetTransactionPoolHashesRequest), + GetOutputDistribution(crate::json::GetOutputDistributionRequest), } impl RpcRequest for BinRequest { @@ -162,7 +172,8 @@ impl RpcRequest for BinRequest { | Self::GetHashes(_) | Self::GetOutputIndexes(_) | Self::GetOuts(_) - | Self::GetTransactionPoolHashes(()) => false, + | Self::GetTransactionPoolHashes(()) + | Self::GetOutputDistribution(_) => false, } } } @@ -179,6 +190,33 @@ pub enum BinResponse { GetOutputIndexes(GetOutputIndexesResponse), GetOuts(GetOutsResponse), GetTransactionPoolHashes(GetTransactionPoolHashesResponse), + GetOutputDistribution(crate::json::GetOutputDistributionResponse), +} + +#[cfg(feature = "axum")] +#[cfg(feature = "epee")] +impl axum::response::IntoResponse for BinResponse { + fn into_response(self) -> axum::response::Response { + use cuprate_epee_encoding::to_bytes; + + let mut bytes = axum::body::Bytes::new(); + let writer = &mut bytes; + + let result = match self { + Self::GetBlocks(s) => to_bytes(s), + Self::GetBlocksByHeight(s) => to_bytes(s), + Self::GetHashes(s) => to_bytes(s), + Self::GetOutputIndexes(s) => to_bytes(s), + Self::GetOuts(s) => to_bytes(s), + Self::GetTransactionPoolHashes(s) => to_bytes(s), + Self::GetOutputDistribution(s) => to_bytes(s), + }; + + match result { + Ok(bytes) => bytes.into_response(), + Err(e) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), + } + } } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index fdb2a0358..666666c79 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -483,9 +483,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", } From b2d7e8a9819fed7f7bb7e67d618315d7acef6dac Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 16:14:42 -0400 Subject: [PATCH 62/93] types: remove `()` type alias, add `(restricted)` --- rpc/types/src/bin.rs | 2 +- rpc/types/src/json.rs | 48 ++++++++++++++++++------------------ rpc/types/src/macros.rs | 48 +++++++++++++++++++++--------------- rpc/types/src/other.rs | 54 ++++++++++++++++++++--------------------- 4 files changed, 80 insertions(+), 72 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 157c60103..7c2242710 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -172,7 +172,7 @@ impl RpcRequest for BinRequest { | Self::GetHashes(_) | Self::GetOutputIndexes(_) | Self::GetOuts(_) - | Self::GetTransactionPoolHashes(()) + | Self::GetTransactionPoolHashes(_) | Self::GetOutputDistribution(_) => false, } } diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 666666c79..f36df4696 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -206,7 +206,7 @@ define_request_and_response! { generateblocks, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1130..=1161, - GenerateBlocks, + GenerateBlocks (restricted), Request { amount_of_blocks: u64, prev_block: String, @@ -306,7 +306,7 @@ define_request_and_response! { get_connections, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1734..=1754, - GetConnections, + GetConnections (restricted), Request {}, ResponseBase { // FIXME: This is a `std::list` in `monerod` because...? @@ -385,7 +385,7 @@ define_request_and_response! { set_bans, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2032..=2067, - SetBans, + SetBans (restricted), Request { bans: Vec, }, @@ -396,7 +396,7 @@ define_request_and_response! { get_bans, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1997..=2030, - GetBans, + GetBans (restricted), Request {}, ResponseBase { bans: Vec, @@ -407,7 +407,7 @@ define_request_and_response! { banned, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2069..=2094, - Banned, + Banned (restricted), #[cfg_attr(feature = "serde", serde(transparent))] #[repr(transparent)] Request { @@ -425,7 +425,7 @@ define_request_and_response! { flush_txpool, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2096..=2116, - FlushTransactionPool, + FlushTransactionPool (restricted), Request { txids: Vec = default_vec::(), "default_vec", }, @@ -458,7 +458,7 @@ define_request_and_response! { get_coinbase_tx_sum, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2213..=2248, - GetCoinbaseTxSum, + GetCoinbaseTxSum (restricted), Request { height: u64, count: u64, @@ -509,7 +509,7 @@ define_request_and_response! { get_alternate_chains, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2279..=2310, - GetAlternateChains, + GetAlternateChains (restricted), Request {}, ResponseBase { chains: Vec, @@ -520,7 +520,7 @@ define_request_and_response! { relay_tx, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2361..=2381, - RelayTx, + RelayTx (restricted), Request { txids: Vec, }, @@ -536,7 +536,7 @@ define_request_and_response! { sync_info, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2383..=2443, - SyncInfo, + SyncInfo (restricted), Request {}, AccessResponseBase { height: u64, @@ -606,7 +606,7 @@ define_request_and_response! { prune_blockchain, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2747..=2772, - PruneBlockchain, + PruneBlockchain (restricted), #[derive(Copy)] Request { check: bool = default_false(), "default_false", @@ -622,7 +622,7 @@ define_request_and_response! { calc_pow, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1046..=1066, - CalcPow, + CalcPow (restricted), Request { major_version: u8, height: u64, @@ -640,7 +640,7 @@ define_request_and_response! { flush_cache, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2774..=2796, - FlushCache, + FlushCache (restricted), #[derive(Copy)] Request { bad_txs: bool = default_false(), "default_false", @@ -709,7 +709,7 @@ impl RpcRequest for JsonRpcRequest { match self { // Normal methods. These are allowed // even on restricted RPC servers (18089). - Self::GetBlockCount(()) + Self::GetBlockCount(_) | Self::OnGetBlockHash(_) | Self::SubmitBlock(_) | Self::GetLastBlockHeader(_) @@ -717,27 +717,27 @@ impl RpcRequest for JsonRpcRequest { | Self::GetBlockHeaderByHeight(_) | Self::GetBlockHeadersRange(_) | Self::GetBlock(_) - | Self::GetInfo(()) - | Self::HardForkInfo(()) + | Self::GetInfo(_) + | Self::HardForkInfo(_) | Self::GetOutputHistogram(_) - | Self::GetVersion(()) - | Self::GetFeeEstimate(()) - | Self::GetTransactionPoolBacklog(()) - | Self::GetMinerData(()) + | Self::GetVersion(_) + | Self::GetFeeEstimate(_) + | Self::GetTransactionPoolBacklog(_) + | Self::GetMinerData(_) | Self::AddAuxPow(_) => false, // Restricted methods. These are only allowed // for unrestricted RPC servers (18081). Self::GenerateBlocks(_) - | Self::GetConnections(()) + | Self::GetConnections(_) | Self::SetBans(_) - | Self::GetBans(()) + | Self::GetBans(_) | Self::Banned(_) | Self::FlushTransactionPool(_) | Self::GetCoinbaseTxSum(_) - | Self::GetAlternateChains(()) + | Self::GetAlternateChains(_) | Self::RelayTx(_) - | Self::SyncInfo(()) + | Self::SyncInfo(_) | Self::PruneBlockchain(_) | Self::CalcPow(_) | Self::FlushCache(_) => true, diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index e13013872..03244b754 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -60,7 +60,7 @@ macro_rules! define_request_and_response { // Attributes added here will apply to _both_ // request and response types. $( #[$type_attr:meta] )* - $type_name:ident, + $type_name:ident $(($restricted:ident))?, // The request type (and any doc comments, derives, etc). $( #[$request_type_attr:meta] )* @@ -100,7 +100,7 @@ macro_rules! define_request_and_response { $( #[$type_attr] )* /// $( #[$request_type_attr] )* - [<$type_name Request>] { + [<$type_name Request>] $(($restricted))? { $( $( #[$request_field_attr] )* $request_field: $request_field_type @@ -141,6 +141,29 @@ macro_rules! define_request_and_response { } pub(crate) use define_request_and_response; +//---------------------------------------------------------------------------------------------------- impl_rpc_request +/// TODO +macro_rules! impl_rpc_request { + // TODO + ($t:ident, $restricted:ident) => { + impl $crate::RpcRequest for $t { + fn is_restricted(&self) -> bool { + true + } + } + }; + + // TODO + ($t:ident) => { + impl $crate::RpcRequest for $t { + fn is_restricted(&self) -> bool { + false + } + } + }; +} +pub(crate) use impl_rpc_request; + //---------------------------------------------------------------------------------------------------- define_request /// Define a request type. /// @@ -148,28 +171,11 @@ pub(crate) use define_request_and_response; /// /// `__` is used to notate that this shouldn't be called directly. macro_rules! __define_request { - //------------------------------------------------------------------------------ - // This branch will generate a type alias to `()` if only given `{}` as input. ( // Any doc comments, derives, etc. $( #[$attr:meta] )* // The response type. - $t:ident {} - ) => { - $( #[$attr] )* - /// - /// This request has no inputs. - pub type $t = (); - }; - - //------------------------------------------------------------------------------ - // This branch of the macro expects fields within the `{}`, - // and will generate a `struct` - ( - // Any doc comments, derives, etc. - $( #[$attr:meta] )* - // The response type. - $t:ident { + $t:ident $(($restricted:ident))? { // And any fields. $( $( #[$field_attr:meta] )* // field attributes @@ -195,6 +201,8 @@ macro_rules! __define_request { )* } + $crate::macros::impl_rpc_request!($t $(, $restricted)?); + #[cfg(feature = "epee")] ::cuprate_epee_encoding::epee_object! { $t, diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 3e9742806..2eecbf164 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -107,7 +107,7 @@ define_request_and_response! { start_mining, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 665..=691, - StartMining, + StartMining (restricted), Request { miner_address: String, threads_count: u64, @@ -121,7 +121,7 @@ define_request_and_response! { stop_mining, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 825..=843, - StopMining, + StopMining (restricted), Request {}, ResponseBase {} } @@ -130,7 +130,7 @@ define_request_and_response! { mining_status, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 846..=895, - MiningStatus, + MiningStatus (restricted), Request {}, ResponseBase { active: bool, @@ -155,7 +155,7 @@ define_request_and_response! { save_bc, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 898..=916, - SaveBc, + SaveBc (restricted), Request {}, ResponseBase {} } @@ -164,7 +164,7 @@ define_request_and_response! { get_peer_list, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1369..=1417, - GetPeerList, + GetPeerList (restricted), Request { public_only: bool = default_true(), "default_true", include_blocked: bool = default_false(), "default_false", @@ -179,7 +179,7 @@ define_request_and_response! { set_log_hash_rate, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1450..=1470, - SetLogHashRate, + SetLogHashRate (restricted), #[derive(Copy)] Request { visible: bool, @@ -191,7 +191,7 @@ define_request_and_response! { set_log_level, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1450..=1470, - SetLogLevel, + SetLogLevel (restricted), #[derive(Copy)] Request { level: u8, @@ -203,7 +203,7 @@ define_request_and_response! { set_log_categories, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1494..=1517, - SetLogCategories, + SetLogCategories (restricted), Request { categories: String = default_string(), "default_string", }, @@ -216,7 +216,7 @@ define_request_and_response! { set_bootstrap_daemon, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1785..=1812, - SetBootstrapDaemon, + SetBootstrapDaemon (restricted), Request { address: String, username: String, @@ -256,7 +256,7 @@ define_request_and_response! { stop_daemon, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1814..=1831, - StopDaemon, + StopDaemon (restricted), Request {}, ResponseBase { status: Status, @@ -279,7 +279,7 @@ define_request_and_response! { set_limit, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1876..=1903, - SetLimit, + SetLimit (restricted), Request { limit_down: i64, limit_up: i64, @@ -294,7 +294,7 @@ define_request_and_response! { out_peers, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1876..=1903, - OutPeers, + OutPeers (restricted), Request { set: bool = default_true(), "default_true", out_peers: u32, @@ -308,7 +308,7 @@ define_request_and_response! { get_net_stats, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 793..=822, - GetNetStats, + GetNetStats (restricted), Request {}, ResponseBase { start_time: u64, @@ -337,7 +337,7 @@ define_request_and_response! { update, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2324..=2359, - Update, + Update (restricted), Request { command: String, path: String = default_string(), "default_string", @@ -356,7 +356,7 @@ define_request_and_response! { pop_blocks, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2722..=2745, - PopBlocks, + PopBlocks (restricted), Request { nblocks: u64, }, @@ -394,7 +394,7 @@ define_request_and_response! { UNDOCUMENTED_ENDPOINT, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1419..=1448, - GetPublicNodes, + GetPublicNodes (restricted), Request { gray: bool = default_false(), "default_false", white: bool = default_true(), "default_true", @@ -446,33 +446,33 @@ impl RpcRequest for OtherRequest { match self { // Normal methods. These are allowed // even on restricted RPC servers (18089). - Self::GetHeight(()) + Self::GetHeight(_) | Self::GetTransactions(_) - | Self::GetAltBlocksHashes(()) + | Self::GetAltBlocksHashes(_) | Self::IsKeyImageSpent(_) | Self::SendRawTransaction(_) - | Self::GetTransactionPool(()) - | Self::GetTransactionPoolStats(()) - | Self::GetLimit(()) + | Self::GetTransactionPool(_) + | Self::GetTransactionPoolStats(_) + | Self::GetLimit(_) | Self::GetOuts(_) | Self::GetTxIdsLoose(_) - | Self::GetTransactionPoolHashes(()) => false, + | Self::GetTransactionPoolHashes(_) => false, // Restricted methods. These are only allowed // for unrestricted RPC servers (18081). // TODO Self::StartMining(_) - | Self::StopMining(()) - | Self::MiningStatus(()) - | Self::SaveBc(()) + | Self::StopMining(_) + | Self::MiningStatus(_) + | Self::SaveBc(_) | Self::GetPeerList(_) | Self::SetLogHashRate(_) | Self::SetLogLevel(_) | Self::SetLogCategories(_) | Self::SetBootstrapDaemon(_) - | Self::GetNetStats(()) + | Self::GetNetStats(_) | Self::SetLimit(_) - | Self::StopDaemon(()) + | Self::StopDaemon(_) | Self::OutPeers(_) | Self::Update(_) | Self::PopBlocks(_) From 5dad516076bde1e8be6853ed8245d5bb3b1abe83 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 16:27:24 -0400 Subject: [PATCH 63/93] types: add `other::InPeers` --- rpc/types/src/other.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 2eecbf164..b573a8d73 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -304,6 +304,20 @@ define_request_and_response! { } } +define_request_and_response! { + in_peers, + cc73fe71162d564ffda8e549b79a350bca53c454 => + core_rpc_server_commands_defs.h => 1932..=1956, + InPeers (restricted), + Request { + set: bool = default_true(), "default_true", + in_peers: u32, + }, + ResponseBase { + in_peers: u32, + } +} + define_request_and_response! { get_net_stats, cc73fe71162d564ffda8e549b79a350bca53c454 => @@ -432,6 +446,7 @@ pub enum OtherRequest { GetLimit(GetLimitRequest), SetLimit(SetLimitRequest), OutPeers(OutPeersRequest), + InPeers(InPeersRequest), GetNetStats(GetNetStatsRequest), GetOuts(GetOutsRequest), Update(UpdateRequest), @@ -474,6 +489,7 @@ impl RpcRequest for OtherRequest { | Self::SetLimit(_) | Self::StopDaemon(_) | Self::OutPeers(_) + | Self::InPeers(_) | Self::Update(_) | Self::PopBlocks(_) | Self::GetPublicNodes(_) => true, @@ -507,6 +523,7 @@ pub enum OtherResponse { GetLimit(GetLimitResponse), SetLimit(SetLimitResponse), OutPeers(OutPeersResponse), + InPeers(InPeersResponse), GetNetStats(GetNetStatsResponse), GetOuts(GetOutsResponse), Update(UpdateResponse), From fa576ed95a7d86c41e363b090ca99f3db2acc3d2 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 16:27:55 -0400 Subject: [PATCH 64/93] interface: routes --- rpc/interface/Cargo.toml | 2 +- rpc/interface/src/free.rs | 93 ++++++++--------- rpc/interface/src/route/bin.rs | 24 ++--- rpc/interface/src/route/mod.rs | 4 +- rpc/interface/src/route/other.rs | 174 +++++++++++++------------------ rpc/interface/src/rpc_handler.rs | 1 + rpc/interface/src/rpc_state.rs | 1 + 7 files changed, 128 insertions(+), 171 deletions(-) diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 5c5848fd2..1fac2001d 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -14,7 +14,7 @@ default = [] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding" } cuprate-json-rpc = { path = "../json-rpc" } -cuprate-rpc-types = { path = "../types" } +cuprate-rpc-types = { path = "../types", features = ["serde", "epee", "axum"] } axum = { workspace = true, features = ["json"] } serde = { workspace = true } diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 3e1781f04..47988a701 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -10,7 +10,7 @@ use crate::{ error::Error, request::Request, response::Response, - route::{self, bin}, + route::{bin, json, other, unknown}, rpc_handler::RpcHandler, RpcState, }; @@ -23,51 +23,43 @@ pub fn create_router() -> Router { // List of `monerod` routes: // - let router = Router::new(); + // let mut router = Router::new(); - // // JSON-RPC route. - // router = router.route("/json_rpc", get(route::json_rpc::)); - - // // Other JSON routes. - // for other_route in [ - // "/get_height", - // "/getheight", - // "/get_transactions", - // "/gettransactions", - // "/get_alt_blocks_hashes", - // "/is_key_image_spent", - // "/send_raw_transaction", - // "/sendrawtransaction", - // "/start_mining", - // "/stop_mining", - // "/mining_status", - // "/save_bc", - // "/get_peer_list", - // "/get_public_nodes", - // "/set_log_hash_rate", - // "/set_log_level", - // "/set_log_categories", - // "/get_transaction_pool", - // "/get_transaction_pool_hashes", - // "/get_transaction_pool_stats", - // "/set_bootstrap_daemon", - // "/stop_daemon", - // "/get_info", - // "/getinfo", - // "/get_net_stats", - // "/get_limit", - // "/set_limit", - // "/out_peers", - // "/in_peers", - // "/get_outs", - // "/update", - // "/pop_blocks", - // ] { - // router = router.route(other_route, get(route::other::)); - // } - - // Binary routes. - router + Router::new() + // JSON-RPC route. + .route("/json_rpc", get(json::json_rpc::)) + // Other JSON routes. + .route("/get_height", get(other::get_height::)) + .route("/getheight", get(other::get_height::)) + .route("/get_transactions", get(other::get_transactions::)) + .route("/gettransactions", get(other::get_transactions::)) + .route("/get_alt_blocks_hashes", get(other::get_alt_blocks_hashes::)) + .route("/is_key_image_spent", get(other::is_key_image_spent::)) + .route("/send_raw_transaction", get(other::send_raw_transaction::)) + .route("/sendrawtransaction", get(other::send_raw_transaction::)) + .route("/start_mining", get(other::start_mining::)) + .route("/stop_mining", get(other::stop_mining::)) + .route("/mining_status", get(other::mining_status::)) + .route("/save_bc", get(other::save_bc::)) + .route("/get_peer_list", get(other::get_peer_list::)) + .route("/get_public_nodes", get(other::get_public_nodes::)) + .route("/set_log_hash_rate", get(other::set_log_hash_rate::)) + .route("/set_log_level", get(other::set_log_level::)) + .route("/set_log_categories", get(other::set_log_categories::)) + .route("/get_transaction_pool", get(other::get_transaction_pool::)) + .route("/get_transaction_pool_hashes", get(other::get_transaction_pool_hashes::)) + .route("/get_transaction_pool_stats", get(other::get_transaction_pool_stats::)) + .route("/set_bootstrap_daemon", get(other::set_bootstrap_daemon::)) + .route("/stop_daemon", get(other::stop_daemon::)) + .route("/get_net_stats", get(other::get_net_stats::)) + .route("/get_limit", get(other::get_limit::)) + .route("/set_limit", get(other::set_limit::)) + .route("/out_peers", get(other::out_peers::)) + .route("/in_peers", get(other::in_peers::)) + .route("/get_outs", get(other::get_outs::)) + .route("/update", get(other::update::)) + .route("/pop_blocks", get(other::pop_blocks::)) + // Binary routes. .route("/get_blocks.bin", get(bin::get_blocks::)) .route("/getblocks.bin", get(bin::get_blocks::)) .route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::)) @@ -78,9 +70,10 @@ pub fn create_router() -> Router { .route("/get_outs.bin", get(bin::get_outs::)) .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) - - // // Unknown route. - // router = router.route("/*", get(route::unknown)); - - // router + // Unknown route (catch-all). + // + // Deprecated routes will also route here, list: + // - `get_info` + // - `getinfo` + .route("/*", get(unknown::unknown)) } diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index c3f649544..c294420fe 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -34,21 +34,9 @@ use crate::{ //---------------------------------------------------------------------------------------------------- Routes /// TODO -macro_rules! serialize_binary_request { - ($variant:ident, $request:ident) => { - BinRequest::$variant( - from_bytes(&mut $request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?, - ) - }; - ($variant:ident, $request:ident $(=> $constructor:expr)?) => { - BinRequest::$variant(()) - }; -} - -/// TODO -macro_rules! generate_binary_endpoints { +macro_rules! generate_endpoints { ($( - $endpoint:ident => $variant:ident $(=> $constructor:expr)? + $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( /// TODO @@ -58,7 +46,9 @@ macro_rules! generate_binary_endpoints { mut request: Bytes, ) -> Result { // Serialize into the request type. - let request = serialize_binary_request!($variant, request $(=> $constructor)?); + let request = BinRequest::$variant( + from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + ); // TODO: call handler let Response::Binary(response) = todo!() else { @@ -80,13 +70,13 @@ macro_rules! generate_binary_endpoints { }}; } -generate_binary_endpoints! { +generate_endpoints! { get_blocks => GetBlocks, get_blocks_by_height => GetBlocksByHeight, get_hashes => GetHashes, get_o_indexes => GetOutputIndexes, get_outs => GetOuts, - get_transaction_pool_hashes => GetTransactionPoolHashes => (), + get_transaction_pool_hashes => GetTransactionPoolHashes, get_output_distribution => GetOutputDistribution } diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 40beadad5..2c8c3063f 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,6 +1,6 @@ //! TODO pub(crate) mod bin; -mod json; +pub(crate) mod json; pub(crate) mod other; -mod unknown; +pub(crate) mod unknown; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 2e082acfa..5ad4ec3c0 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -11,14 +11,24 @@ use cuprate_json_rpc::{ }; use tower::{Service, ServiceExt}; -use cuprate_epee_encoding::from_bytes; use cuprate_rpc_types::{ - bin::{ - BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, - GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, + other::{ + GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse, + GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest, + GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest, + GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, + GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest, + GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse, + GetTxIdsLooseRequest, GetTxIdsLooseResponse, InPeersRequest, InPeersResponse, + IsKeyImageSpentRequest, IsKeyImageSpentResponse, MiningStatusRequest, MiningStatusResponse, + OtherRequest, OtherResponse, OutPeersRequest, OutPeersResponse, PopBlocksRequest, + PopBlocksResponse, SaveBcRequest, SaveBcResponse, SendRawTransactionRequest, + SendRawTransactionResponse, SetBootstrapDaemonRequest, SetBootstrapDaemonResponse, + SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest, SetLogCategoriesResponse, + SetLogHashRateRequest, SetLogHashRateResponse, SetLogLevelRequest, SetLogLevelResponse, + StartMiningRequest, StartMiningResponse, StopDaemonRequest, StopDaemonResponse, + StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, }, - json::{JsonRpcRequest, JsonRpcResponse}, - other::{OtherRequest, OtherResponse}, RpcRequest, }; @@ -29,103 +39,65 @@ use crate::{ //---------------------------------------------------------------------------------------------------- Routes /// TODO -pub(crate) async fn json_rpc( - State(handler): State, - Json(request): Json>, -) -> Result>, StatusCode> { - // Return early if this RPC server is restricted and - // the requested method is only for non-restricted RPC. - if handler.state().restricted() && request.body.is_restricted() { - let error_object = ErrorObject { - code: ErrorCode::ServerError(-1 /* TODO */), - message: Cow::Borrowed("Restricted. TODO"), - data: None, - }; - - // JSON-RPC 2.0 rule: - // If there was an error in detecting the `Request`'s ID, - // the `Response` must contain an `Id::Null` - let id = request.id.unwrap_or(Id::Null); - - let response = cuprate_json_rpc::Response::err(id, error_object); - - // TODO - return Ok(Json(response)); - } - - // TODO: call handler - let Response::JsonRpc(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) +macro_rules! generate_endpoints { + ($( + $endpoint:ident => $variant:ident $(=> $constructor:expr)? + ),*) => { paste::paste! { + $( + /// TODO + #[allow(unused_mut)] + pub(crate) async fn $endpoint( + State(handler): State, + Json(request): Json<[<$variant Request>]>, + ) -> Result]>, StatusCode> { + if handler.state().restricted() && request.is_restricted() { + todo!(); + } + + // TODO: call handler + let Response::Other(response) = todo!() else { + panic!("RPC handler did not return a binary response"); + }; + + // Assert the response from the inner handler is correct. + match response { + OtherResponse::$variant(response) => Ok(Json(response)), + _ => panic!("RPC handler returned incorrect response"), + } + } + )* + }}; } -/// TODO -pub(crate) async fn binary( - State(handler): State, - endpoint: &'static str, - mut request: Bytes, // TODO: BinRequest -) -> Result { - let error = |_| StatusCode::INTERNAL_SERVER_ERROR; - - let request = match endpoint { - "/get_blocks.bin" | "/getblocks.bin" => { - BinRequest::GetBlocks(from_bytes(&mut request).map_err(error)?) - } - "/get_blocks_by_height.bin" | "/getblocks_by_height.bin" => { - BinRequest::GetBlocksByHeight(from_bytes(&mut request).map_err(error)?) - } - "/get_hashes.bin" | "/gethashes.bin" => { - BinRequest::GetHashes(from_bytes(&mut request).map_err(error)?) - } - "/get_o_indexes.bin" => { - BinRequest::GetOutputIndexes(from_bytes(&mut request).map_err(error)?) - } - "/get_outs.bin" => BinRequest::GetOuts(from_bytes(&mut request).map_err(error)?), - "/get_transaction_pool_hashes.bin" => BinRequest::GetTransactionPoolHashes(()), - "/get_output_distribution.bin" => { - BinRequest::GetOutputDistribution(from_bytes(&mut request).map_err(error)?) - } - - // INVARIANT: - // The `create_router` function only passes the above endpoints. - _ => unreachable!(), - }; - - // TODO - if handler.state().restricted() && request.is_restricted() { - return Err(StatusCode::NOT_FOUND); - } - - // TODO: call handler - let Response::Binary(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(response) -} - -/// TODO -pub(crate) async fn other( - State(handler): State, - Json(request): Json, -) -> Result, StatusCode> { - if handler.state().restricted() && request.is_restricted() { - todo!(); - } - - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler returned incorrect response"); - }; - - Ok(Json(response)) -} - -/// TODO -pub(crate) async fn unknown() -> StatusCode { - StatusCode::NOT_FOUND +generate_endpoints! { + get_height => GetHeight, + get_transactions => GetTransactions, + get_alt_blocks_hashes => GetAltBlocksHashes, + is_key_image_spent => IsKeyImageSpent, + send_raw_transaction => SendRawTransaction, + start_mining => StartMining, + stop_mining => StopMining, + mining_status => MiningStatus, + save_bc => SaveBc, + get_peer_list => GetPeerList, + set_log_hash_rate => SetLogHashRate, + set_log_level => SetLogLevel, + set_log_categories => SetLogCategories, + set_bootstrap_daemon => SetBootstrapDaemon, + get_transaction_pool => GetTransactionPool, + get_transaction_pool_stats => GetTransactionPoolStats, + stop_daemon => StopDaemon, + get_limit => GetLimit, + set_limit => SetLimit, + out_peers => OutPeers, + in_peers => InPeers, + get_net_stats => GetNetStats, + get_outs => GetOuts, + update => Update, + pop_blocks => PopBlocks, + get_transaction_ids_loose => GetTxIdsLoose, + get_transaction_pool_hashes => GetTransactionPoolHashes, + get_public_nodes => GetPublicNodes } //---------------------------------------------------------------------------------------------------- Tests diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index a50ef21e4..8f6b9fe54 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -30,6 +30,7 @@ pub trait RpcHandler: Clone + Send + Sync + 'static { fn handler(&self) -> Self::Handler; } +//---------------------------------------------------------------------------------------------------- TODO /// TODO #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ConcreteRpcHandler { diff --git a/rpc/interface/src/rpc_state.rs b/rpc/interface/src/rpc_state.rs index 86ab73e80..a4aca5b2f 100644 --- a/rpc/interface/src/rpc_state.rs +++ b/rpc/interface/src/rpc_state.rs @@ -17,6 +17,7 @@ where fn restricted(&self) -> bool; } +//---------------------------------------------------------------------------------------------------- TODO /// TODO #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ConcreteRpcState { From 50af75f964c5281db042f3b43a8b506146e790c1 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 16:55:57 -0400 Subject: [PATCH 65/93] types: fix `is_restricted()` --- rpc/types/src/rpc_request.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rpc/types/src/rpc_request.rs b/rpc/types/src/rpc_request.rs index 6ec596a22..d060c524c 100644 --- a/rpc/types/src/rpc_request.rs +++ b/rpc/types/src/rpc_request.rs @@ -13,14 +13,17 @@ pub trait RpcRequest { /// RPC servers. /// /// ```rust - /// use cuprate_rpc_types::JsonRpcRequest; + /// use cuprate_rpc_types::{ + /// RpcRequest, + /// json::{GetBlockCountRequest, GetConnectionsRequest}, + /// }; /// /// // Allowed method, even on restricted RPC servers (18089). - /// assert_eq!(JsonRpcRequest::GetBlockCount(()).is_restricted(), false); + /// assert_eq!(GetBlockCountRequest::default().is_restricted(), false); /// /// // Restricted methods, only allowed /// // for unrestricted RPC servers (18081). - /// assert_eq!(JsonRpcRequest::GetConnections(()).is_restricted(), true); + /// assert_eq!(GetConnectionsRequest::default().is_restricted(), true); /// ``` fn is_restricted(&self) -> bool; } From 1e46c98c0a1ff086aa2bd044ce1c5b8aee3c3106 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 19:48:58 -0400 Subject: [PATCH 66/93] interface: reorder short-circuit bool --- rpc/interface/src/route/json.rs | 2 +- rpc/interface/src/route/other.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index ed885fbe3..ae40b3937 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -35,7 +35,7 @@ pub(crate) async fn json_rpc( ) -> Result>, StatusCode> { // Return early if this RPC server is restricted and // the requested method is only for non-restricted RPC. - if handler.state().restricted() && request.body.is_restricted() { + if request.body.is_restricted() && handler.state().restricted() { let error_object = ErrorObject { code: ErrorCode::ServerError(-1 /* TODO */), message: Cow::Borrowed("Restricted. TODO"), diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 5ad4ec3c0..cf2655a85 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -50,7 +50,7 @@ macro_rules! generate_endpoints { State(handler): State, Json(request): Json<[<$variant Request>]>, ) -> Result]>, StatusCode> { - if handler.state().restricted() && request.is_restricted() { + if request.is_restricted() && handler.state().restricted() { todo!(); } From 2d95a28c6e6d4d72566ee68be1091d2dbafb0c2f Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 20:20:29 -0400 Subject: [PATCH 67/93] clean up traits/bounds --- Cargo.lock | 1 + rpc/interface/Cargo.toml | 7 ++-- rpc/interface/src/free.rs | 3 -- rpc/interface/src/lib.rs | 4 +-- rpc/interface/src/route/bin.rs | 5 +-- rpc/interface/src/route/json.rs | 7 ++-- rpc/interface/src/route/other.rs | 7 ++-- rpc/interface/src/route/unknown.rs | 5 +-- rpc/interface/src/rpc_handler.rs | 58 +++++++++++++----------------- rpc/interface/src/rpc_service.rs | 42 ++++++++++++++++++++++ rpc/interface/src/rpc_state.rs | 31 ---------------- 11 files changed, 79 insertions(+), 91 deletions(-) create mode 100644 rpc/interface/src/rpc_service.rs delete mode 100644 rpc/interface/src/rpc_state.rs diff --git a/Cargo.lock b/Cargo.lock index d9bc94fb9..95f74f550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -809,6 +809,7 @@ version = "0.0.0" dependencies = [ "axum", "cuprate-epee-encoding", + "cuprate-helper", "cuprate-json-rpc", "cuprate-rpc-types", "paste", diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 1fac2001d..001366f5c 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -12,9 +12,10 @@ keywords = ["cuprate", "rpc", "interface"] default = [] [dependencies] -cuprate-epee-encoding = { path = "../../net/epee-encoding" } -cuprate-json-rpc = { path = "../json-rpc" } -cuprate-rpc-types = { path = "../types", features = ["serde", "epee", "axum"] } +cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false } +cuprate-json-rpc = { path = "../json-rpc", default-features = false } +cuprate-rpc-types = { path = "../types", features = ["serde", "epee", "axum"], default-features = false } +cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } axum = { workspace = true, features = ["json"] } serde = { workspace = true } diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 47988a701..196529660 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -12,7 +12,6 @@ use crate::{ response::Response, route::{bin, json, other, unknown}, rpc_handler::RpcHandler, - RpcState, }; //---------------------------------------------------------------------------------------------------- Router @@ -23,8 +22,6 @@ pub fn create_router() -> Router { // List of `monerod` routes: // - // let mut router = Router::new(); - Router::new() // JSON-RPC route. .route("/json_rpc", get(json::json_rpc::)) diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index d962ae4d0..5c4c08df3 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -113,11 +113,11 @@ mod request; mod response; mod route; mod rpc_handler; -mod rpc_state; +mod rpc_service; pub use error::Error; pub use free::create_router; pub use request::Request; pub use response::Response; pub use rpc_handler::{ConcreteRpcHandler, RpcHandler}; -pub use rpc_state::{ConcreteRpcState, RpcState}; +pub use rpc_service::RpcService; diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index c294420fe..721576c60 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -27,10 +27,7 @@ use cuprate_rpc_types::{ RpcRequest, }; -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; +use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index ae40b3937..d2649708c 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -22,10 +22,7 @@ use cuprate_rpc_types::{ RpcRequest, }; -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; +use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -35,7 +32,7 @@ pub(crate) async fn json_rpc( ) -> Result>, StatusCode> { // Return early if this RPC server is restricted and // the requested method is only for non-restricted RPC. - if request.body.is_restricted() && handler.state().restricted() { + if request.body.is_restricted() && handler.restricted() { let error_object = ErrorObject { code: ErrorCode::ServerError(-1 /* TODO */), message: Cow::Borrowed("Restricted. TODO"), diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index cf2655a85..3c4fd7369 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -32,10 +32,7 @@ use cuprate_rpc_types::{ RpcRequest, }; -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; +use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -50,7 +47,7 @@ macro_rules! generate_endpoints { State(handler): State, Json(request): Json<[<$variant Request>]>, ) -> Result]>, StatusCode> { - if request.is_restricted() && handler.state().restricted() { + if request.is_restricted() && handler.restricted() { todo!(); } diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/unknown.rs index 84c68e5fa..59566c8b4 100644 --- a/rpc/interface/src/route/unknown.rs +++ b/rpc/interface/src/route/unknown.rs @@ -22,10 +22,7 @@ use cuprate_rpc_types::{ RpcRequest, }; -use crate::{ - error::Error, request::Request, response::Response, rpc_handler::RpcHandler, - rpc_state::RpcState, -}; +use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 8f6b9fe54..ff787064b 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -6,53 +6,43 @@ use std::{future::Future, marker::PhantomData, sync::Arc}; use axum::extract::State; use tower::Service; -use crate::{ - error::Error, request::Request, response::Response, rpc_state::ConcreteRpcState, RpcState, -}; +use cuprate_helper::asynch::InfallibleOneshotReceiver; + +use crate::{error::Error, request::Request, response::Response, RpcService}; //---------------------------------------------------------------------------------------------------- TODO /// TODO -pub trait RpcHandler: Clone + Send + Sync + 'static { - /// TODO - type State: RpcState; - - /// TODO - type Handler: Send + Sync + 'static + Service; - // where - // >::Response: Into, - // >::Error: Into, - // >::Future: Future>; - - /// TODO - fn state(&self) -> &Self::State; - +pub trait RpcHandler: RpcService { /// TODO - fn handler(&self) -> Self::Handler; + fn restricted(&self) -> bool; } //---------------------------------------------------------------------------------------------------- TODO /// TODO #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ConcreteRpcHandler { - state: ConcreteRpcState, - _handler: PhantomData, +pub struct ConcreteRpcHandler { + restricted: bool, +} + +impl RpcHandler for ConcreteRpcHandler { + fn restricted(&self) -> bool { + self.restricted + } } -impl RpcHandler for ConcreteRpcHandler -where - H: Clone + Send + Sync + 'static + Service, - >::Response: Into, - >::Error: Into, - >::Future: Future>, -{ - type State = ConcreteRpcState; - type Handler = H; - - fn state(&self) -> &Self::State { - &self.state +impl Service for ConcreteRpcHandler { + type Response = Response; + type Error = Error; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() } - fn handler(&self) -> Self::Handler { + fn call(&mut self, req: Request) -> Self::Future { todo!() } } diff --git a/rpc/interface/src/rpc_service.rs b/rpc/interface/src/rpc_service.rs new file mode 100644 index 000000000..2f9fe0b60 --- /dev/null +++ b/rpc/interface/src/rpc_service.rs @@ -0,0 +1,42 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +use std::{future::Future, marker::PhantomData, sync::Arc}; + +use axum::extract::State; +use tower::Service; + +use cuprate_helper::asynch::InfallibleOneshotReceiver; + +use crate::{error::Error, request::Request, response::Response}; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +pub trait RpcService: + Clone + + Send + + Sync + + 'static + + Service< + Request, + Response = Response, + Error = Error, + Future = InfallibleOneshotReceiver>, + > +{ +} + +/// TODO +impl RpcService for S where + Self: Clone + + Send + + Sync + + 'static + + Service< + Request, + Response = Response, + Error = Error, + Future = InfallibleOneshotReceiver>, + > +{ +} diff --git a/rpc/interface/src/rpc_state.rs b/rpc/interface/src/rpc_state.rs deleted file mode 100644 index a4aca5b2f..000000000 --- a/rpc/interface/src/rpc_state.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Use -use std::{future::Future, marker::PhantomData, sync::Arc}; - -use tower::Service; - -use crate::{error::Error, request::Request, response::Response}; - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -pub trait RpcState -where - Self: Clone + Send + Sync + 'static, -{ - /// TODO - fn restricted(&self) -> bool; -} - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ConcreteRpcState { - restricted: bool, -} - -impl RpcState for ConcreteRpcState { - fn restricted(&self) -> bool { - self.restricted - } -} From 476f6ee1ba7688b3ffd1ebdf3e9b1930890d4caa Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 20:28:57 -0400 Subject: [PATCH 68/93] types: remove `axum` feature --- Cargo.lock | 1 - rpc/interface/Cargo.toml | 2 +- rpc/types/Cargo.toml | 3 +-- rpc/types/src/bin.rs | 35 ----------------------------------- 4 files changed, 2 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95f74f550..97b658b9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -821,7 +821,6 @@ dependencies = [ name = "cuprate-rpc-types" version = "0.0.0" dependencies = [ - "axum", "cuprate-epee-encoding", "cuprate-fixed-bytes", "monero-serai", diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 001366f5c..1c53f712b 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -14,7 +14,7 @@ default = [] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false } cuprate-json-rpc = { path = "../json-rpc", default-features = false } -cuprate-rpc-types = { path = "../types", features = ["serde", "epee", "axum"], default-features = false } +cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } axum = { workspace = true, features = ["json"] } diff --git a/rpc/types/Cargo.toml b/rpc/types/Cargo.toml index 22df6590d..1176526ad 100644 --- a/rpc/types/Cargo.toml +++ b/rpc/types/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/types" keywords = ["cuprate", "rpc", "types", "monero"] [features] -default = ["serde", "epee", "axum"] +default = ["serde", "epee"] serde = ["dep:serde", "cuprate-fixed-bytes/serde"] epee = ["dep:cuprate-epee-encoding"] @@ -17,7 +17,6 @@ epee = ["dep:cuprate-epee-encoding"] cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true } cuprate-fixed-bytes = { path = "../../net/fixed-bytes" } -axum = { workspace = true, optional = true } monero-serai = { workspace = true } paste = { workspace = true } serde = { workspace = true, optional = true } diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 7c2242710..f11896aa7 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -5,15 +5,6 @@ //---------------------------------------------------------------------------------------------------- Import use cuprate_fixed_bytes::ByteArrayVec; -#[cfg(feature = "axum")] -use axum::{ - async_trait, - body::{Body, Bytes}, - extract::{FromRequest, Request}, - http::StatusCode, - response::{IntoResponse, Response}, -}; - #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -193,32 +184,6 @@ pub enum BinResponse { GetOutputDistribution(crate::json::GetOutputDistributionResponse), } -#[cfg(feature = "axum")] -#[cfg(feature = "epee")] -impl axum::response::IntoResponse for BinResponse { - fn into_response(self) -> axum::response::Response { - use cuprate_epee_encoding::to_bytes; - - let mut bytes = axum::body::Bytes::new(); - let writer = &mut bytes; - - let result = match self { - Self::GetBlocks(s) => to_bytes(s), - Self::GetBlocksByHeight(s) => to_bytes(s), - Self::GetHashes(s) => to_bytes(s), - Self::GetOutputIndexes(s) => to_bytes(s), - Self::GetOuts(s) => to_bytes(s), - Self::GetTransactionPoolHashes(s) => to_bytes(s), - Self::GetOutputDistribution(s) => to_bytes(s), - }; - - match result { - Ok(bytes) => bytes.into_response(), - Err(e) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), - } - } -} - //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { From 35d1cebd7c53d9b653d4f8a2cf9b40847e9b28b5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 20:29:03 -0400 Subject: [PATCH 69/93] interface: cleanup unused imports --- rpc/interface/src/free.rs | 8 +------- rpc/interface/src/route/bin.rs | 26 +++-------------------- rpc/interface/src/route/json.rs | 13 +++--------- rpc/interface/src/route/other.rs | 33 +++++++++++++----------------- rpc/interface/src/route/unknown.rs | 22 +------------------- rpc/interface/src/rpc_handler.rs | 3 --- rpc/interface/src/rpc_service.rs | 3 --- 7 files changed, 22 insertions(+), 86 deletions(-) diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 196529660..dc69deece 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -1,15 +1,9 @@ //! Free functions. //---------------------------------------------------------------------------------------------------- Use -use std::{future::Future, marker::PhantomData}; - -use axum::{extract::State, routing::method_routing::get, Router}; -use tower::Service; +use axum::{routing::method_routing::get, Router}; use crate::{ - error::Error, - request::Request, - response::Response, route::{bin, json, other, unknown}, rpc_handler::RpcHandler, }; diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 721576c60..6dce251c7 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -2,32 +2,12 @@ #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, future::Future, sync::Arc}; - -use axum::{body::Bytes, extract::State, http::StatusCode, response::IntoResponse, Json}; -use cuprate_json_rpc::{ - error::{ErrorCode, ErrorObject}, - Id, -}; -use tower::{Service, ServiceExt}; +use axum::{body::Bytes, extract::State, http::StatusCode}; use cuprate_epee_encoding::from_bytes; -use cuprate_rpc_types::{ - bin::{ - BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksByHeightResponse, - GetBlocksRequest, GetBlocksResponse, GetHashesRequest, GetHashesResponse, - GetOutputIndexesRequest, GetOutputIndexesResponse, GetOutsRequest, GetOutsResponse, - GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse, - }, - json::{ - GetOutputDistributionRequest, GetOutputDistributionResponse, JsonRpcRequest, - JsonRpcResponse, - }, - other::{OtherRequest, OtherResponse}, - RpcRequest, -}; +use cuprate_rpc_types::bin::{BinRequest, BinResponse}; -use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index d2649708c..bcf0a479c 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -2,27 +2,20 @@ #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, future::Future, sync::Arc}; +use std::borrow::Cow; -use axum::{body::Bytes, extract::State, http::StatusCode, Json}; +use axum::{extract::State, http::StatusCode, Json}; use cuprate_json_rpc::{ error::{ErrorCode, ErrorObject}, Id, }; -use tower::{Service, ServiceExt}; -use cuprate_epee_encoding::from_bytes; use cuprate_rpc_types::{ - bin::{ - BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, - GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, - }, json::{JsonRpcRequest, JsonRpcResponse}, - other::{OtherRequest, OtherResponse}, RpcRequest, }; -use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 3c4fd7369..e7fcbf886 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -2,14 +2,8 @@ #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, future::Future, sync::Arc}; -use axum::{body::Bytes, extract::State, http::StatusCode, Json}; -use cuprate_json_rpc::{ - error::{ErrorCode, ErrorObject}, - Id, -}; -use tower::{Service, ServiceExt}; +use axum::{extract::State, http::StatusCode, Json}; use cuprate_rpc_types::{ other::{ @@ -21,18 +15,18 @@ use cuprate_rpc_types::{ GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse, GetTxIdsLooseRequest, GetTxIdsLooseResponse, InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse, MiningStatusRequest, MiningStatusResponse, - OtherRequest, OtherResponse, OutPeersRequest, OutPeersResponse, PopBlocksRequest, - PopBlocksResponse, SaveBcRequest, SaveBcResponse, SendRawTransactionRequest, - SendRawTransactionResponse, SetBootstrapDaemonRequest, SetBootstrapDaemonResponse, - SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest, SetLogCategoriesResponse, - SetLogHashRateRequest, SetLogHashRateResponse, SetLogLevelRequest, SetLogLevelResponse, - StartMiningRequest, StartMiningResponse, StopDaemonRequest, StopDaemonResponse, - StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, + OtherResponse, OutPeersRequest, OutPeersResponse, PopBlocksRequest, PopBlocksResponse, + SaveBcRequest, SaveBcResponse, SendRawTransactionRequest, SendRawTransactionResponse, + SetBootstrapDaemonRequest, SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, + SetLogCategoriesRequest, SetLogCategoriesResponse, SetLogHashRateRequest, + SetLogHashRateResponse, SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, + StartMiningResponse, StopDaemonRequest, StopDaemonResponse, StopMiningRequest, + StopMiningResponse, UpdateRequest, UpdateResponse, }, RpcRequest, }; -use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -57,10 +51,11 @@ macro_rules! generate_endpoints { }; // Assert the response from the inner handler is correct. - match response { - OtherResponse::$variant(response) => Ok(Json(response)), - _ => panic!("RPC handler returned incorrect response"), - } + let OtherResponse::$variant(response) = response else { + panic!("RPC handler returned incorrect response") + }; + + Ok(Json(response)) } )* }}; diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/unknown.rs index 59566c8b4..b980e0dca 100644 --- a/rpc/interface/src/route/unknown.rs +++ b/rpc/interface/src/route/unknown.rs @@ -2,27 +2,7 @@ #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import -use std::{borrow::Cow, future::Future, sync::Arc}; - -use axum::{body::Bytes, extract::State, http::StatusCode, Json}; -use cuprate_json_rpc::{ - error::{ErrorCode, ErrorObject}, - Id, -}; -use tower::{Service, ServiceExt}; - -use cuprate_epee_encoding::from_bytes; -use cuprate_rpc_types::{ - bin::{ - BinRequest, BinResponse, GetBlocksByHeightRequest, GetBlocksRequest, GetHashesRequest, - GetOutputIndexesRequest, GetOutsRequest, GetTransactionPoolHashesRequest, - }, - json::{JsonRpcRequest, JsonRpcResponse}, - other::{OtherRequest, OtherResponse}, - RpcRequest, -}; - -use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; +use axum::http::StatusCode; //---------------------------------------------------------------------------------------------------- Routes /// TODO diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index ff787064b..3fc1d0e44 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -1,9 +1,6 @@ //! TODO //---------------------------------------------------------------------------------------------------- Use -use std::{future::Future, marker::PhantomData, sync::Arc}; - -use axum::extract::State; use tower::Service; use cuprate_helper::asynch::InfallibleOneshotReceiver; diff --git a/rpc/interface/src/rpc_service.rs b/rpc/interface/src/rpc_service.rs index 2f9fe0b60..191ab50c9 100644 --- a/rpc/interface/src/rpc_service.rs +++ b/rpc/interface/src/rpc_service.rs @@ -1,9 +1,6 @@ //! TODO //---------------------------------------------------------------------------------------------------- Use -use std::{future::Future, marker::PhantomData, sync::Arc}; - -use axum::extract::State; use tower::Service; use cuprate_helper::asynch::InfallibleOneshotReceiver; From d57a5c9a3fa905e5863949f818daa339d68e4420 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 24 Jul 2024 20:42:16 -0400 Subject: [PATCH 70/93] interface: call handler in routes --- rpc/interface/src/error.rs | 10 ++++++++++ rpc/interface/src/route/bin.rs | 11 +++++++---- rpc/interface/src/route/json.rs | 16 +++++++++------ rpc/interface/src/route/other.rs | 34 ++++++++++++++++++-------------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/rpc/interface/src/error.rs b/rpc/interface/src/error.rs index fc816590e..c613a4f24 100644 --- a/rpc/interface/src/error.rs +++ b/rpc/interface/src/error.rs @@ -2,10 +2,20 @@ //---------------------------------------------------------------------------------------------------- Import +use axum::http::StatusCode; + //---------------------------------------------------------------------------------------------------- TODO /// TODO +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Error {} +impl From for StatusCode { + fn from(value: Error) -> Self { + // TODO + Self::INTERNAL_SERVER_ERROR + } +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 6dce251c7..ec922f4f6 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -3,11 +3,12 @@ //---------------------------------------------------------------------------------------------------- Import use axum::{body::Bytes, extract::State, http::StatusCode}; +use tower::ServiceExt; use cuprate_epee_encoding::from_bytes; use cuprate_rpc_types::bin::{BinRequest, BinResponse}; -use crate::{response::Response, rpc_handler::RpcHandler}; +use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -27,12 +28,14 @@ macro_rules! generate_endpoints { from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? ); - // TODO: call handler + // Send request. + let request = Request::Binary(request); + let channel = handler.oneshot(request).await?; + + // Assert the response from the inner handler is correct. let Response::Binary(response) = todo!() else { panic!("RPC handler did not return a binary response"); }; - - // Assert the response from the inner handler is correct. let BinResponse::$variant(response) = response else { panic!("RPC handler returned incorrect response"); }; diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index bcf0a479c..1cc497ffa 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -5,17 +5,18 @@ use std::borrow::Cow; use axum::{extract::State, http::StatusCode, Json}; +use tower::ServiceExt; + use cuprate_json_rpc::{ error::{ErrorCode, ErrorObject}, Id, }; - use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse}, RpcRequest, }; -use crate::{response::Response, rpc_handler::RpcHandler}; +use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -28,7 +29,7 @@ pub(crate) async fn json_rpc( if request.body.is_restricted() && handler.restricted() { let error_object = ErrorObject { code: ErrorCode::ServerError(-1 /* TODO */), - message: Cow::Borrowed("Restricted. TODO"), + message: Cow::Borrowed("Restricted. TODO: mimic monerod message"), data: None, }; @@ -39,12 +40,15 @@ pub(crate) async fn json_rpc( let response = cuprate_json_rpc::Response::err(id, error_object); - // TODO return Ok(Json(response)); } - // TODO: call handler - let Response::JsonRpc(response) = todo!() else { + // Send request. + let request = Request::JsonRpc(request); + let channel = handler.oneshot(request).await?; + + // Assert the response from the inner handler is correct. + let Response::JsonRpc(response) = channel else { panic!("RPC handler returned incorrect response"); }; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index e7fcbf886..0ef0f0762 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -2,8 +2,8 @@ #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import - use axum::{extract::State, http::StatusCode, Json}; +use tower::ServiceExt; use cuprate_rpc_types::{ other::{ @@ -15,18 +15,18 @@ use cuprate_rpc_types::{ GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse, GetTxIdsLooseRequest, GetTxIdsLooseResponse, InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse, MiningStatusRequest, MiningStatusResponse, - OtherResponse, OutPeersRequest, OutPeersResponse, PopBlocksRequest, PopBlocksResponse, - SaveBcRequest, SaveBcResponse, SendRawTransactionRequest, SendRawTransactionResponse, - SetBootstrapDaemonRequest, SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, - SetLogCategoriesRequest, SetLogCategoriesResponse, SetLogHashRateRequest, - SetLogHashRateResponse, SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, - StartMiningResponse, StopDaemonRequest, StopDaemonResponse, StopMiningRequest, - StopMiningResponse, UpdateRequest, UpdateResponse, + OtherRequest, OtherResponse, OutPeersRequest, OutPeersResponse, PopBlocksRequest, + PopBlocksResponse, SaveBcRequest, SaveBcResponse, SendRawTransactionRequest, + SendRawTransactionResponse, SetBootstrapDaemonRequest, SetBootstrapDaemonResponse, + SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest, SetLogCategoriesResponse, + SetLogHashRateRequest, SetLogHashRateResponse, SetLogLevelRequest, SetLogLevelResponse, + StartMiningRequest, StartMiningResponse, StopDaemonRequest, StopDaemonResponse, + StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, }, RpcRequest, }; -use crate::{response::Response, rpc_handler::RpcHandler}; +use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -38,19 +38,23 @@ macro_rules! generate_endpoints { /// TODO #[allow(unused_mut)] pub(crate) async fn $endpoint( - State(handler): State, + State(mut handler): State, Json(request): Json<[<$variant Request>]>, ) -> Result]>, StatusCode> { + // Check if restricted. if request.is_restricted() && handler.restricted() { - todo!(); + // TODO: mimic `monerod` behavior. + return Err(StatusCode::FORBIDDEN); } - // TODO: call handler - let Response::Other(response) = todo!() else { - panic!("RPC handler did not return a binary response"); - }; + // Send request. + let request = Request::Other(OtherRequest::$variant(request)); + let channel = handler.oneshot(request).await?; // Assert the response from the inner handler is correct. + let Response::Other(response) = channel else { + panic!("RPC handler did not return a binary response"); + }; let OtherResponse::$variant(response) = response else { panic!("RPC handler returned incorrect response") }; From 0438c4866111253fb3f64267d9f3b5bd80b2c783 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 25 Jul 2024 18:14:22 -0400 Subject: [PATCH 71/93] json: TODO distribution test --- rpc/types/src/json.rs | 25 +++++++++++++------------ test-utils/src/rpc/data/json.rs | 3 ++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 74ccb9a0e..dd2e6483e 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -1339,18 +1339,19 @@ define_request_and_response! { to_height: u64 = default_zero::(), "default_zero", }, - #[doc = serde_doc_test!( - GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { - base: AccessResponseBase::ok(), - distributions: vec![Distribution::Uncompressed { - start_height: 1462078, - base: 0, - distribution: "".into(), - amount: 2628780000, - binary: true, - }], - } - )] + // TODO: enable test after binary string impl. + // #[doc = serde_doc_test!( + // GET_OUTPUT_DISTRIBUTION_RESPONSE => GetOutputDistributionResponse { + // base: AccessResponseBase::ok(), + // distributions: vec![Distribution::Uncompressed(DistributionUncompressed { + // start_height: 1462078, + // base: 0, + // distribution: vec![], + // amount: 2628780000, + // binary: true, + // })], + // } + // )] AccessResponseBase { distributions: Vec, } diff --git a/test-utils/src/rpc/data/json.rs b/test-utils/src/rpc/data/json.rs index 05df19541..a05af6700 100644 --- a/test-utils/src/rpc/data/json.rs +++ b/test-utils/src/rpc/data/json.rs @@ -1111,7 +1111,8 @@ r#"{ "amount": 2628780000, "base": 0, "distribution": "", - "start_height": 1462078 + "start_height": 1462078, + "binary": false }], "status": "OK", "top_hash": "", From d7379eadf6443a57a4433cc4e824481476a5e31d Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 25 Jul 2024 20:45:22 -0400 Subject: [PATCH 72/93] interface: readme intro --- Cargo.toml | 1 - rpc/interface/Cargo.toml | 2 +- rpc/interface/README.md | 38 ++++++++++++++++++++++++++++++++++++-- rpc/interface/src/free.rs | 9 +++++---- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 832968109..22a158551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,6 @@ opt-level = 1 opt-level = 3 [workspace.dependencies] -axum = { version = "0.7.5", default-features = false } async-trait = { version = "0.1.74", default-features = false } bitflags = { version = "2.4.2", default-features = false } borsh = { version = "1.2.1", default-features = false } diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 1c53f712b..1888b7e8a 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -17,7 +17,7 @@ cuprate-json-rpc = { path = "../json-rpc", default-features = false } cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } -axum = { workspace = true, features = ["json"] } +axum = { version = "0.7.5", features = ["json"], default-features = false } serde = { workspace = true } tower = { workspace = true } paste = { workspace = true } diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 7517c6d5a..96eadbee3 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -1,6 +1,40 @@ -TODO +# `cuprate-rpc-interface` +This crate provides Cuprate's RPC _interface_. -# What +```text + cuprate-rpc-interface provides these parts + │ + ┌────┴────┐ +┌───────────────────────────┤ ├───────────────────┐ +▼ ▼ ▼ ▼ +CLIENT -> ROUTE -> REQUEST -> HANDLER -> RESPONSE -> CLIENT + ▲ ▲ + └───┬───┘ + │ + You provide this part +``` + +Everything coming _in_ from a client including: +- Any lower-level HTTP stuff +- Endpoint routing +- Request (de)serialization + +is handled by this crate. + +This is where your [`RpcHandler`] turns this [`Request`] into a [`Response`]. + +You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client. + +The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate; +it implements the regular RPC server modeled after `monerod`. + +# Router + +# Requests, responses, and errors + +# The RPC handler [`Service`](tower::Service) + +# Routes # Feature flags List of feature flags for `cuprate-rpc-interface`. diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index dc69deece..70e6a42ba 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -9,13 +9,14 @@ use crate::{ }; //---------------------------------------------------------------------------------------------------- Router +/// Create the RPC router. +/// /// TODO -#[allow(clippy::needless_pass_by_value)] +/// +/// # Routes +/// List of `monerod` routes, [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189). #[rustfmt::skip] pub fn create_router() -> Router { - // List of `monerod` routes: - // - Router::new() // JSON-RPC route. .route("/json_rpc", get(json::json_rpc::)) From 1ccabec6200c83a5995b0ee5117b73cdcd42ee4f Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 26 Jul 2024 19:48:53 -0400 Subject: [PATCH 73/93] combine `RpcHandler` + `RpcService`, add `RpcDummyHandler` --- Cargo.lock | 1 + rpc/interface/Cargo.toml | 12 ++- rpc/interface/src/lib.rs | 8 +- rpc/interface/src/rpc_handler.rs | 50 ++++------ rpc/interface/src/rpc_handler_dummy.rs | 125 +++++++++++++++++++++++++ rpc/interface/src/rpc_service.rs | 39 -------- 6 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 rpc/interface/src/rpc_handler_dummy.rs delete mode 100644 rpc/interface/src/rpc_service.rs diff --git a/Cargo.lock b/Cargo.lock index f057ef2af..8cb21d402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ dependencies = [ "cuprate-helper", "cuprate-json-rpc", "cuprate-rpc-types", + "futures", "paste", "serde", "tower", diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 1888b7e8a..b39bcdc51 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -9,7 +9,8 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte keywords = ["cuprate", "rpc", "interface"] [features] -default = [] +default = ["dummy"] +dummy = [] [dependencies] cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false } @@ -17,9 +18,10 @@ cuprate-json-rpc = { path = "../json-rpc", default-features = false } cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } -axum = { version = "0.7.5", features = ["json"], default-features = false } -serde = { workspace = true } -tower = { workspace = true } -paste = { workspace = true } +axum = { version = "0.7.5", features = ["json"], default-features = false } +serde = { workspace = true } +tower = { workspace = true } +paste = { workspace = true } +futures = { workspace = true } [dev-dependencies] diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 5c4c08df3..894829c5f 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -113,11 +113,13 @@ mod request; mod response; mod route; mod rpc_handler; -mod rpc_service; +#[cfg(feature = "dummy")] +mod rpc_handler_dummy; pub use error::Error; pub use free::create_router; pub use request::Request; pub use response::Response; -pub use rpc_handler::{ConcreteRpcHandler, RpcHandler}; -pub use rpc_service::RpcService; +pub use rpc_handler::RpcHandler; +#[cfg(feature = "dummy")] +pub use rpc_handler_dummy::RpcHandlerDummy; diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 3fc1d0e44..4b5e7579b 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -1,45 +1,31 @@ //! TODO //---------------------------------------------------------------------------------------------------- Use +use std::task::Poll; + +use futures::{channel::oneshot::channel, FutureExt}; use tower::Service; use cuprate_helper::asynch::InfallibleOneshotReceiver; +use cuprate_json_rpc::Id; +use cuprate_rpc_types::json::JsonRpcRequest; -use crate::{error::Error, request::Request, response::Response, RpcService}; +use crate::{error::Error, request::Request, response::Response}; //---------------------------------------------------------------------------------------------------- TODO /// TODO -pub trait RpcHandler: RpcService { +pub trait RpcHandler: + Clone + + Send + + Sync + + 'static + + Service< + Request, + Response = Response, + Error = Error, + Future = InfallibleOneshotReceiver>, + > +{ /// TODO fn restricted(&self) -> bool; } - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ConcreteRpcHandler { - restricted: bool, -} - -impl RpcHandler for ConcreteRpcHandler { - fn restricted(&self) -> bool { - self.restricted - } -} - -impl Service for ConcreteRpcHandler { - type Response = Response; - type Error = Error; - type Future = InfallibleOneshotReceiver>; - - fn poll_ready( - &mut self, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - todo!() - } - - fn call(&mut self, req: Request) -> Self::Future { - todo!() - } -} diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs new file mode 100644 index 000000000..5a1964c50 --- /dev/null +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -0,0 +1,125 @@ +//! TODO + +//---------------------------------------------------------------------------------------------------- Use +use std::task::Poll; + +use futures::{channel::oneshot::channel, FutureExt}; +use tower::Service; + +use cuprate_helper::asynch::InfallibleOneshotReceiver; +use cuprate_json_rpc::Id; +use cuprate_rpc_types::json::JsonRpcRequest; + +use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; + +//---------------------------------------------------------------------------------------------------- TODO +/// TODO +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RpcHandlerDummy { + /// TODO + pub restricted: bool, +} + +impl RpcHandler for RpcHandlerDummy { + fn restricted(&self) -> bool { + self.restricted + } +} + +impl Service for RpcHandlerDummy { + type Response = Response; + type Error = Error; + type Future = InfallibleOneshotReceiver>; + + fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> Self::Future { + use cuprate_rpc_types::bin::BinRequest as BReq; + use cuprate_rpc_types::bin::BinResponse as BResp; + use cuprate_rpc_types::json::JsonRpcRequest as JReq; + use cuprate_rpc_types::json::JsonRpcResponse as JResp; + use cuprate_rpc_types::other::OtherRequest as OReq; + use cuprate_rpc_types::other::OtherResponse as OResp; + + #[rustfmt::skip] + #[allow(clippy::default_trait_access)] + let resp = match req { + Request::JsonRpc(j) => Response::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body { + JReq::GetBlockCount(_) => JResp::GetBlockCount(Default::default()), + JReq::OnGetBlockHash(_) => JResp::OnGetBlockHash(Default::default()), + JReq::SubmitBlock(_) => JResp::SubmitBlock(Default::default()), + JReq::GenerateBlocks(_) => JResp::GenerateBlocks(Default::default()), + JReq::GetLastBlockHeader(_) => JResp::GetLastBlockHeader(Default::default()), + JReq::GetBlockHeaderByHash(_) => JResp::GetBlockHeaderByHash(Default::default()), + JReq::GetBlockHeaderByHeight(_) => JResp::GetBlockHeaderByHeight(Default::default()), + JReq::GetBlockHeadersRange(_) => JResp::GetBlockHeadersRange(Default::default()), + JReq::GetBlock(_) => JResp::GetBlock(Default::default()), + JReq::GetConnections(_) => JResp::GetConnections(Default::default()), + JReq::GetInfo(_) => JResp::GetInfo(Default::default()), + JReq::HardForkInfo(_) => JResp::HardForkInfo(Default::default()), + JReq::SetBans(_) => JResp::SetBans(Default::default()), + JReq::GetBans(_) => JResp::GetBans(Default::default()), + JReq::Banned(_) => JResp::Banned(Default::default()), + JReq::FlushTransactionPool(_) => JResp::FlushTransactionPool(Default::default()), + JReq::GetOutputHistogram(_) => JResp::GetOutputHistogram(Default::default()), + JReq::GetCoinbaseTxSum(_) => JResp::GetCoinbaseTxSum(Default::default()), + JReq::GetVersion(_) => JResp::GetVersion(Default::default()), + JReq::GetFeeEstimate(_) => JResp::GetFeeEstimate(Default::default()), + JReq::GetAlternateChains(_) => JResp::GetAlternateChains(Default::default()), + JReq::RelayTx(_) => JResp::RelayTx(Default::default()), + JReq::SyncInfo(_) => JResp::SyncInfo(Default::default()), + JReq::GetTransactionPoolBacklog(_) => JResp::GetTransactionPoolBacklog(Default::default()), + JReq::GetMinerData(_) => JResp::GetMinerData(Default::default()), + JReq::PruneBlockchain(_) => JResp::PruneBlockchain(Default::default()), + JReq::CalcPow(_) => JResp::CalcPow(Default::default()), + JReq::FlushCache(_) => JResp::FlushCache(Default::default()), + JReq::AddAuxPow(_) => JResp::AddAuxPow(Default::default()), + JReq::GetTxIdsLoose(_) => JResp::GetTxIdsLoose(Default::default()), + })), + Request::Binary(b) => Response::Binary(match b { + BReq::GetBlocks(_) => BResp::GetBlocks(Default::default()), + BReq::GetBlocksByHeight(_) => BResp::GetBlocksByHeight(Default::default()), + BReq::GetHashes(_) => BResp::GetHashes(Default::default()), + BReq::GetOutputIndexes(_) => BResp::GetOutputIndexes(Default::default()), + BReq::GetOuts(_) => BResp::GetOuts(Default::default()), + BReq::GetTransactionPoolHashes(_) => BResp::GetTransactionPoolHashes(Default::default()), + BReq::GetOutputDistribution(_) => BResp::GetOutputDistribution(Default::default()), + }), + Request::Other(o) => Response::Other(match o { + OReq::GetHeight(_) => OResp::GetHeight(Default::default()), + OReq::GetTransactions(_) => OResp::GetTransactions(Default::default()), + OReq::GetAltBlocksHashes(_) => OResp::GetAltBlocksHashes(Default::default()), + OReq::IsKeyImageSpent(_) => OResp::IsKeyImageSpent(Default::default()), + OReq::SendRawTransaction(_) => OResp::SendRawTransaction(Default::default()), + OReq::StartMining(_) => OResp::StartMining(Default::default()), + OReq::StopMining(_) => OResp::StopMining(Default::default()), + OReq::MiningStatus(_) => OResp::MiningStatus(Default::default()), + OReq::SaveBc(_) => OResp::SaveBc(Default::default()), + OReq::GetPeerList(_) => OResp::GetPeerList(Default::default()), + OReq::SetLogHashRate(_) => OResp::SetLogHashRate(Default::default()), + OReq::SetLogLevel(_) => OResp::SetLogLevel(Default::default()), + OReq::SetLogCategories(_) => OResp::SetLogCategories(Default::default()), + OReq::SetBootstrapDaemon(_) => OResp::SetBootstrapDaemon(Default::default()), + OReq::GetTransactionPool(_) => OResp::GetTransactionPool(Default::default()), + OReq::GetTransactionPoolStats(_) => OResp::GetTransactionPoolStats(Default::default()), + OReq::StopDaemon(_) => OResp::StopDaemon(Default::default()), + OReq::GetLimit(_) => OResp::GetLimit(Default::default()), + OReq::SetLimit(_) => OResp::SetLimit(Default::default()), + OReq::OutPeers(_) => OResp::OutPeers(Default::default()), + OReq::InPeers(_) => OResp::InPeers(Default::default()), + OReq::GetNetStats(_) => OResp::GetNetStats(Default::default()), + OReq::GetOuts(_) => OResp::GetOuts(Default::default()), + OReq::Update(_) => OResp::Update(Default::default()), + OReq::PopBlocks(_) => OResp::PopBlocks(Default::default()), + OReq::GetTransactionPoolHashes(_) => OResp::GetTransactionPoolHashes(Default::default()), + OReq::GetPublicNodes(_) => OResp::GetPublicNodes(Default::default()), + }) + }; + + let (tx, rx) = channel(); + drop(tx.send(Ok(resp))); + InfallibleOneshotReceiver::from(rx) + } +} diff --git a/rpc/interface/src/rpc_service.rs b/rpc/interface/src/rpc_service.rs deleted file mode 100644 index 191ab50c9..000000000 --- a/rpc/interface/src/rpc_service.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Use -use tower::Service; - -use cuprate_helper::asynch::InfallibleOneshotReceiver; - -use crate::{error::Error, request::Request, response::Response}; - -//---------------------------------------------------------------------------------------------------- TODO -/// TODO -pub trait RpcService: - Clone - + Send - + Sync - + 'static - + Service< - Request, - Response = Response, - Error = Error, - Future = InfallibleOneshotReceiver>, - > -{ -} - -/// TODO -impl RpcService for S where - Self: Clone - + Send - + Sync - + 'static - + Service< - Request, - Response = Response, - Error = Error, - Future = InfallibleOneshotReceiver>, - > -{ -} From 1b3cceab5fcfe8eaece4091eb34438a13b9a95fd Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Fri, 26 Jul 2024 21:08:48 -0400 Subject: [PATCH 74/93] interface: readme docs + test --- Cargo.lock | 102 +++++++++++++++++ rpc/interface/Cargo.toml | 4 + rpc/interface/README.md | 103 +++++++++++++++--- rpc/interface/src/free.rs | 6 +- rpc/interface/src/lib.rs | 12 +- rpc/interface/src/route/bin.rs | 6 +- .../src/route/{unknown.rs => fallback.rs} | 2 +- rpc/interface/src/route/json.rs | 8 +- rpc/interface/src/route/mod.rs | 2 +- rpc/interface/src/route/other.rs | 8 +- rpc/interface/src/{error.rs => rpc_error.rs} | 7 +- rpc/interface/src/rpc_handler.rs | 10 +- rpc/interface/src/rpc_handler_dummy.rs | 21 ++-- .../src/{request.rs => rpc_request.rs} | 2 +- .../src/{response.rs => rpc_response.rs} | 2 +- rpc/types/src/bin.rs | 6 +- .../src/{rpc_request.rs => is_restricted.rs} | 2 +- rpc/types/src/json.rs | 6 +- rpc/types/src/lib.rs | 4 +- rpc/types/src/macros.rs | 12 +- rpc/types/src/other.rs | 6 +- 21 files changed, 259 insertions(+), 72 deletions(-) rename rpc/interface/src/route/{unknown.rs => fallback.rs} (91%) rename rpc/interface/src/{error.rs => rpc_error.rs} (85%) rename rpc/interface/src/{request.rs => rpc_request.rs} (96%) rename rpc/interface/src/{response.rs => rpc_response.rs} (96%) rename rpc/types/src/{rpc_request.rs => is_restricted.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index 8cb21d402..94beecb5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -119,6 +125,8 @@ dependencies = [ "http", "http-body", "http-body-util", + "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -129,10 +137,13 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", + "serde_urlencoded", "sync_wrapper 1.0.1", + "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -153,6 +164,7 @@ dependencies = [ "sync_wrapper 0.1.2", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -429,6 +441,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -815,7 +836,10 @@ dependencies = [ "futures", "paste", "serde", + "serde_json", + "tokio", "tower", + "ureq", ] [[package]] @@ -1083,6 +1107,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flexible-transcript" version = "0.3.2" @@ -1232,6 +1266,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1369,6 +1422,12 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.3.1" @@ -1378,9 +1437,11 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2296,6 +2357,7 @@ version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -2453,6 +2515,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2827,6 +2901,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2891,6 +2966,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.1" @@ -2998,6 +3091,15 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index b39bcdc51..a778b3077 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -25,3 +25,7 @@ paste = { workspace = true } futures = { workspace = true } [dev-dependencies] +axum = { version = "0.7.5", features = ["json", "tokio", "http2"] } +serde_json = { workspace = true, features = ["std"] } +tokio = { workspace = true, features = ["full"] } +ureq = { version = "2.10.0", features = ["json"] } \ No newline at end of file diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 96eadbee3..e74a018d6 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -3,38 +3,109 @@ This crate provides Cuprate's RPC _interface_. ```text cuprate-rpc-interface provides these parts - │ - ┌────┴────┐ + │ │ ┌───────────────────────────┤ ├───────────────────┐ ▼ ▼ ▼ ▼ -CLIENT -> ROUTE -> REQUEST -> HANDLER -> RESPONSE -> CLIENT +CLIENT ─► ROUTE ─► REQUEST ─► HANDLER ─► RESPONSE ─► CLIENT ▲ ▲ └───┬───┘ │ You provide this part ``` -Everything coming _in_ from a client including: -- Any lower-level HTTP stuff -- Endpoint routing -- Request (de)serialization - -is handled by this crate. +Everything coming _in_ from a client is handled by this crate. -This is where your [`RpcHandler`] turns this [`Request`] into a [`Response`]. +This is where your [`RpcHandler`] turns this [`RpcRequest`] into a [`RpcResponse`]. You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client. The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate; it implements the regular RPC server modeled after `monerod`. -# Router +# Purpose +`cuprate-rpc-interface` is built on-top of [`axum`], +which is the crate _actually_ handling everything. + +This crate simply handles: +- Registering endpoint routes (e.g. `/get_block.bin`) +- Handler function signatures +- (De)serialization of data (JSON-RPC methods/params, binary, JSON) + +The actual server details are all handled by the [`axum`] and [`tower`] ecosystem, i.e. +the whole purpose of this crate is to: +1. Implement a [`RpcHandler`] +2. Use it with [`create_router`] to generate an + [`axum::Router`] with all Monero RPC routes/types set +4. Do whatever with it + +# The [`RpcHandler`] +This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s, +i.e. the "inner handler". + +Said concretely, [`RpcHandler`] is a `tower::Service` where the associated types are from this crate: +- [`RpcRequest`] +- [`RpcResponse`] +- [`RpcError`] + +The [`RpcHandler`] must also hold some state that is required +for RPC server operation. + +The only state currently need is [`RpcHandler::restricted`], which determines if an RPC +server is restricted or not, and thus, if some endpoints/methods are allowed or not. -# Requests, responses, and errors +# Example +Example usage of this crate + starting an RPC server. -# The RPC handler [`Service`](tower::Service) +This uses `RpcHandlerDummy` as the handler; it responds with the +correct response, but always with a default value. -# Routes +```rust +use std::sync::Arc; + +use axum::{extract::Request, response::Response}; +use tokio::{net::TcpListener, sync::Barrier}; + +use cuprate_rpc_types::other::{OtherRequest, OtherResponse}; +use cuprate_rpc_interface::{create_router, RpcHandlerDummy, RpcRequest}; + +#[tokio::main] +async fn main() { + // Create the router. + let state = RpcHandlerDummy { restricted: false }; + let router = create_router().with_state(state); + + // Start a server. + let listener = TcpListener::bind("127.0.0.1:0") + .await + .unwrap(); + let port = listener.local_addr().unwrap().port(); + + // Run the server with `axum`. + let barrier = Arc::new(Barrier::new(2)); + let barrier2 = barrier.clone(); + tokio::task::spawn(async move { + barrier2.wait(); + axum::serve(listener, router).await.unwrap(); + }); + barrier.wait(); + + // Send a request. + // TODO: endpoints without inputs shouldn't error without input. + let url = format!("http://127.0.0.1:{port}/get_height"); + let request = OtherRequest::GetHeight(Default::default()); + let body: OtherResponse = ureq::get(&url) + .set("Content-Type", "application/json") + .send_json(request) + .unwrap() + .into_json() + .unwrap(); + + // Assert the response is as expected. + // We just made an RPC call :) + let expected = OtherResponse::GetHeight(Default::default()); + assert_eq!(body, expected); +} +``` # Feature flags List of feature flags for `cuprate-rpc-interface`. @@ -42,4 +113,6 @@ List of feature flags for `cuprate-rpc-interface`. All are enabled by default. | Feature flag | Does what | -|--------------|-----------| \ No newline at end of file +|--------------|-----------| +| `serde` | Enables serde on applicable types +| `dummy` | Enables the `RpcHandlerDummy` type \ No newline at end of file diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 70e6a42ba..090485ba5 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -4,7 +4,7 @@ use axum::{routing::method_routing::get, Router}; use crate::{ - route::{bin, json, other, unknown}, + route::{bin, fallback, json, other}, rpc_handler::RpcHandler, }; @@ -62,10 +62,10 @@ pub fn create_router() -> Router { .route("/get_outs.bin", get(bin::get_outs::)) .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) - // Unknown route (catch-all). + // Fallback route (catch-all). // // Deprecated routes will also route here, list: // - `get_info` // - `getinfo` - .route("/*", get(unknown::unknown)) + .fallback(fallback::fallback) } diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 894829c5f..fa082a0da 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -106,20 +106,20 @@ //---------------------------------------------------------------------------------------------------- Mod mod constants; -mod error; mod free; mod macros; -mod request; -mod response; mod route; +mod rpc_error; mod rpc_handler; #[cfg(feature = "dummy")] mod rpc_handler_dummy; +mod rpc_request; +mod rpc_response; -pub use error::Error; pub use free::create_router; -pub use request::Request; -pub use response::Response; +pub use rpc_error::RpcError; pub use rpc_handler::RpcHandler; #[cfg(feature = "dummy")] pub use rpc_handler_dummy::RpcHandlerDummy; +pub use rpc_request::RpcRequest; +pub use rpc_response::RpcResponse; diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index ec922f4f6..3da903340 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -8,7 +8,7 @@ use tower::ServiceExt; use cuprate_epee_encoding::from_bytes; use cuprate_rpc_types::bin::{BinRequest, BinResponse}; -use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -29,11 +29,11 @@ macro_rules! generate_endpoints { ); // Send request. - let request = Request::Binary(request); + let request = RpcRequest::Binary(request); let channel = handler.oneshot(request).await?; // Assert the response from the inner handler is correct. - let Response::Binary(response) = todo!() else { + let RpcResponse::Binary(response) = todo!() else { panic!("RPC handler did not return a binary response"); }; let BinResponse::$variant(response) = response else { diff --git a/rpc/interface/src/route/unknown.rs b/rpc/interface/src/route/fallback.rs similarity index 91% rename from rpc/interface/src/route/unknown.rs rename to rpc/interface/src/route/fallback.rs index b980e0dca..bce22ee8f 100644 --- a/rpc/interface/src/route/unknown.rs +++ b/rpc/interface/src/route/fallback.rs @@ -6,7 +6,7 @@ use axum::http::StatusCode; //---------------------------------------------------------------------------------------------------- Routes /// TODO -pub(crate) async fn unknown() -> StatusCode { +pub(crate) async fn fallback() -> StatusCode { StatusCode::NOT_FOUND } diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index 1cc497ffa..a9d8be734 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -13,10 +13,10 @@ use cuprate_json_rpc::{ }; use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse}, - RpcRequest, + IsRestricted, }; -use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -44,11 +44,11 @@ pub(crate) async fn json_rpc( } // Send request. - let request = Request::JsonRpc(request); + let request = RpcRequest::JsonRpc(request); let channel = handler.oneshot(request).await?; // Assert the response from the inner handler is correct. - let Response::JsonRpc(response) = channel else { + let RpcResponse::JsonRpc(response) = channel else { panic!("RPC handler returned incorrect response"); }; diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 2c8c3063f..89018c51f 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,6 +1,6 @@ //! TODO pub(crate) mod bin; +pub(crate) mod fallback; pub(crate) mod json; pub(crate) mod other; -pub(crate) mod unknown; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index ffbdea9c4..0e5244f54 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -23,10 +23,10 @@ use cuprate_rpc_types::{ StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, }, - RpcRequest, + IsRestricted, }; -use crate::{request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes /// TODO @@ -48,11 +48,11 @@ macro_rules! generate_endpoints { } // Send request. - let request = Request::Other(OtherRequest::$variant(request)); + let request = RpcRequest::Other(OtherRequest::$variant(request)); let channel = handler.oneshot(request).await?; // Assert the response from the inner handler is correct. - let Response::Other(response) = channel else { + let RpcResponse::Other(response) = channel else { panic!("RPC handler did not return a binary response"); }; let OtherResponse::$variant(response) = response else { diff --git a/rpc/interface/src/error.rs b/rpc/interface/src/rpc_error.rs similarity index 85% rename from rpc/interface/src/error.rs rename to rpc/interface/src/rpc_error.rs index c613a4f24..17f611d40 100644 --- a/rpc/interface/src/error.rs +++ b/rpc/interface/src/rpc_error.rs @@ -1,16 +1,15 @@ //! TODO //---------------------------------------------------------------------------------------------------- Import - use axum::http::StatusCode; //---------------------------------------------------------------------------------------------------- TODO /// TODO #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Error {} +pub enum RpcError {} -impl From for StatusCode { - fn from(value: Error) -> Self { +impl From for StatusCode { + fn from(value: RpcError) -> Self { // TODO Self::INTERNAL_SERVER_ERROR } diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 4b5e7579b..1a9285a92 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -10,7 +10,7 @@ use cuprate_helper::asynch::InfallibleOneshotReceiver; use cuprate_json_rpc::Id; use cuprate_rpc_types::json::JsonRpcRequest; -use crate::{error::Error, request::Request, response::Response}; +use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- TODO /// TODO @@ -20,10 +20,10 @@ pub trait RpcHandler: + Sync + 'static + Service< - Request, - Response = Response, - Error = Error, - Future = InfallibleOneshotReceiver>, + RpcRequest, + Response = RpcResponse, + Error = RpcError, + Future = InfallibleOneshotReceiver>, > { /// TODO diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index 5a1964c50..0f2285801 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -10,7 +10,10 @@ use cuprate_helper::asynch::InfallibleOneshotReceiver; use cuprate_json_rpc::Id; use cuprate_rpc_types::json::JsonRpcRequest; -use crate::{error::Error, request::Request, response::Response, rpc_handler::RpcHandler}; +use crate::{ + rpc_error::RpcError, rpc_handler::RpcHandler, rpc_request::RpcRequest, + rpc_response::RpcResponse, +}; //---------------------------------------------------------------------------------------------------- TODO /// TODO @@ -26,16 +29,16 @@ impl RpcHandler for RpcHandlerDummy { } } -impl Service for RpcHandlerDummy { - type Response = Response; - type Error = Error; - type Future = InfallibleOneshotReceiver>; +impl Service for RpcHandlerDummy { + type Response = RpcResponse; + type Error = RpcError; + type Future = InfallibleOneshotReceiver>; fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: Request) -> Self::Future { + fn call(&mut self, req: RpcRequest) -> Self::Future { use cuprate_rpc_types::bin::BinRequest as BReq; use cuprate_rpc_types::bin::BinResponse as BResp; use cuprate_rpc_types::json::JsonRpcRequest as JReq; @@ -46,7 +49,7 @@ impl Service for RpcHandlerDummy { #[rustfmt::skip] #[allow(clippy::default_trait_access)] let resp = match req { - Request::JsonRpc(j) => Response::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body { + RpcRequest::JsonRpc(j) => RpcResponse::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body { JReq::GetBlockCount(_) => JResp::GetBlockCount(Default::default()), JReq::OnGetBlockHash(_) => JResp::OnGetBlockHash(Default::default()), JReq::SubmitBlock(_) => JResp::SubmitBlock(Default::default()), @@ -78,7 +81,7 @@ impl Service for RpcHandlerDummy { JReq::AddAuxPow(_) => JResp::AddAuxPow(Default::default()), JReq::GetTxIdsLoose(_) => JResp::GetTxIdsLoose(Default::default()), })), - Request::Binary(b) => Response::Binary(match b { + RpcRequest::Binary(b) => RpcResponse::Binary(match b { BReq::GetBlocks(_) => BResp::GetBlocks(Default::default()), BReq::GetBlocksByHeight(_) => BResp::GetBlocksByHeight(Default::default()), BReq::GetHashes(_) => BResp::GetHashes(Default::default()), @@ -87,7 +90,7 @@ impl Service for RpcHandlerDummy { BReq::GetTransactionPoolHashes(_) => BResp::GetTransactionPoolHashes(Default::default()), BReq::GetOutputDistribution(_) => BResp::GetOutputDistribution(Default::default()), }), - Request::Other(o) => Response::Other(match o { + RpcRequest::Other(o) => RpcResponse::Other(match o { OReq::GetHeight(_) => OResp::GetHeight(Default::default()), OReq::GetTransactions(_) => OResp::GetTransactions(Default::default()), OReq::GetAltBlocksHashes(_) => OResp::GetAltBlocksHashes(Default::default()), diff --git a/rpc/interface/src/request.rs b/rpc/interface/src/rpc_request.rs similarity index 96% rename from rpc/interface/src/request.rs rename to rpc/interface/src/rpc_request.rs index 65b404c2c..de81c7e16 100644 --- a/rpc/interface/src/request.rs +++ b/rpc/interface/src/rpc_request.rs @@ -5,7 +5,7 @@ use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherReque //---------------------------------------------------------------------------------------------------- TODO /// TODO -pub enum Request { +pub enum RpcRequest { /// TODO JsonRpc(cuprate_json_rpc::Request), /// TODO diff --git a/rpc/interface/src/response.rs b/rpc/interface/src/rpc_response.rs similarity index 96% rename from rpc/interface/src/response.rs rename to rpc/interface/src/rpc_response.rs index f37e41bea..c904ad069 100644 --- a/rpc/interface/src/response.rs +++ b/rpc/interface/src/rpc_response.rs @@ -5,7 +5,7 @@ use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherRes //---------------------------------------------------------------------------------------------------- Status /// TODO -pub enum Response { +pub enum RpcResponse { /// TODO JsonRpc(cuprate_json_rpc::Response), /// TODO diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index b28cd29a2..5b5999491 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -28,7 +28,7 @@ use crate::{ HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolInfoExtent, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, - RpcRequest, + IsRestricted, }; //---------------------------------------------------------------------------------------------------- Definitions @@ -399,6 +399,7 @@ impl EpeeObject for GetBlocksResponse { #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum BinRequest { GetBlocks(GetBlocksRequest), GetBlocksByHeight(GetBlocksByHeightRequest), @@ -409,7 +410,7 @@ pub enum BinRequest { GetOutputDistribution(crate::json::GetOutputDistributionRequest), } -impl RpcRequest for BinRequest { +impl IsRestricted for BinRequest { /// All binary methods are un-restricted, i.e. // all of them will return `false`. fn is_restricted(&self) -> bool { @@ -427,6 +428,7 @@ impl RpcRequest for BinRequest { //---------------------------------------------------------------------------------------------------- Response /// TODO +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] diff --git a/rpc/types/src/rpc_request.rs b/rpc/types/src/is_restricted.rs similarity index 97% rename from rpc/types/src/rpc_request.rs rename to rpc/types/src/is_restricted.rs index d060c524c..e17faedd7 100644 --- a/rpc/types/src/rpc_request.rs +++ b/rpc/types/src/is_restricted.rs @@ -4,7 +4,7 @@ //---------------------------------------------------------------------------------------------------- Struct definitions /// TODO -pub trait RpcRequest { +pub trait IsRestricted { /// Returns `true` if this method should /// only be allowed on local servers. /// diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index a4f1ef4f0..ce7b46c2b 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -19,7 +19,7 @@ use crate::{ GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, }, - RpcRequest, + IsRestricted, }; //---------------------------------------------------------------------------------------------------- Macro @@ -1560,6 +1560,7 @@ define_request_and_response! { //---------------------------------------------------------------------------------------------------- Request /// TODO +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] @@ -1596,7 +1597,7 @@ pub enum JsonRpcRequest { GetTxIdsLoose(GetTxIdsLooseRequest), } -impl RpcRequest for JsonRpcRequest { +impl IsRestricted for JsonRpcRequest { fn is_restricted(&self) -> bool { match self { // Normal methods. These are allowed @@ -1640,6 +1641,7 @@ impl RpcRequest for JsonRpcRequest { //---------------------------------------------------------------------------------------------------- Response /// TODO +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 2a7acc4d2..569c70378 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -111,8 +111,8 @@ mod constants; mod defaults; mod free; +mod is_restricted; mod macros; -mod rpc_request; #[cfg(feature = "serde")] mod serde; @@ -128,4 +128,4 @@ pub use constants::{ CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR, }; -pub use rpc_request::RpcRequest; +pub use is_restricted::IsRestricted; diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index 7eb83593d..c74b4853e 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -141,12 +141,12 @@ macro_rules! define_request_and_response { } pub(crate) use define_request_and_response; -//---------------------------------------------------------------------------------------------------- impl_rpc_request +//---------------------------------------------------------------------------------------------------- impl_is_restricted /// TODO -macro_rules! impl_rpc_request { +macro_rules! impl_is_restricted { // TODO ($t:ident, $restricted:ident) => { - impl $crate::RpcRequest for $t { + impl $crate::IsRestricted for $t { fn is_restricted(&self) -> bool { true } @@ -155,14 +155,14 @@ macro_rules! impl_rpc_request { // TODO ($t:ident) => { - impl $crate::RpcRequest for $t { + impl $crate::IsRestricted for $t { fn is_restricted(&self) -> bool { false } } }; } -pub(crate) use impl_rpc_request; +pub(crate) use impl_is_restricted; //---------------------------------------------------------------------------------------------------- define_request /// Define a request type. @@ -201,7 +201,7 @@ macro_rules! define_request { )* } - $crate::macros::impl_rpc_request!($t $(, $restricted)?); + $crate::macros::impl_is_restricted!($t $(, $restricted)?); #[cfg(feature = "epee")] ::cuprate_epee_encoding::epee_object! { diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 137b576f7..0b58c0a87 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -14,7 +14,7 @@ use crate::{ GetOutputsOut, KeyImageSpentStatus, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, TxpoolStats, }, - RpcRequest, + IsRestricted, }; //---------------------------------------------------------------------------------------------------- Macro @@ -953,6 +953,7 @@ define_request_and_response! { //---------------------------------------------------------------------------------------------------- Request /// TODO +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] @@ -986,7 +987,7 @@ pub enum OtherRequest { GetPublicNodes(GetPublicNodesRequest), } -impl RpcRequest for OtherRequest { +impl IsRestricted for OtherRequest { fn is_restricted(&self) -> bool { match self { // Normal methods. These are allowed @@ -1028,6 +1029,7 @@ impl RpcRequest for OtherRequest { //---------------------------------------------------------------------------------------------------- Response /// TODO +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] From 4377ffede05e4adaf20fcc70a2d8fda276993a3d Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 28 Jul 2024 15:49:19 -0400 Subject: [PATCH 75/93] `IsRestricted` -> `RpcCall` --- rpc/interface/src/route/json.rs | 2 +- rpc/interface/src/route/other.rs | 2 +- rpc/types/src/bin.rs | 4 ++-- rpc/types/src/json.rs | 4 ++-- rpc/types/src/lib.rs | 4 ++-- rpc/types/src/macros.rs | 12 ++++++------ rpc/types/src/other.rs | 4 ++-- rpc/types/src/{is_restricted.rs => rpc_call.rs} | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) rename rpc/types/src/{is_restricted.rs => rpc_call.rs} (92%) diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index a9d8be734..7000c4472 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -13,7 +13,7 @@ use cuprate_json_rpc::{ }; use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse}, - IsRestricted, + RpcCall, }; use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 0e5244f54..4c85f0c85 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -23,7 +23,7 @@ use cuprate_rpc_types::{ StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse, UpdateRequest, UpdateResponse, }, - IsRestricted, + RpcCall, }; use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 5b5999491..f0cf2932e 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -28,7 +28,7 @@ use crate::{ HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolInfoExtent, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, - IsRestricted, + rpc_call::RpcCall, }; //---------------------------------------------------------------------------------------------------- Definitions @@ -410,7 +410,7 @@ pub enum BinRequest { GetOutputDistribution(crate::json::GetOutputDistributionRequest), } -impl IsRestricted for BinRequest { +impl RpcCall for BinRequest { /// All binary methods are un-restricted, i.e. // all of them will return `false`. fn is_restricted(&self) -> bool { diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index ce7b46c2b..d2e80f2d2 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -19,7 +19,7 @@ use crate::{ GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, }, - IsRestricted, + rpc_call::RpcCall, }; //---------------------------------------------------------------------------------------------------- Macro @@ -1597,7 +1597,7 @@ pub enum JsonRpcRequest { GetTxIdsLoose(GetTxIdsLooseRequest), } -impl IsRestricted for JsonRpcRequest { +impl RpcCall for JsonRpcRequest { fn is_restricted(&self) -> bool { match self { // Normal methods. These are allowed diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 569c70378..0fc2c3f80 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -111,8 +111,8 @@ mod constants; mod defaults; mod free; -mod is_restricted; mod macros; +mod rpc_call; #[cfg(feature = "serde")] mod serde; @@ -128,4 +128,4 @@ pub use constants::{ CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR, }; -pub use is_restricted::IsRestricted; +pub use rpc_call::RpcCall; diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index c74b4853e..b39c2ae1d 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -141,12 +141,12 @@ macro_rules! define_request_and_response { } pub(crate) use define_request_and_response; -//---------------------------------------------------------------------------------------------------- impl_is_restricted +//---------------------------------------------------------------------------------------------------- impl_rpc_call /// TODO -macro_rules! impl_is_restricted { +macro_rules! impl_rpc_call { // TODO ($t:ident, $restricted:ident) => { - impl $crate::IsRestricted for $t { + impl $crate::RpcCall for $t { fn is_restricted(&self) -> bool { true } @@ -155,14 +155,14 @@ macro_rules! impl_is_restricted { // TODO ($t:ident) => { - impl $crate::IsRestricted for $t { + impl $crate::RpcCall for $t { fn is_restricted(&self) -> bool { false } } }; } -pub(crate) use impl_is_restricted; +pub(crate) use impl_rpc_call; //---------------------------------------------------------------------------------------------------- define_request /// Define a request type. @@ -201,7 +201,7 @@ macro_rules! define_request { )* } - $crate::macros::impl_is_restricted!($t $(, $restricted)?); + $crate::macros::impl_rpc_call!($t $(, $restricted)?); #[cfg(feature = "epee")] ::cuprate_epee_encoding::epee_object! { diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 0b58c0a87..8b06171c9 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -14,7 +14,7 @@ use crate::{ GetOutputsOut, KeyImageSpentStatus, OutKey, Peer, PublicNode, SpentKeyImageInfo, Status, TxEntry, TxInfo, TxpoolStats, }, - IsRestricted, + rpc_call::RpcCall, }; //---------------------------------------------------------------------------------------------------- Macro @@ -987,7 +987,7 @@ pub enum OtherRequest { GetPublicNodes(GetPublicNodesRequest), } -impl IsRestricted for OtherRequest { +impl RpcCall for OtherRequest { fn is_restricted(&self) -> bool { match self { // Normal methods. These are allowed diff --git a/rpc/types/src/is_restricted.rs b/rpc/types/src/rpc_call.rs similarity index 92% rename from rpc/types/src/is_restricted.rs rename to rpc/types/src/rpc_call.rs index e17faedd7..60dc08923 100644 --- a/rpc/types/src/is_restricted.rs +++ b/rpc/types/src/rpc_call.rs @@ -4,8 +4,8 @@ //---------------------------------------------------------------------------------------------------- Struct definitions /// TODO -pub trait IsRestricted { - /// Returns `true` if this method should +pub trait RpcCall { + /// Returns `true` if this RPC method should /// only be allowed on local servers. /// /// If this returns `false`, it should be @@ -14,7 +14,7 @@ pub trait IsRestricted { /// /// ```rust /// use cuprate_rpc_types::{ - /// RpcRequest, + /// RpcCall, /// json::{GetBlockCountRequest, GetConnectionsRequest}, /// }; /// From 25d643598f505509c6c364c7bdda0c115ebe756c Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 28 Jul 2024 20:53:10 -0400 Subject: [PATCH 76/93] fix no input route problem --- rpc/interface/README.md | 6 +- rpc/interface/src/route/bin.rs | 48 ++++++++++-- rpc/interface/src/route/json.rs | 2 +- rpc/interface/src/route/other.rs | 69 ++++++++++++----- rpc/types/src/bin.rs | 32 +++++--- rpc/types/src/json.rs | 126 +++++++++++++++++++------------ rpc/types/src/lib.rs | 2 +- rpc/types/src/macros.rs | 54 ++++++++++--- rpc/types/src/other.rs | 113 ++++++++++++++++----------- rpc/types/src/rpc_call.rs | 47 +++++++++++- 10 files changed, 352 insertions(+), 147 deletions(-) diff --git a/rpc/interface/README.md b/rpc/interface/README.md index e74a018d6..756641294 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -89,13 +89,11 @@ async fn main() { }); barrier.wait(); - // Send a request. - // TODO: endpoints without inputs shouldn't error without input. + // Send a request. This endpoint has no inputs. let url = format!("http://127.0.0.1:{port}/get_height"); - let request = OtherRequest::GetHeight(Default::default()); let body: OtherResponse = ureq::get(&url) .set("Content-Type", "application/json") - .send_json(request) + .call() .unwrap() .into_json() .unwrap(); diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 3da903340..c516c6260 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -6,13 +6,13 @@ use axum::{body::Bytes, extract::State, http::StatusCode}; use tower::ServiceExt; use cuprate_epee_encoding::from_bytes; -use cuprate_rpc_types::bin::{BinRequest, BinResponse}; +use cuprate_rpc_types::bin::{BinRequest, BinResponse, GetTransactionPoolHashesRequest}; use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes /// TODO -macro_rules! generate_endpoints { +macro_rules! generate_endpoints_with_input { ($( $endpoint:ident => $variant:ident ),*) => { paste::paste! { @@ -28,9 +28,38 @@ macro_rules! generate_endpoints { from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? ); + generate_endpoints_inner!($variant, handler, request) + } + )* + }}; +} + +/// TODO +macro_rules! generate_endpoints_with_no_input { + ($( + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + /// TODO + #[allow(unused_mut)] + pub(crate) async fn $endpoint( + State(handler): State, + ) -> Result { + const REQUEST: BinRequest = BinRequest::$variant([<$variant Request>] {}); + generate_endpoints_inner!($variant, handler, REQUEST) + } + )* + }}; +} + +/// TODO +macro_rules! generate_endpoints_inner { + ($variant:ident, $handler:ident, $request:expr) => { + paste::paste! { + { // Send request. - let request = RpcRequest::Binary(request); - let channel = handler.oneshot(request).await?; + let request = RpcRequest::Binary($request); + let channel = $handler.oneshot(request).await?; // Assert the response from the inner handler is correct. let RpcResponse::Binary(response) = todo!() else { @@ -46,20 +75,23 @@ macro_rules! generate_endpoints { Err(e) => Err(StatusCode::INTERNAL_SERVER_ERROR), } } - )* - }}; + } + }; } -generate_endpoints! { +generate_endpoints_with_input! { get_blocks => GetBlocks, get_blocks_by_height => GetBlocksByHeight, get_hashes => GetHashes, get_o_indexes => GetOutputIndexes, get_outs => GetOuts, - get_transaction_pool_hashes => GetTransactionPoolHashes, get_output_distribution => GetOutputDistribution } +generate_endpoints_with_no_input! { + get_transaction_pool_hashes => GetTransactionPoolHashes +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index 7000c4472..9286bdf62 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -13,7 +13,7 @@ use cuprate_json_rpc::{ }; use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse}, - RpcCall, + RpcCallValue, }; use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 4c85f0c85..3c3030e0c 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -30,9 +30,9 @@ use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcR //---------------------------------------------------------------------------------------------------- Routes /// TODO -macro_rules! generate_endpoints { +macro_rules! generate_endpoints_with_input { ($( - $endpoint:ident => $variant:ident $(=> $constructor:expr)? + $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( /// TODO @@ -41,15 +41,43 @@ macro_rules! generate_endpoints { State(mut handler): State, Json(request): Json<[<$variant Request>]>, ) -> Result]>, StatusCode> { + generate_endpoints_inner!($variant, handler, request) + } + )* + }}; +} + +/// TODO +macro_rules! generate_endpoints_with_no_input { + ($( + $endpoint:ident => $variant:ident + ),*) => { paste::paste! { + $( + /// TODO + #[allow(unused_mut)] + pub(crate) async fn $endpoint( + State(mut handler): State, + ) -> Result]>, StatusCode> { + generate_endpoints_inner!($variant, handler, [<$variant Request>] {}) + } + )* + }}; +} + +/// TODO +macro_rules! generate_endpoints_inner { + ($variant:ident, $handler:ident, $request:expr) => { + paste::paste! { + { // Check if restricted. - if request.is_restricted() && handler.restricted() { + if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() { // TODO: mimic `monerod` behavior. return Err(StatusCode::FORBIDDEN); } // Send request. - let request = RpcRequest::Other(OtherRequest::$variant(request)); - let channel = handler.oneshot(request).await?; + let request = RpcRequest::Other(OtherRequest::$variant($request)); + let channel = $handler.oneshot(request).await?; // Assert the response from the inner handler is correct. let RpcResponse::Other(response) = channel else { @@ -61,40 +89,43 @@ macro_rules! generate_endpoints { Ok(Json(response)) } - )* - }}; + } + }; } -generate_endpoints! { - get_height => GetHeight, +generate_endpoints_with_input! { get_transactions => GetTransactions, - get_alt_blocks_hashes => GetAltBlocksHashes, is_key_image_spent => IsKeyImageSpent, send_raw_transaction => SendRawTransaction, start_mining => StartMining, - stop_mining => StopMining, - mining_status => MiningStatus, - save_bc => SaveBc, get_peer_list => GetPeerList, set_log_hash_rate => SetLogHashRate, set_log_level => SetLogLevel, set_log_categories => SetLogCategories, set_bootstrap_daemon => SetBootstrapDaemon, - get_transaction_pool => GetTransactionPool, - get_transaction_pool_stats => GetTransactionPoolStats, - stop_daemon => StopDaemon, - get_limit => GetLimit, set_limit => SetLimit, out_peers => OutPeers, in_peers => InPeers, - get_net_stats => GetNetStats, get_outs => GetOuts, update => Update, pop_blocks => PopBlocks, - get_transaction_pool_hashes => GetTransactionPoolHashes, get_public_nodes => GetPublicNodes } +generate_endpoints_with_no_input! { + get_height => GetHeight, + get_alt_blocks_hashes => GetAltBlocksHashes, + stop_mining => StopMining, + mining_status => MiningStatus, + save_bc => SaveBc, + get_transaction_pool => GetTransactionPool, + get_transaction_pool_stats => GetTransactionPoolStats, + stop_daemon => StopDaemon, + get_limit => GetLimit, + get_net_stats => GetNetStats, + get_transaction_pool_hashes => GetTransactionPoolHashes +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index f0cf2932e..65fd5caaa 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -28,7 +28,7 @@ use crate::{ HardforkEntry, HistogramEntry, OutKeyBin, OutputDistributionData, Peer, PoolInfoExtent, PoolTxInfo, SetBan, Span, Status, TxBacklogEntry, }, - rpc_call::RpcCall, + rpc_call::{RpcCall, RpcCallValue}, }; //---------------------------------------------------------------------------------------------------- Definitions @@ -410,18 +410,28 @@ pub enum BinRequest { GetOutputDistribution(crate::json::GetOutputDistributionRequest), } -impl RpcCall for BinRequest { - /// All binary methods are un-restricted, i.e. - // all of them will return `false`. +impl RpcCallValue for BinRequest { fn is_restricted(&self) -> bool { match self { - Self::GetBlocks(_) - | Self::GetBlocksByHeight(_) - | Self::GetHashes(_) - | Self::GetOutputIndexes(_) - | Self::GetOuts(_) - | Self::GetTransactionPoolHashes(_) - | Self::GetOutputDistribution(_) => false, + Self::GetBlocks(x) => x.is_restricted(), + Self::GetBlocksByHeight(x) => x.is_restricted(), + Self::GetHashes(x) => x.is_restricted(), + Self::GetOutputIndexes(x) => x.is_restricted(), + Self::GetOuts(x) => x.is_restricted(), + Self::GetTransactionPoolHashes(x) => x.is_restricted(), + Self::GetOutputDistribution(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetBlocks(x) => x.is_empty(), + Self::GetBlocksByHeight(x) => x.is_empty(), + Self::GetHashes(x) => x.is_empty(), + Self::GetOutputIndexes(x) => x.is_empty(), + Self::GetOuts(x) => x.is_empty(), + Self::GetTransactionPoolHashes(x) => x.is_empty(), + Self::GetOutputDistribution(x) => x.is_empty(), } } } diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index d2e80f2d2..9426fc083 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -19,7 +19,7 @@ use crate::{ GetMinerDataTxBacklogEntry, HardforkEntry, HistogramEntry, OutputDistributionData, SetBan, Span, Status, SyncInfoPeer, TxBacklogEntry, }, - rpc_call::RpcCall, + rpc_call::RpcCallValue, }; //---------------------------------------------------------------------------------------------------- Macro @@ -222,7 +222,7 @@ define_request_and_response! { get_block_count, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 919..=933, - GetBlockCount, + GetBlockCount (empty), // There are no request fields specified, // this will cause the macro to generate a @@ -637,7 +637,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1734..=1754, - GetConnections (restricted), + GetConnections (restricted, empty), Request {}, @@ -712,7 +712,7 @@ define_request_and_response! { get_info, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 693..=789, - GetInfo, + GetInfo (empty), Request {}, #[doc = serde_doc_test!( @@ -806,7 +806,7 @@ define_request_and_response! { hard_fork_info, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1958..=1995, - HardForkInfo, + HardForkInfo (empty), Request {}, #[doc = serde_doc_test!( @@ -867,7 +867,7 @@ define_request_and_response! { get_bans, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1997..=2030, - GetBans (restricted), + GetBans (restricted, empty), Request {}, #[doc = serde_doc_test!( @@ -1032,7 +1032,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2170..=2211, - GetVersion, + GetVersion (empty), Request {}, #[doc = serde_doc_test!( @@ -1123,7 +1123,7 @@ define_request_and_response! { get_fee_estimate, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2250..=2277, - GetFeeEstimate, + GetFeeEstimate (empty), Request {}, #[doc = serde_doc_test!( @@ -1145,7 +1145,7 @@ define_request_and_response! { get_alternate_chains, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2279..=2310, - GetAlternateChains (restricted), + GetAlternateChains (restricted, empty), Request {}, #[doc = serde_doc_test!( @@ -1212,7 +1212,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 2383..=2443, - SyncInfo (restricted), + SyncInfo (restricted, empty), Request {}, @@ -1302,7 +1302,7 @@ define_request_and_response! { get_txpool_backlog, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1637..=1664, - GetTransactionPoolBacklog, + GetTransactionPoolBacklog (empty), Request {}, // TODO: enable test after binary string impl. @@ -1369,7 +1369,7 @@ define_request_and_response! { get_miner_data, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 996..=1044, - GetMinerData, + GetMinerData (empty), Request {}, #[doc = serde_doc_test!( @@ -1597,44 +1597,74 @@ pub enum JsonRpcRequest { GetTxIdsLoose(GetTxIdsLooseRequest), } -impl RpcCall for JsonRpcRequest { +impl RpcCallValue for JsonRpcRequest { fn is_restricted(&self) -> bool { match self { - // Normal methods. These are allowed - // even on restricted RPC servers (18089). - Self::GetBlockCount(_) - | Self::OnGetBlockHash(_) - | Self::SubmitBlock(_) - | Self::GetLastBlockHeader(_) - | Self::GetBlockHeaderByHash(_) - | Self::GetBlockHeaderByHeight(_) - | Self::GetBlockHeadersRange(_) - | Self::GetBlock(_) - | Self::GetInfo(_) - | Self::HardForkInfo(_) - | Self::GetOutputHistogram(_) - | Self::GetVersion(_) - | Self::GetFeeEstimate(_) - | Self::GetTransactionPoolBacklog(_) - | Self::GetMinerData(_) - | Self::AddAuxPow(_) - | Self::GetTxIdsLoose(_) => false, - - // Restricted methods. These are only allowed - // for unrestricted RPC servers (18081). - Self::GenerateBlocks(_) - | Self::GetConnections(_) - | Self::SetBans(_) - | Self::GetBans(_) - | Self::Banned(_) - | Self::FlushTransactionPool(_) - | Self::GetCoinbaseTxSum(_) - | Self::GetAlternateChains(_) - | Self::RelayTx(_) - | Self::SyncInfo(_) - | Self::PruneBlockchain(_) - | Self::CalcPow(_) - | Self::FlushCache(_) => true, + Self::GetBlockCount(x) => x.is_restricted(), + Self::OnGetBlockHash(x) => x.is_restricted(), + Self::SubmitBlock(x) => x.is_restricted(), + Self::GetLastBlockHeader(x) => x.is_restricted(), + Self::GetBlockHeaderByHash(x) => x.is_restricted(), + Self::GetBlockHeaderByHeight(x) => x.is_restricted(), + Self::GetBlockHeadersRange(x) => x.is_restricted(), + Self::GetBlock(x) => x.is_restricted(), + Self::GetInfo(x) => x.is_restricted(), + Self::HardForkInfo(x) => x.is_restricted(), + Self::GetOutputHistogram(x) => x.is_restricted(), + Self::GetVersion(x) => x.is_restricted(), + Self::GetFeeEstimate(x) => x.is_restricted(), + Self::GetTransactionPoolBacklog(x) => x.is_restricted(), + Self::GetMinerData(x) => x.is_restricted(), + Self::AddAuxPow(x) => x.is_restricted(), + Self::GetTxIdsLoose(x) => x.is_restricted(), + Self::GenerateBlocks(x) => x.is_restricted(), + Self::GetConnections(x) => x.is_restricted(), + Self::SetBans(x) => x.is_restricted(), + Self::GetBans(x) => x.is_restricted(), + Self::Banned(x) => x.is_restricted(), + Self::FlushTransactionPool(x) => x.is_restricted(), + Self::GetCoinbaseTxSum(x) => x.is_restricted(), + Self::GetAlternateChains(x) => x.is_restricted(), + Self::RelayTx(x) => x.is_restricted(), + Self::SyncInfo(x) => x.is_restricted(), + Self::PruneBlockchain(x) => x.is_restricted(), + Self::CalcPow(x) => x.is_restricted(), + Self::FlushCache(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetBlockCount(x) => x.is_empty(), + Self::OnGetBlockHash(x) => x.is_empty(), + Self::SubmitBlock(x) => x.is_empty(), + Self::GetLastBlockHeader(x) => x.is_empty(), + Self::GetBlockHeaderByHash(x) => x.is_empty(), + Self::GetBlockHeaderByHeight(x) => x.is_empty(), + Self::GetBlockHeadersRange(x) => x.is_empty(), + Self::GetBlock(x) => x.is_empty(), + Self::GetInfo(x) => x.is_empty(), + Self::HardForkInfo(x) => x.is_empty(), + Self::GetOutputHistogram(x) => x.is_empty(), + Self::GetVersion(x) => x.is_empty(), + Self::GetFeeEstimate(x) => x.is_empty(), + Self::GetTransactionPoolBacklog(x) => x.is_empty(), + Self::GetMinerData(x) => x.is_empty(), + Self::AddAuxPow(x) => x.is_empty(), + Self::GetTxIdsLoose(x) => x.is_empty(), + Self::GenerateBlocks(x) => x.is_empty(), + Self::GetConnections(x) => x.is_empty(), + Self::SetBans(x) => x.is_empty(), + Self::GetBans(x) => x.is_empty(), + Self::Banned(x) => x.is_empty(), + Self::FlushTransactionPool(x) => x.is_empty(), + Self::GetCoinbaseTxSum(x) => x.is_empty(), + Self::GetAlternateChains(x) => x.is_empty(), + Self::RelayTx(x) => x.is_empty(), + Self::SyncInfo(x) => x.is_empty(), + Self::PruneBlockchain(x) => x.is_empty(), + Self::CalcPow(x) => x.is_empty(), + Self::FlushCache(x) => x.is_empty(), } } } diff --git a/rpc/types/src/lib.rs b/rpc/types/src/lib.rs index 0fc2c3f80..b48f22ed8 100644 --- a/rpc/types/src/lib.rs +++ b/rpc/types/src/lib.rs @@ -128,4 +128,4 @@ pub use constants::{ CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR, }; -pub use rpc_call::RpcCall; +pub use rpc_call::{RpcCall, RpcCallValue}; diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index b39c2ae1d..0a8db1c9d 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -60,7 +60,7 @@ macro_rules! define_request_and_response { // Attributes added here will apply to _both_ // request and response types. $( #[$type_attr:meta] )* - $type_name:ident $(($restricted:ident))?, + $type_name:ident $(($restricted:ident $(, $empty:ident)?))?, // The request type (and any doc comments, derives, etc). $( #[$request_type_attr:meta] )* @@ -100,7 +100,7 @@ macro_rules! define_request_and_response { $( #[$type_attr] )* /// $( #[$request_type_attr] )* - [<$type_name Request>] $(($restricted))? { + [<$type_name Request>] $(($restricted $(, $empty)?))? { $( $( #[$request_field_attr] )* $request_field: $request_field_type @@ -145,21 +145,55 @@ pub(crate) use define_request_and_response; /// TODO macro_rules! impl_rpc_call { // TODO - ($t:ident, $restricted:ident) => { + ($t:ident, restricted, empty) => { impl $crate::RpcCall for $t { - fn is_restricted(&self) -> bool { - true + const IS_RESTRICTED: bool = true; + const IS_EMPTY: bool = true; + } + + impl From<()> for $t { + fn from(_: ()) -> Self { + Self {} } } + + impl From<$t> for () { + fn from(_: $t) -> Self {} + } }; // TODO - ($t:ident) => { + ($t:ident, empty) => { impl $crate::RpcCall for $t { - fn is_restricted(&self) -> bool { - false + const IS_RESTRICTED: bool = false; + const IS_EMPTY: bool = true; + } + + impl From<()> for $t { + fn from(_: ()) -> Self { + Self {} } } + + impl From<$t> for () { + fn from(_: $t) -> Self {} + } + }; + + // TODO + ($t:ident, restricted) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = true; + const IS_EMPTY: bool = false; + } + }; + + // TODO + ($t:ident) => { + impl $crate::RpcCall for $t { + const IS_RESTRICTED: bool = false; + const IS_EMPTY: bool = false; + } }; } pub(crate) use impl_rpc_call; @@ -175,7 +209,7 @@ macro_rules! define_request { // Any doc comments, derives, etc. $( #[$attr:meta] )* // The response type. - $t:ident $(($restricted:ident))? { + $t:ident $(($restricted:ident $(, $empty:ident)?))? { // And any fields. $( $( #[$field_attr:meta] )* // field attributes @@ -201,7 +235,7 @@ macro_rules! define_request { )* } - $crate::macros::impl_rpc_call!($t $(, $restricted)?); + $crate::macros::impl_rpc_call!($t $(, $restricted $(, $empty)?)?); #[cfg(feature = "epee")] ::cuprate_epee_encoding::epee_object! { diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 8b06171c9..cf9132cfe 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -15,6 +15,7 @@ use crate::{ TxEntry, TxInfo, TxpoolStats, }, rpc_call::RpcCall, + RpcCallValue, }; //---------------------------------------------------------------------------------------------------- Macro @@ -97,7 +98,7 @@ define_request_and_response! { get_height, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 138..=160, - GetHeight, + GetHeight (empty), Request {}, #[doc = serde_doc_test!( @@ -150,7 +151,7 @@ define_request_and_response! { get_alt_blocks_hashes, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 288..=308, - GetAltBlocksHashes, + GetAltBlocksHashes (empty), Request {}, #[doc = serde_doc_test!( @@ -291,7 +292,7 @@ define_request_and_response! { stop_mining, cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 825..=843, - StopMining (restricted), + StopMining (restricted, empty), Request {}, #[doc = serde_doc_test!( @@ -560,7 +561,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1569..=1591, - GetTransactionPool, + GetTransactionPool (empty), Request {}, #[doc = serde_doc_test!(GET_TRANSACTION_POOL_RESPONSE)] @@ -575,7 +576,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1712..=1732, - GetTransactionPoolStats, + GetTransactionPoolStats (empty), Request {}, #[doc = serde_doc_test!( @@ -619,7 +620,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1814..=1831, - StopDaemon (restricted), + StopDaemon (restricted, empty), Request {}, #[doc = serde_doc_test!( @@ -637,7 +638,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1852..=1874, - GetLimit, + GetLimit (empty), Request {}, #[doc = serde_doc_test!( @@ -733,7 +734,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 793..=822, - GetNetStats (restricted), + GetNetStats (restricted, empty), Request {}, #[doc = serde_doc_test!( @@ -872,7 +873,7 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 1615..=1635, - GetTransactionPoolHashes, + GetTransactionPoolHashes (empty), Request {}, #[doc = serde_doc_test!( @@ -987,42 +988,68 @@ pub enum OtherRequest { GetPublicNodes(GetPublicNodesRequest), } -impl RpcCall for OtherRequest { +impl RpcCallValue for OtherRequest { fn is_restricted(&self) -> bool { match self { - // Normal methods. These are allowed - // even on restricted RPC servers (18089). - Self::GetHeight(_) - | Self::GetTransactions(_) - | Self::GetAltBlocksHashes(_) - | Self::IsKeyImageSpent(_) - | Self::SendRawTransaction(_) - | Self::GetTransactionPool(_) - | Self::GetTransactionPoolStats(_) - | Self::GetLimit(_) - | Self::GetOuts(_) - | Self::GetTransactionPoolHashes(_) => false, - - // Restricted methods. These are only allowed - // for unrestricted RPC servers (18081). - // TODO - Self::StartMining(_) - | Self::StopMining(_) - | Self::MiningStatus(_) - | Self::SaveBc(_) - | Self::GetPeerList(_) - | Self::SetLogHashRate(_) - | Self::SetLogLevel(_) - | Self::SetLogCategories(_) - | Self::SetBootstrapDaemon(_) - | Self::GetNetStats(_) - | Self::SetLimit(_) - | Self::StopDaemon(_) - | Self::OutPeers(_) - | Self::InPeers(_) - | Self::Update(_) - | Self::PopBlocks(_) - | Self::GetPublicNodes(_) => true, + Self::GetHeight(x) => x.is_restricted(), + Self::GetTransactions(x) => x.is_restricted(), + Self::GetAltBlocksHashes(x) => x.is_restricted(), + Self::IsKeyImageSpent(x) => x.is_restricted(), + Self::SendRawTransaction(x) => x.is_restricted(), + Self::StartMining(x) => x.is_restricted(), + Self::StopMining(x) => x.is_restricted(), + Self::MiningStatus(x) => x.is_restricted(), + Self::SaveBc(x) => x.is_restricted(), + Self::GetPeerList(x) => x.is_restricted(), + Self::SetLogHashRate(x) => x.is_restricted(), + Self::SetLogLevel(x) => x.is_restricted(), + Self::SetLogCategories(x) => x.is_restricted(), + Self::SetBootstrapDaemon(x) => x.is_restricted(), + Self::GetTransactionPool(x) => x.is_restricted(), + Self::GetTransactionPoolStats(x) => x.is_restricted(), + Self::StopDaemon(x) => x.is_restricted(), + Self::GetLimit(x) => x.is_restricted(), + Self::SetLimit(x) => x.is_restricted(), + Self::OutPeers(x) => x.is_restricted(), + Self::InPeers(x) => x.is_restricted(), + Self::GetNetStats(x) => x.is_restricted(), + Self::GetOuts(x) => x.is_restricted(), + Self::Update(x) => x.is_restricted(), + Self::PopBlocks(x) => x.is_restricted(), + Self::GetTransactionPoolHashes(x) => x.is_restricted(), + Self::GetPublicNodes(x) => x.is_restricted(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::GetHeight(x) => x.is_empty(), + Self::GetTransactions(x) => x.is_empty(), + Self::GetAltBlocksHashes(x) => x.is_empty(), + Self::IsKeyImageSpent(x) => x.is_empty(), + Self::SendRawTransaction(x) => x.is_empty(), + Self::StartMining(x) => x.is_empty(), + Self::StopMining(x) => x.is_empty(), + Self::MiningStatus(x) => x.is_empty(), + Self::SaveBc(x) => x.is_empty(), + Self::GetPeerList(x) => x.is_empty(), + Self::SetLogHashRate(x) => x.is_empty(), + Self::SetLogLevel(x) => x.is_empty(), + Self::SetLogCategories(x) => x.is_empty(), + Self::SetBootstrapDaemon(x) => x.is_empty(), + Self::GetTransactionPool(x) => x.is_empty(), + Self::GetTransactionPoolStats(x) => x.is_empty(), + Self::StopDaemon(x) => x.is_empty(), + Self::GetLimit(x) => x.is_empty(), + Self::SetLimit(x) => x.is_empty(), + Self::OutPeers(x) => x.is_empty(), + Self::InPeers(x) => x.is_empty(), + Self::GetNetStats(x) => x.is_empty(), + Self::GetOuts(x) => x.is_empty(), + Self::Update(x) => x.is_empty(), + Self::PopBlocks(x) => x.is_empty(), + Self::GetTransactionPoolHashes(x) => x.is_empty(), + Self::GetPublicNodes(x) => x.is_empty(), } } } diff --git a/rpc/types/src/rpc_call.rs b/rpc/types/src/rpc_call.rs index 60dc08923..2f4b18838 100644 --- a/rpc/types/src/rpc_call.rs +++ b/rpc/types/src/rpc_call.rs @@ -5,6 +5,34 @@ //---------------------------------------------------------------------------------------------------- Struct definitions /// TODO pub trait RpcCall { + /// Is `true` if this RPC method should + /// only be allowed on local servers. + /// + /// If this is `false`, it should be + /// okay to execute the method even on restricted + /// RPC servers. + /// + /// ```rust + /// use cuprate_rpc_types::{ + /// RpcCall, + /// json::{GetBlockCountRequest, GetConnectionsRequest}, + /// }; + /// + /// // Allowed method, even on restricted RPC servers (18089). + /// assert_eq!(GetBlockCountRequest::IS_RESTRICTED.is_some_and(|x| !x)); + /// + /// // Restricted methods, only allowed + /// // for unrestricted RPC servers (18081). + /// assert_eq!(GetConnectionsRequest::IS_RESTRICTED.is_some_and(|x| x)); + /// ``` + const IS_RESTRICTED: bool; + + /// TODO + const IS_EMPTY: bool; +} + +/// TODO +pub trait RpcCallValue { /// Returns `true` if this RPC method should /// only be allowed on local servers. /// @@ -19,13 +47,28 @@ pub trait RpcCall { /// }; /// /// // Allowed method, even on restricted RPC servers (18089). - /// assert_eq!(GetBlockCountRequest::default().is_restricted(), false); + /// assert_eq!(GetBlockCountRequest::IS_RESTRICTED.is_some_and(|x| !x)); /// /// // Restricted methods, only allowed /// // for unrestricted RPC servers (18081). - /// assert_eq!(GetConnectionsRequest::default().is_restricted(), true); + /// assert_eq!(GetConnectionsRequest::IS_RESTRICTED.is_some_and(|x| x)); /// ``` fn is_restricted(&self) -> bool; + + /// TODO + fn is_empty(&self) -> bool; +} + +impl RpcCallValue for T { + #[inline] + fn is_restricted(&self) -> bool { + Self::IS_RESTRICTED + } + + #[inline] + fn is_empty(&self) -> bool { + Self::IS_EMPTY + } } //---------------------------------------------------------------------------------------------------- Tests From 82301bfa78fef0d9218578913a46635aad00b312 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 29 Jul 2024 20:48:27 -0400 Subject: [PATCH 77/93] interface: `RpcHandlerDummy` docs --- rpc/interface/src/rpc_handler_dummy.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index 0f2285801..60b390b2d 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -1,4 +1,4 @@ -//! TODO +//! Dummy implementation of [`RpcHandler`]. //---------------------------------------------------------------------------------------------------- Use use std::task::Poll; @@ -16,10 +16,18 @@ use crate::{ }; //---------------------------------------------------------------------------------------------------- TODO -/// TODO +/// An [`RpcHandler`] that always returns [`Default::default`]. +/// +/// This `struct` implements [`RpcHandler`], and always responds +/// with the response `struct` set to [`Default::default`]. +/// +/// TODO: test asserting `json_rpc` id, response, etc. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RpcHandlerDummy { - /// TODO + /// Should this RPC server be restricted? + /// + /// The dummy will honor this [`bool`] + /// on restricted methods/endpoints. pub restricted: bool, } From c96b0502b59fab33932335966e8af4227a5fece5 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 30 Jul 2024 20:03:02 -0400 Subject: [PATCH 78/93] interface: crate docs --- rpc/interface/Cargo.toml | 11 +-- rpc/interface/README.md | 116 +++++++++++++++++-------- rpc/interface/src/constants.rs | 11 --- rpc/interface/src/free.rs | 7 +- rpc/interface/src/lib.rs | 2 - rpc/interface/src/macros.rs | 3 - rpc/interface/src/route/bin.rs | 2 +- rpc/interface/src/route/fallback.rs | 2 +- rpc/interface/src/route/json.rs | 2 +- rpc/interface/src/route/mod.rs | 2 +- rpc/interface/src/route/other.rs | 2 +- rpc/interface/src/rpc_error.rs | 15 +++- rpc/interface/src/rpc_handler.rs | 36 ++++++-- rpc/interface/src/rpc_handler_dummy.rs | 9 +- rpc/interface/src/rpc_request.rs | 19 ++-- rpc/interface/src/rpc_response.rs | 19 ++-- rpc/types/src/json.rs | 7 +- 17 files changed, 174 insertions(+), 91 deletions(-) delete mode 100644 rpc/interface/src/constants.rs delete mode 100644 rpc/interface/src/macros.rs diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index a778b3077..925c4ab5d 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -18,11 +18,12 @@ cuprate-json-rpc = { path = "../json-rpc", default-features = false } cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false } cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } -axum = { version = "0.7.5", features = ["json"], default-features = false } -serde = { workspace = true } -tower = { workspace = true } -paste = { workspace = true } -futures = { workspace = true } +axum = { version = "0.7.5", features = ["json"], default-features = false } +serde = { workspace = true } +serde_json = { workspace = true, features = ["std"] } +tower = { workspace = true } +paste = { workspace = true } +futures = { workspace = true } [dev-dependencies] axum = { version = "0.7.5", features = ["json", "tokio", "http2"] } diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 756641294..7d4369824 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -1,6 +1,8 @@ # `cuprate-rpc-interface` This crate provides Cuprate's RPC _interface_. +This crate is _not_ a standalone RPC server, it is just the interface. + ```text cuprate-rpc-interface provides these parts │ │ @@ -20,7 +22,7 @@ This is where your [`RpcHandler`] turns this [`RpcRequest`] into a [`RpcResponse You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client. The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate; -it implements the regular RPC server modeled after `monerod`. +it implements the standard RPC handlers modeled after `monerod`. # Purpose `cuprate-rpc-interface` is built on-top of [`axum`], @@ -28,14 +30,15 @@ which is the crate _actually_ handling everything. This crate simply handles: - Registering endpoint routes (e.g. `/get_block.bin`) -- Handler function signatures -- (De)serialization of data (JSON-RPC methods/params, binary, JSON) +- Defining handler function signatures +- (De)serialization of requests/responses (JSON-RPC, binary, JSON) + +The actual server details are all handled by the [`axum`] and [`tower`] ecosystem. -The actual server details are all handled by the [`axum`] and [`tower`] ecosystem, i.e. -the whole purpose of this crate is to: +The proper usage of this crate is to: 1. Implement a [`RpcHandler`] 2. Use it with [`create_router`] to generate an - [`axum::Router`] with all Monero RPC routes/types set + [`axum::Router`] with all Monero RPC routes set 4. Do whatever with it # The [`RpcHandler`] @@ -53,55 +56,94 @@ for RPC server operation. The only state currently need is [`RpcHandler::restricted`], which determines if an RPC server is restricted or not, and thus, if some endpoints/methods are allowed or not. +# Unknown endpoint behavior +TODO: decide what this crate should return (per different endpoint) +when a request is received to an unknown endpoint, including HTTP stuff, e.g. status code. + +# Unknown JSON-RPC method behavior +TODO: decide what this crate returns when a `/json_rpc` +request is received with an unknown method, including HTTP stuff, e.g. status code. + # Example Example usage of this crate + starting an RPC server. -This uses `RpcHandlerDummy` as the handler; it responds with the -correct response, but always with a default value. +This uses `RpcHandlerDummy` as the handler; it always responds with the +correct response type, but set to a default value regardless of the request. ```rust use std::sync::Arc; -use axum::{extract::Request, response::Response}; use tokio::{net::TcpListener, sync::Barrier}; -use cuprate_rpc_types::other::{OtherRequest, OtherResponse}; +use cuprate_json_rpc::{Request, Response, Id}; +use cuprate_rpc_types::{ + json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse}, + other::{OtherRequest, OtherResponse}, +}; use cuprate_rpc_interface::{create_router, RpcHandlerDummy, RpcRequest}; -#[tokio::main] -async fn main() { - // Create the router. - let state = RpcHandlerDummy { restricted: false }; - let router = create_router().with_state(state); - - // Start a server. - let listener = TcpListener::bind("127.0.0.1:0") - .await - .unwrap(); - let port = listener.local_addr().unwrap().port(); - - // Run the server with `axum`. - let barrier = Arc::new(Barrier::new(2)); - let barrier2 = barrier.clone(); - tokio::task::spawn(async move { - barrier2.wait(); - axum::serve(listener, router).await.unwrap(); - }); - barrier.wait(); - - // Send a request. This endpoint has no inputs. +// Send a `/get_height` request. This endpoint has no inputs. +async fn get_height(port: u16) -> OtherResponse { let url = format!("http://127.0.0.1:{port}/get_height"); - let body: OtherResponse = ureq::get(&url) + ureq::get(&url) .set("Content-Type", "application/json") .call() .unwrap() .into_json() - .unwrap(); + .unwrap() +} - // Assert the response is as expected. - // We just made an RPC call :) +// Send a JSON-RPC request with the `get_block_count` method. +// +// The returned [`String`] is JSON. +async fn get_block_count(port: u16) -> String { + let url = format!("http://127.0.0.1:{port}/json_rpc"); + let method = JsonRpcRequest::GetBlockCount(Default::default()); + let request = Request::new(method); + ureq::get(&url) + .set("Content-Type", "application/json") + .send_json(request) + .unwrap() + .into_string() + .unwrap() +} + +#[tokio::main] +async fn main() { + // Start a local RPC server. + let port = { + // Create the router. + let state = RpcHandlerDummy { restricted: false }; + let router = create_router().with_state(state); + + // Start a server. + let listener = TcpListener::bind("127.0.0.1:0") + .await + .unwrap(); + let port = listener.local_addr().unwrap().port(); + + // Run the server with `axum`. + tokio::task::spawn(async move { + axum::serve(listener, router).await.unwrap(); + }); + + port + }; + + // Assert the response is the default. + let response = get_height(port).await; let expected = OtherResponse::GetHeight(Default::default()); - assert_eq!(body, expected); + assert_eq!(response, expected); + + // Assert the response JSON is correct. + let response = get_block_count(port).await; + let expected = r#"{"jsonrpc":"2.0","id":null,"result":{"status":"OK","untrusted":false,"count":0}}"#; + assert_eq!(response, expected); + + // Assert that (de)serialization works. + let expected = Response::ok(Id::Null, Default::default()); + let response: Response = serde_json::from_str(&response).unwrap(); + assert_eq!(response, expected); } ``` diff --git a/rpc/interface/src/constants.rs b/rpc/interface/src/constants.rs deleted file mode 100644 index 668b3b201..000000000 --- a/rpc/interface/src/constants.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! TODO - -//---------------------------------------------------------------------------------------------------- Import - -//---------------------------------------------------------------------------------------------------- Status - -//---------------------------------------------------------------------------------------------------- Tests -#[cfg(test)] -mod test { - // use super::*; -} diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs index 090485ba5..295e15318 100644 --- a/rpc/interface/src/free.rs +++ b/rpc/interface/src/free.rs @@ -9,12 +9,13 @@ use crate::{ }; //---------------------------------------------------------------------------------------------------- Router -/// Create the RPC router. +/// Create the RPC [`axum::Router`]. /// -/// TODO +/// This creates an [`axum::Router`] with all Monero RPC routes +/// registered and handled by your [`RpcHandler`] of choice. /// /// # Routes -/// List of `monerod` routes, [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189). +/// For a list of all `monerod` routes, [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189), or the source code of this function. #[rustfmt::skip] pub fn create_router() -> Router { Router::new() diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index fa082a0da..1201175b3 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -105,9 +105,7 @@ #![allow(dead_code, unreachable_code, clippy::diverging_sub_expression)] //---------------------------------------------------------------------------------------------------- Mod -mod constants; mod free; -mod macros; mod route; mod rpc_error; mod rpc_handler; diff --git a/rpc/interface/src/macros.rs b/rpc/interface/src/macros.rs deleted file mode 100644 index 0c939696b..000000000 --- a/rpc/interface/src/macros.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Macros. - -//---------------------------------------------------------------------------------------------------- TODO diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index c516c6260..f40eb107b 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -1,4 +1,4 @@ -//! TODO +//! Binary route functions. #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import diff --git a/rpc/interface/src/route/fallback.rs b/rpc/interface/src/route/fallback.rs index bce22ee8f..8ee0e7473 100644 --- a/rpc/interface/src/route/fallback.rs +++ b/rpc/interface/src/route/fallback.rs @@ -1,4 +1,4 @@ -//! TODO +//! Fallback route functions. #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index 9286bdf62..ce15c43e8 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -1,4 +1,4 @@ -//! TODO +//! JSON-RPC 2.0 endpoint route functions. #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 89018c51f..85c5b77fe 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,4 +1,4 @@ -//! TODO +//! Routing functions. pub(crate) mod bin; pub(crate) mod fallback; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 3c3030e0c..41f37410b 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -1,4 +1,4 @@ -//! TODO +//! Other JSON endpoint route functions. #![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import diff --git a/rpc/interface/src/rpc_error.rs b/rpc/interface/src/rpc_error.rs index 17f611d40..b5e32d5e3 100644 --- a/rpc/interface/src/rpc_error.rs +++ b/rpc/interface/src/rpc_error.rs @@ -1,10 +1,19 @@ -//! TODO +//! RPC errors. //---------------------------------------------------------------------------------------------------- Import use axum::http::StatusCode; -//---------------------------------------------------------------------------------------------------- TODO -/// TODO +//---------------------------------------------------------------------------------------------------- RpcError +/// Possible errors during RPC operation. +/// +/// These are any errors that can happen _during_ a handler function. +/// I.e. if this error surfaces, it happened _after_ the request was +/// deserialized. +/// +/// This is the `Error` type required to be used in an [`RpcHandler`](crate::RpcHandler). +/// +/// TODO: This is empty as possible errors will be +/// enumerated when the handler functions are created. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum RpcError {} diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 1a9285a92..8928d122c 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -1,8 +1,9 @@ -//! TODO +//! RPC handler trait. //---------------------------------------------------------------------------------------------------- Use -use std::task::Poll; +use std::{future::Future, task::Poll}; +use axum::{http::StatusCode, response::IntoResponse}; use futures::{channel::oneshot::channel, FutureExt}; use tower::Service; @@ -12,8 +13,26 @@ use cuprate_rpc_types::json::JsonRpcRequest; use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcResponse}; -//---------------------------------------------------------------------------------------------------- TODO -/// TODO +//---------------------------------------------------------------------------------------------------- RpcHandler +/// An RPC handler. +/// +/// This trait represents a type that can turn [`RpcRequest`]s into [`RpcResponse`]s. +/// +/// Implementors of this trait must be [`tower::Service`]s that use: +/// - [`RpcRequest`] as the generic `Request` type +/// - [`RpcResponse`] as the associated `Response` type +/// - [`RpcError`] as the associated `Error` type +/// - `InfallibleOneshotReceiver>` as the associated `Future` type +/// +/// See this crate's `RpcHandlerDummy` for an implementation example of this trait. +/// +/// # Panics +/// Your [`RpcHandler`] must reply to [`RpcRequest`]s with the correct +/// [`RpcResponse`] or else this crate will panic during routing functions. +/// +/// For example, upon a [`RpcRequest::Binary`] must be replied with +/// [`RpcRequest::Binary`]. If an [`RpcRequest::Other`] were returned instead, +/// this crate would panic. pub trait RpcHandler: Clone + Send @@ -26,6 +45,13 @@ pub trait RpcHandler: Future = InfallibleOneshotReceiver>, > { - /// TODO + /// Is this [`RpcHandler`] restricted? + /// + /// If this returns `true`, restricted methods and endpoints such as: + /// - `/json_rpc`'s `relay_tx` method + /// - The `/pop_blocks` endpoint + /// + /// will automatically be denied access when using the + /// [`axum::Router`] provided by [`crate::create_router`]. fn restricted(&self) -> bool; } diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index 60b390b2d..bfc324198 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -15,16 +15,19 @@ use crate::{ rpc_response::RpcResponse, }; -//---------------------------------------------------------------------------------------------------- TODO +//---------------------------------------------------------------------------------------------------- RpcHandlerDummy /// An [`RpcHandler`] that always returns [`Default::default`]. /// /// This `struct` implements [`RpcHandler`], and always responds /// with the response `struct` set to [`Default::default`]. /// -/// TODO: test asserting `json_rpc` id, response, etc. +/// See the [`crate`] documentation for example usage. +/// +/// This is mostly used for testing purposes and can +/// be disabled by disable the `dummy` feature flag. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RpcHandlerDummy { - /// Should this RPC server be restricted? + /// Should this RPC server be [restricted](RpcHandler::restricted)? /// /// The dummy will honor this [`bool`] /// on restricted methods/endpoints. diff --git a/rpc/interface/src/rpc_request.rs b/rpc/interface/src/rpc_request.rs index de81c7e16..d3abd07cf 100644 --- a/rpc/interface/src/rpc_request.rs +++ b/rpc/interface/src/rpc_request.rs @@ -1,16 +1,23 @@ -//! TODO +//! RPC requests. //---------------------------------------------------------------------------------------------------- Import use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest}; -//---------------------------------------------------------------------------------------------------- TODO -/// TODO +//---------------------------------------------------------------------------------------------------- RpcRequest +/// All possible RPC requests. +/// +/// This enum encapsulates all possible RPC requests: +/// - JSON RPC 2.0 requests +/// - Binary requests +/// - Other JSON requests +/// +/// It is the `Request` type required to be used in an [`RpcHandler`](crate::RpcHandler). pub enum RpcRequest { - /// TODO + /// JSON-RPC 2.0 requests. JsonRpc(cuprate_json_rpc::Request), - /// TODO + /// Binary requests. Binary(BinRequest), - /// TODO + /// Other JSON requests. Other(OtherRequest), } diff --git a/rpc/interface/src/rpc_response.rs b/rpc/interface/src/rpc_response.rs index c904ad069..0ff503602 100644 --- a/rpc/interface/src/rpc_response.rs +++ b/rpc/interface/src/rpc_response.rs @@ -1,16 +1,23 @@ -//! TODO +//! RPC responses. //---------------------------------------------------------------------------------------------------- Import use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse}; -//---------------------------------------------------------------------------------------------------- Status -/// TODO +//---------------------------------------------------------------------------------------------------- RpcResponse +/// All possible RPC responses. +/// +/// This enum encapsulates all possible RPC responses: +/// - JSON RPC 2.0 responses +/// - Binary responses +/// - Other JSON responses +/// +/// It is the `Response` type required to be used in an [`RpcHandler`](crate::RpcHandler). pub enum RpcResponse { - /// TODO + /// JSON RPC 2.0 responses. JsonRpc(cuprate_json_rpc::Response), - /// TODO + /// Binary responses. Binary(BinResponse), - /// TODO + /// Other JSON responses. Other(OtherResponse), } diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 9426fc083..82888cb32 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -1562,7 +1562,10 @@ define_request_and_response! { /// TODO #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[cfg_attr(feature = "serde", serde(untagged))] +#[cfg_attr( + feature = "serde", + serde(rename_all = "snake_case", tag = "method", content = "params") +)] #[allow(missing_docs)] pub enum JsonRpcRequest { GetBlockCount(GetBlockCountRequest), @@ -1673,7 +1676,7 @@ impl RpcCallValue for JsonRpcRequest { /// TODO #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[cfg_attr(feature = "serde", serde(untagged))] +#[cfg_attr(feature = "serde", serde(untagged, rename_all = "snake_case"))] #[allow(missing_docs)] pub enum JsonRpcResponse { GetBlockCount(GetBlockCountResponse), From 85eb23f50f9480d6cfd838fce4ec2d5c75e6a9f9 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Tue, 30 Jul 2024 20:59:16 -0400 Subject: [PATCH 79/93] replace `create_router` with `RouterBuilder` --- rpc/interface/README.md | 6 +- rpc/interface/src/free.rs | 72 ----------- rpc/interface/src/lib.rs | 4 +- rpc/interface/src/router_builder.rs | 183 ++++++++++++++++++++++++++++ rpc/interface/src/rpc_handler.rs | 2 +- 5 files changed, 189 insertions(+), 78 deletions(-) delete mode 100644 rpc/interface/src/free.rs create mode 100644 rpc/interface/src/router_builder.rs diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 7d4369824..6228eaeba 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -37,7 +37,7 @@ The actual server details are all handled by the [`axum`] and [`tower`] ecosyste The proper usage of this crate is to: 1. Implement a [`RpcHandler`] -2. Use it with [`create_router`] to generate an +2. Use it with [`RouterBuilder`] to generate an [`axum::Router`] with all Monero RPC routes set 4. Do whatever with it @@ -80,7 +80,7 @@ use cuprate_rpc_types::{ json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse}, other::{OtherRequest, OtherResponse}, }; -use cuprate_rpc_interface::{create_router, RpcHandlerDummy, RpcRequest}; +use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy, RpcRequest}; // Send a `/get_height` request. This endpoint has no inputs. async fn get_height(port: u16) -> OtherResponse { @@ -114,7 +114,7 @@ async fn main() { let port = { // Create the router. let state = RpcHandlerDummy { restricted: false }; - let router = create_router().with_state(state); + let router = RouterBuilder::new().all().build().with_state(state); // Start a server. let listener = TcpListener::bind("127.0.0.1:0") diff --git a/rpc/interface/src/free.rs b/rpc/interface/src/free.rs deleted file mode 100644 index 295e15318..000000000 --- a/rpc/interface/src/free.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Free functions. - -//---------------------------------------------------------------------------------------------------- Use -use axum::{routing::method_routing::get, Router}; - -use crate::{ - route::{bin, fallback, json, other}, - rpc_handler::RpcHandler, -}; - -//---------------------------------------------------------------------------------------------------- Router -/// Create the RPC [`axum::Router`]. -/// -/// This creates an [`axum::Router`] with all Monero RPC routes -/// registered and handled by your [`RpcHandler`] of choice. -/// -/// # Routes -/// For a list of all `monerod` routes, [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189), or the source code of this function. -#[rustfmt::skip] -pub fn create_router() -> Router { - Router::new() - // JSON-RPC route. - .route("/json_rpc", get(json::json_rpc::)) - // Other JSON routes. - .route("/get_height", get(other::get_height::)) - .route("/getheight", get(other::get_height::)) - .route("/get_transactions", get(other::get_transactions::)) - .route("/gettransactions", get(other::get_transactions::)) - .route("/get_alt_blocks_hashes", get(other::get_alt_blocks_hashes::)) - .route("/is_key_image_spent", get(other::is_key_image_spent::)) - .route("/send_raw_transaction", get(other::send_raw_transaction::)) - .route("/sendrawtransaction", get(other::send_raw_transaction::)) - .route("/start_mining", get(other::start_mining::)) - .route("/stop_mining", get(other::stop_mining::)) - .route("/mining_status", get(other::mining_status::)) - .route("/save_bc", get(other::save_bc::)) - .route("/get_peer_list", get(other::get_peer_list::)) - .route("/get_public_nodes", get(other::get_public_nodes::)) - .route("/set_log_hash_rate", get(other::set_log_hash_rate::)) - .route("/set_log_level", get(other::set_log_level::)) - .route("/set_log_categories", get(other::set_log_categories::)) - .route("/get_transaction_pool", get(other::get_transaction_pool::)) - .route("/get_transaction_pool_hashes", get(other::get_transaction_pool_hashes::)) - .route("/get_transaction_pool_stats", get(other::get_transaction_pool_stats::)) - .route("/set_bootstrap_daemon", get(other::set_bootstrap_daemon::)) - .route("/stop_daemon", get(other::stop_daemon::)) - .route("/get_net_stats", get(other::get_net_stats::)) - .route("/get_limit", get(other::get_limit::)) - .route("/set_limit", get(other::set_limit::)) - .route("/out_peers", get(other::out_peers::)) - .route("/in_peers", get(other::in_peers::)) - .route("/get_outs", get(other::get_outs::)) - .route("/update", get(other::update::)) - .route("/pop_blocks", get(other::pop_blocks::)) - // Binary routes. - .route("/get_blocks.bin", get(bin::get_blocks::)) - .route("/getblocks.bin", get(bin::get_blocks::)) - .route("/get_blocks_by_height.bin", get(bin::get_blocks_by_height::)) - .route("/getblocks_by_height.bin", get(bin::get_blocks_by_height::)) - .route("/get_hashes.bin", get(bin::get_hashes::)) - .route("/gethashes.bin", get(bin::get_hashes::)) - .route("/get_o_indexes.bin", get(bin::get_o_indexes::)) - .route("/get_outs.bin", get(bin::get_outs::)) - .route("/get_transaction_pool_hashes.bin", get(bin::get_transaction_pool_hashes::)) - .route("/get_output_distribution.bin", get(bin::get_output_distribution::)) - // Fallback route (catch-all). - // - // Deprecated routes will also route here, list: - // - `get_info` - // - `getinfo` - .fallback(fallback::fallback) -} diff --git a/rpc/interface/src/lib.rs b/rpc/interface/src/lib.rs index 1201175b3..2656b0746 100644 --- a/rpc/interface/src/lib.rs +++ b/rpc/interface/src/lib.rs @@ -105,8 +105,8 @@ #![allow(dead_code, unreachable_code, clippy::diverging_sub_expression)] //---------------------------------------------------------------------------------------------------- Mod -mod free; mod route; +mod router_builder; mod rpc_error; mod rpc_handler; #[cfg(feature = "dummy")] @@ -114,7 +114,7 @@ mod rpc_handler_dummy; mod rpc_request; mod rpc_response; -pub use free::create_router; +pub use router_builder::RouterBuilder; pub use rpc_error::RpcError; pub use rpc_handler::RpcHandler; #[cfg(feature = "dummy")] diff --git a/rpc/interface/src/router_builder.rs b/rpc/interface/src/router_builder.rs new file mode 100644 index 000000000..7055c660f --- /dev/null +++ b/rpc/interface/src/router_builder.rs @@ -0,0 +1,183 @@ +//! Free functions. + +use std::marker::PhantomData; + +//---------------------------------------------------------------------------------------------------- Use +use axum::{routing::method_routing::get, Router}; + +use crate::{ + route::{bin, fallback, json, other}, + rpc_handler::RpcHandler, +}; + +//---------------------------------------------------------------------------------------------------- RouterBuilder +/// Generate the `RouterBuilder` struct. +macro_rules! generate_router_builder { + ($( + // Syntax: + // $BUILDER_FUNCTION_NAME => $ACTUAL_ENDPOINT => $ENDPOINT_FUNCTION + $endpoint_ident:ident => $endpoint_string:literal => $endpoint_fn:expr + ),* $(,)?) => { + /// Builder for creating the RPC router. + /// + /// This builder allows you to selectively enable endpoints for the router, + /// and a [`fallback`](RouterBuilder::fallback) route. + /// + /// The [`default`](RouterBuilder::default) is to enable [`all`](RouterBuilder::all) routes. + /// + /// # Routes + /// Functions that enable routes are separated into 3 groups: + /// - `json_rpc` (enables all of JSON RPC 2.0) + /// - `other_` (e.g. [`other_get_height`](RouterBuilder::other_get_height)) + /// - `binary_` (e.g. [`binary_get_blocks`](RouterBuilder::binary_get_blocks)) + /// + /// For a list of all `monerod` routes, see + /// [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189), + /// or the source file of this type. + /// + /// # Aliases + /// Some routes have aliases, such as [`/get_height`](RouterBuilder::other_get_height) + /// and [`/getheight`](RouterBuilder::other_getheight). + /// + /// These both route to the same handler function, but they do not enable each other. + /// + /// If desired, you can enable `/get_height` but not `/getheight`. + /// + /// # Example + /// ```rust + /// use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy}; + /// + /// // Create a router with _only_ `/json_rpc` enabled. + /// let only_json_rpc = RouterBuilder::::new() + /// .json_rpc() + /// .build(); + /// + /// // Create a router with: + /// // - `/get_outs.bin` enabled + /// // - A fallback enabled + /// let get_outs_bin_and_fallback = RouterBuilder::::new() + /// .binary_get_outs() + /// .fallback() + /// .build(); + /// + /// // Create a router with all endpoints enabled. + /// let all = RouterBuilder::::new() + /// .all() + /// .build(); + /// ``` + #[allow(clippy::struct_excessive_bools)] + #[derive(Clone)] + pub struct RouterBuilder { + router: Router, + } + + impl RouterBuilder { + /// Create a new [`Self`]. + #[must_use] + pub fn new() -> Self { + Self { + router: Router::new(), + } + } + + /// Build [`Self`] into a [`Router`]. + /// + /// All endpoints enabled in [`RouterBuilder`] + /// will be enabled in this [`Router`]. + pub fn build(self) -> Router { + self.router + } + + /// Enable all endpoints, including [`Self::fallback`]. + #[must_use] + pub fn all(mut self) -> Self { + $( + self = self.$endpoint_ident(); + )* + + self.fallback() + } + + /// Enable the catch-all fallback route. + /// + /// Any unknown or disabled route will route here, e.g.: + /// - `get_info` + /// - `getinfo` + /// - `asdf` + #[must_use] + pub fn fallback(self) -> Self { + Self { + router: self.router.fallback(fallback::fallback), + } + } + + $( + #[doc = concat!( + "Enable the `", + $endpoint_string, + "` endpoint.", + )] + #[must_use] + pub fn $endpoint_ident(self) -> Self { + Self { + router: self.router.route($endpoint_string, $endpoint_fn), + } + } + )* + } + }; +} + +generate_router_builder! { + // JSON-RPC 2.0 route. + json_rpc => "/json_rpc" => get(json::json_rpc::), + // Other JSON routes. + other_get_height => "/get_height" => get(other::get_height::), + other_getheight => "/getheight" => get(other::get_height::), + other_get_transactions => "/get_transactions" => get(other::get_transactions::), + other_gettransactions => "/gettransactions" => get(other::get_transactions::), + other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => get(other::get_alt_blocks_hashes::), + other_is_key_image_spent => "/is_key_image_spent" => get(other::is_key_image_spent::), + other_send_raw_transaction => "/send_raw_transaction" => get(other::send_raw_transaction::), + other_sendrawtransaction => "/sendrawtransaction" => get(other::send_raw_transaction::), + other_start_mining => "/start_mining" => get(other::start_mining::), + other_stop_mining => "/stop_mining" => get(other::stop_mining::), + other_mining_status => "/mining_status" => get(other::mining_status::), + other_save_bc => "/save_bc" => get(other::save_bc::), + other_get_peer_list => "/get_peer_list" => get(other::get_peer_list::), + other_get_public_nodes => "/get_public_nodes" => get(other::get_public_nodes::), + other_set_log_hash_rate => "/set_log_hash_rate" => get(other::set_log_hash_rate::), + other_set_log_level => "/set_log_level" => get(other::set_log_level::), + other_set_log_categories => "/set_log_categories" => get(other::set_log_categories::), + other_get_transaction_pool => "/get_transaction_pool" => get(other::get_transaction_pool::), + other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => get(other::get_transaction_pool_hashes::), + other_get_transaction_pool_stats => "/get_transaction_pool_stats" => get(other::get_transaction_pool_stats::), + other_set_bootstrap_daemon => "/set_bootstrap_daemon" => get(other::set_bootstrap_daemon::), + other_stop_daemon => "/stop_daemon" => get(other::stop_daemon::), + other_get_net_stats => "/get_net_stats" => get(other::get_net_stats::), + other_get_limit => "/get_limit" => get(other::get_limit::), + other_set_limit => "/set_limit" => get(other::set_limit::), + other_out_peers => "/out_peers" => get(other::out_peers::), + other_in_peers => "/in_peers" => get(other::in_peers::), + other_get_outs => "/get_outs" => get(other::get_outs::), + other_update => "/update" => get(other::update::), + other_pop_blocks => "/pop_blocks" => get(other::pop_blocks::), + // Binary routes. + binary_get_blocks => "/get_blocks.bin" => get(bin::get_blocks::), + binary_getblocks => "/getblocks.bin" => get(bin::get_blocks::), + binary_get_blocks_by_height => "/get_blocks_by_height.bin" => get(bin::get_blocks_by_height::), + binary_getblocks_by_height => "/getblocks_by_height.bin" => get(bin::get_blocks_by_height::), + binary_get_hashes => "/get_hashes.bin" => get(bin::get_hashes::), + binary_gethashes => "/gethashes.bin" => get(bin::get_hashes::), + binary_get_o_indexes => "/get_o_indexes.bin" => get(bin::get_o_indexes::), + binary_get_outs => "/get_outs.bin" => get(bin::get_outs::), + binary_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => get(bin::get_transaction_pool_hashes::), + binary_get_output_distribution => "/get_output_distribution.bin" => get(bin::get_output_distribution::), +} + +impl Default for RouterBuilder { + /// Uses [`Self::all`]. + fn default() -> Self { + Self::new().all() + } +} diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index 8928d122c..ccd000c76 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -52,6 +52,6 @@ pub trait RpcHandler: /// - The `/pop_blocks` endpoint /// /// will automatically be denied access when using the - /// [`axum::Router`] provided by [`crate::create_router`]. + /// [`axum::Router`] provided by [`RouterBuilder`](crate::RouterBuilder). fn restricted(&self) -> bool; } From fad423ba280afdae767bd327fa9841b4ae163144 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 31 Jul 2024 19:53:58 -0400 Subject: [PATCH 80/93] types: docs --- rpc/types/src/bin.rs | 8 +++-- rpc/types/src/json.rs | 12 ++++++- rpc/types/src/macros.rs | 23 +++++++++--- rpc/types/src/other.rs | 32 +++++++++++++++-- rpc/types/src/rpc_call.rs | 74 ++++++++++++++++++++++++--------------- 5 files changed, 111 insertions(+), 38 deletions(-) diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 65fd5caaa..ed0229d2c 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -395,7 +395,9 @@ impl EpeeObject for GetBlocksResponse { } //---------------------------------------------------------------------------------------------------- Request -/// TODO +/// Binary requests. +/// +/// This enum contains all [`crate::bin`] requests. #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] @@ -437,7 +439,9 @@ impl RpcCallValue for BinRequest { } //---------------------------------------------------------------------------------------------------- Response -/// TODO +/// Binary responses. +/// +/// This enum contains all [`crate::bin`] responses. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 82888cb32..320c59048 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -97,7 +97,17 @@ define_request_and_response! { cc73fe71162d564ffda8e549b79a350bca53c454 => core_rpc_server_commands_defs.h => 943..=994, // The base type name. - GetBlockTemplate, + // + // After the type name, 2 optional idents are allowed: + // - `restricted` + // - `empty` + // + // These have to be within `()` and will affect the + // [`crate::RpcCall`] implementation on the request type. + // + // This type is not either restricted or empty so nothing is + // here, but the correct syntax is shown in a comment below: + GetBlockTemplate /* (restricted, empty) */, // The request type. // diff --git a/rpc/types/src/macros.rs b/rpc/types/src/macros.rs index 0a8db1c9d..60ffa90aa 100644 --- a/rpc/types/src/macros.rs +++ b/rpc/types/src/macros.rs @@ -60,6 +60,13 @@ macro_rules! define_request_and_response { // Attributes added here will apply to _both_ // request and response types. $( #[$type_attr:meta] )* + // After the type name, 2 optional idents are allowed: + // + // - `restricted` + // - `empty` + // + // These have to be within `()` and will affect the + // [`crate::RpcCall`] implementation on the request type. $type_name:ident $(($restricted:ident $(, $empty:ident)?))?, // The request type (and any doc comments, derives, etc). @@ -142,9 +149,15 @@ macro_rules! define_request_and_response { pub(crate) use define_request_and_response; //---------------------------------------------------------------------------------------------------- impl_rpc_call -/// TODO +/// Implement [`crate::RpcCall`] and [`crate::RpcCallValue`] on request types. +/// +/// Input for this is: +/// `$REQUEST_TYPE restricted empty` +/// where `restricted` and `empty` are the idents themselves. +/// The implementation for [`crate::RpcCall`] will change +/// depending if they exist or not. macro_rules! impl_rpc_call { - // TODO + // Restricted and empty RPC calls. ($t:ident, restricted, empty) => { impl $crate::RpcCall for $t { const IS_RESTRICTED: bool = true; @@ -162,7 +175,7 @@ macro_rules! impl_rpc_call { } }; - // TODO + // Empty RPC calls. ($t:ident, empty) => { impl $crate::RpcCall for $t { const IS_RESTRICTED: bool = false; @@ -180,7 +193,7 @@ macro_rules! impl_rpc_call { } }; - // TODO + // Restricted RPC calls. ($t:ident, restricted) => { impl $crate::RpcCall for $t { const IS_RESTRICTED: bool = true; @@ -188,7 +201,7 @@ macro_rules! impl_rpc_call { } }; - // TODO + // Not restrict or empty RPC calls. ($t:ident) => { impl $crate::RpcCall for $t { const IS_RESTRICTED: bool = false; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index cf9132cfe..8ba329ccb 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -953,7 +953,21 @@ define_request_and_response! { } //---------------------------------------------------------------------------------------------------- Request -/// TODO +/// Other JSON requests. +/// +/// This enum contains all [`crate::other`] requests. +/// +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`OtherRequest::SetLogLevel`] +/// has the same (de)serialization as [`SetLogLevelRequest`]. +/// +/// ```rust +/// use cuprate_rpc_types::other::*; +/// +/// let request = OtherRequest::SetLogLevel(Default::default()); +/// let json = serde_json::to_string(&request).unwrap(); +/// assert_eq!(json, r#"{"level":0}"#); +/// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] @@ -1055,7 +1069,21 @@ impl RpcCallValue for OtherRequest { } //---------------------------------------------------------------------------------------------------- Response -/// TODO +/// Other JSON responses. +/// +/// This enum contains all [`crate::other`] responses. +/// +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`OtherRequest::SetBootstrapDaemon`] +/// has the same (de)serialization as [`SetBootstrapDaemonResponse`]. +/// +/// ```rust +/// use cuprate_rpc_types::other::*; +/// +/// let response = OtherResponse::SetBootstrapDaemon(Default::default()); +/// let json = serde_json::to_string(&response).unwrap(); +/// assert_eq!(json, r#"{"status":"OK"}"#); +/// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] diff --git a/rpc/types/src/rpc_call.rs b/rpc/types/src/rpc_call.rs index 2f4b18838..5fb742e00 100644 --- a/rpc/types/src/rpc_call.rs +++ b/rpc/types/src/rpc_call.rs @@ -1,9 +1,18 @@ -//! TODO +//! RPC call metadata. //---------------------------------------------------------------------------------------------------- Import -//---------------------------------------------------------------------------------------------------- Struct definitions -/// TODO +//---------------------------------------------------------------------------------------------------- RpcCall +/// Metadata about an RPC call. +/// +/// This trait describes some metadata about RPC requests. +/// +/// It is implemented on all request types within: +/// - [`crate::json`] +/// - [`crate::other`] +/// - [`crate::bin`] +/// +/// See also [`RpcCallValue`] for a dynamic by-value version of this trait. pub trait RpcCall { /// Is `true` if this RPC method should /// only be allowed on local servers. @@ -13,49 +22,58 @@ pub trait RpcCall { /// RPC servers. /// /// ```rust - /// use cuprate_rpc_types::{ - /// RpcCall, - /// json::{GetBlockCountRequest, GetConnectionsRequest}, - /// }; + /// use cuprate_rpc_types::{RpcCall, json::*}; /// /// // Allowed method, even on restricted RPC servers (18089). - /// assert_eq!(GetBlockCountRequest::IS_RESTRICTED.is_some_and(|x| !x)); + /// assert!(!GetBlockCountRequest::IS_RESTRICTED); /// /// // Restricted methods, only allowed /// // for unrestricted RPC servers (18081). - /// assert_eq!(GetConnectionsRequest::IS_RESTRICTED.is_some_and(|x| x)); + /// assert!(GetConnectionsRequest::IS_RESTRICTED); /// ``` const IS_RESTRICTED: bool; - /// TODO + /// Is `true` if this RPC method has no inputs, i.e. it is a `struct` with no fields. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCall, json::*}; + /// + /// assert!(GetBlockCountRequest::IS_EMPTY); + /// assert!(!OnGetBlockHashRequest::IS_EMPTY); + /// ``` const IS_EMPTY: bool; } -/// TODO +//---------------------------------------------------------------------------------------------------- RpcCallValue +/// By-value version of [`RpcCall`]. +/// +/// This trait is a mirror of [`RpcCall`], +/// except it takes `self` by value instead +/// of being a `const` property. +/// +/// This exists for `enum`s where requests must be dynamically +/// `match`ed like [`JsonRpcRequest`](crate::json::JsonRpcRequest). +/// +/// All types that implement [`RpcCall`] automatically implement [`RpcCallValue`]. pub trait RpcCallValue { - /// Returns `true` if this RPC method should - /// only be allowed on local servers. - /// - /// If this returns `false`, it should be - /// okay to execute the method even on restricted - /// RPC servers. + /// Same as [`RpcCall::IS_RESTRICTED`]. /// /// ```rust - /// use cuprate_rpc_types::{ - /// RpcCall, - /// json::{GetBlockCountRequest, GetConnectionsRequest}, - /// }; + /// use cuprate_rpc_types::{RpcCallValue, json::*}; /// - /// // Allowed method, even on restricted RPC servers (18089). - /// assert_eq!(GetBlockCountRequest::IS_RESTRICTED.is_some_and(|x| !x)); - /// - /// // Restricted methods, only allowed - /// // for unrestricted RPC servers (18081). - /// assert_eq!(GetConnectionsRequest::IS_RESTRICTED.is_some_and(|x| x)); + /// assert!(!GetBlockCountRequest::default().is_restricted()); + /// assert!(GetConnectionsRequest::default().is_restricted()); /// ``` fn is_restricted(&self) -> bool; - /// TODO + /// Same as [`RpcCall::IS_EMPTY`]. + /// + /// ```rust + /// use cuprate_rpc_types::{RpcCallValue, json::*}; + /// + /// assert!(GetBlockCountRequest::default().is_empty()); + /// assert!(!OnGetBlockHashRequest::default().is_empty()); + /// ``` fn is_empty(&self) -> bool; } From 043d27cbf624821fa838623d5dc1d57185db0796 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 31 Jul 2024 20:05:05 -0400 Subject: [PATCH 81/93] types: doc `JsonRpc{Request,Response}` --- rpc/types/src/json.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 320c59048..8c12d6383 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -1569,7 +1569,11 @@ define_request_and_response! { } //---------------------------------------------------------------------------------------------------- Request -/// TODO +/// JSON-RPC requests. +/// +/// This enum contains all [`crate::json`] requests. +/// +/// TODO: document and test (de)serialization behavior after figuring out `method/params`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr( @@ -1683,7 +1687,26 @@ impl RpcCallValue for JsonRpcRequest { } //---------------------------------------------------------------------------------------------------- Response -/// TODO +/// JSON-RPC responses. +/// +/// This enum contains all [`crate::json`] responses. +/// +/// The `serde` implementation will (de)serialize from +/// the inner variant itself, e.g. [`JsonRpcRequest::Banned`] +/// has the same (de)serialization as [`BannedResponse`]. +/// +/// ```rust +/// use cuprate_rpc_types::{misc::*, json::*}; +/// +/// let response = JsonRpcResponse::Banned(BannedResponse { +/// banned: true, +/// seconds: 123, +/// status: Status::Ok, +/// }); +/// let json = serde_json::to_string(&response).unwrap(); +/// assert_eq!(json, r#"{"banned":true,"seconds":123,"status":"OK"}"#); +/// let response: JsonRpcResponse = serde_json::from_str(&json).unwrap(); +/// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged, rename_all = "snake_case"))] From 7f706b5cd00a9f818eff13d63bc97147ae6a8de1 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 31 Jul 2024 20:10:47 -0400 Subject: [PATCH 82/93] types: readme docs --- rpc/types/README.md | 8 ++++++++ rpc/types/src/bin.rs | 4 ++++ rpc/types/src/json.rs | 5 +++++ rpc/types/src/other.rs | 6 ++++++ 4 files changed, 23 insertions(+) diff --git a/rpc/types/README.md b/rpc/types/README.md index 566cca7eb..b5a4f65fc 100644 --- a/rpc/types/README.md +++ b/rpc/types/README.md @@ -7,6 +7,8 @@ This crate ports the types used in Monero's RPC interface, including: - Mixed types - Other commonly used RPC types +It also includes some traits for these types. + # Modules This crate's types are split in the following manner: @@ -94,6 +96,12 @@ The invariants that can be relied upon: - Types in [`bin`] will implement `epee` correctly - Misc types will implement `serde/epee` correctly as needed +# Requests and responses +For `enum`s that encapsulate all request/response types, see: +- [`crate::json::JsonRpcRequest`] & [`crate::json::JsonRpcResponse`] +- [`crate::bin::BinRequest`] & [`crate::bin::BinResponse`] +- [`crate::other::OtherRequest`] & [`crate::other::OtherResponse`] + # Feature flags List of feature flags for `cuprate-rpc-types`. diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index ed0229d2c..278e5352e 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -398,6 +398,8 @@ impl EpeeObject for GetBlocksResponse { /// Binary requests. /// /// This enum contains all [`crate::bin`] requests. +/// +/// See also: [`BinResponse`]. #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] #[allow(missing_docs)] @@ -442,6 +444,8 @@ impl RpcCallValue for BinRequest { /// Binary responses. /// /// This enum contains all [`crate::bin`] responses. +/// +/// See also: [`BinRequest`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[cfg_attr(feature = "serde", serde(untagged))] diff --git a/rpc/types/src/json.rs b/rpc/types/src/json.rs index 8c12d6383..497106134 100644 --- a/rpc/types/src/json.rs +++ b/rpc/types/src/json.rs @@ -1573,6 +1573,8 @@ define_request_and_response! { /// /// This enum contains all [`crate::json`] requests. /// +/// See also: [`JsonRpcResponse`]. +/// /// TODO: document and test (de)serialization behavior after figuring out `method/params`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] @@ -1691,6 +1693,9 @@ impl RpcCallValue for JsonRpcRequest { /// /// This enum contains all [`crate::json`] responses. /// +/// See also: [`JsonRpcRequest`]. +/// +/// # (De)serialization /// The `serde` implementation will (de)serialize from /// the inner variant itself, e.g. [`JsonRpcRequest::Banned`] /// has the same (de)serialization as [`BannedResponse`]. diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 8ba329ccb..9457250fd 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -957,6 +957,9 @@ define_request_and_response! { /// /// This enum contains all [`crate::other`] requests. /// +/// See also: [`OtherResponse`]. +/// +/// # (De)serialization /// The `serde` implementation will (de)serialize from /// the inner variant itself, e.g. [`OtherRequest::SetLogLevel`] /// has the same (de)serialization as [`SetLogLevelRequest`]. @@ -1073,6 +1076,9 @@ impl RpcCallValue for OtherRequest { /// /// This enum contains all [`crate::other`] responses. /// +/// See also: [`OtherRequest`]. +/// +/// # (De)serialization /// The `serde` implementation will (de)serialize from /// the inner variant itself, e.g. [`OtherRequest::SetBootstrapDaemon`] /// has the same (de)serialization as [`SetBootstrapDaemonResponse`]. From b080f6f2fdc81940065e98d1154e1f699fe69f08 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 1 Aug 2024 16:54:04 -0400 Subject: [PATCH 83/93] interface: doc `route/` --- rpc/interface/src/route/bin.rs | 17 +++++++++++++---- rpc/interface/src/route/fallback.rs | 5 +++-- rpc/interface/src/route/json.rs | 3 +-- rpc/interface/src/route/mod.rs | 3 +++ rpc/interface/src/route/other.rs | 25 +++++++++++++++---------- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index f40eb107b..54c8190ef 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -1,5 +1,4 @@ //! Binary route functions. -#![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import use axum::{body::Bytes, extract::State, http::StatusCode}; @@ -11,9 +10,13 @@ use cuprate_rpc_types::bin::{BinRequest, BinResponse, GetTransactionPoolHashesRe use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes -/// TODO +/// This macro generates route functions that expect input. +/// +/// See below for usage. macro_rules! generate_endpoints_with_input { ($( + // Syntax: + // Function name => Expected input type $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( @@ -34,9 +37,13 @@ macro_rules! generate_endpoints_with_input { }}; } -/// TODO +/// This macro generates route functions that expect _no_ input. +/// +/// See below for usage. macro_rules! generate_endpoints_with_no_input { ($( + // Syntax: + // Function name => Expected input type (that is empty) $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( @@ -52,7 +59,9 @@ macro_rules! generate_endpoints_with_no_input { }}; } -/// TODO +/// De-duplicated inner function body for: +/// - [`generate_endpoints_with_input`] +/// - [`generate_endpoints_with_no_input`] macro_rules! generate_endpoints_inner { ($variant:ident, $handler:ident, $request:expr) => { paste::paste! { diff --git a/rpc/interface/src/route/fallback.rs b/rpc/interface/src/route/fallback.rs index 8ee0e7473..947890142 100644 --- a/rpc/interface/src/route/fallback.rs +++ b/rpc/interface/src/route/fallback.rs @@ -1,11 +1,12 @@ //! Fallback route functions. -#![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import use axum::http::StatusCode; //---------------------------------------------------------------------------------------------------- Routes -/// TODO +/// Fallback route function. +/// +/// This is used as the fallback endpoint in [`crate::RouterBuilder`]. pub(crate) async fn fallback() -> StatusCode { StatusCode::NOT_FOUND } diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json.rs index ce15c43e8..9ab2f3d99 100644 --- a/rpc/interface/src/route/json.rs +++ b/rpc/interface/src/route/json.rs @@ -1,5 +1,4 @@ //! JSON-RPC 2.0 endpoint route functions. -#![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import use std::borrow::Cow; @@ -19,7 +18,7 @@ use cuprate_rpc_types::{ use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes -/// TODO +/// The `/json_rpc` route function used in [`crate::RouterBuilder`]. pub(crate) async fn json_rpc( State(handler): State, Json(request): Json>, diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 85c5b77fe..6bb927ce5 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -1,4 +1,7 @@ //! Routing functions. +//! +//! These are the function signatures passed to +//! [`crate::RouterBuilder`] when registering routes. pub(crate) mod bin; pub(crate) mod fallback; diff --git a/rpc/interface/src/route/other.rs b/rpc/interface/src/route/other.rs index 41f37410b..ce778db98 100644 --- a/rpc/interface/src/route/other.rs +++ b/rpc/interface/src/route/other.rs @@ -1,5 +1,4 @@ //! Other JSON endpoint route functions. -#![allow(clippy::unused_async)] // TODO: remove after impl //---------------------------------------------------------------------------------------------------- Import use axum::{extract::State, http::StatusCode, Json}; @@ -29,16 +28,18 @@ use cuprate_rpc_types::{ use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse}; //---------------------------------------------------------------------------------------------------- Routes -/// TODO +/// This macro generates route functions that expect input. +/// +/// See below for usage. macro_rules! generate_endpoints_with_input { ($( + // Syntax: + // Function name => Expected input type $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( - /// TODO - #[allow(unused_mut)] pub(crate) async fn $endpoint( - State(mut handler): State, + State(handler): State, Json(request): Json<[<$variant Request>]>, ) -> Result]>, StatusCode> { generate_endpoints_inner!($variant, handler, request) @@ -47,16 +48,18 @@ macro_rules! generate_endpoints_with_input { }}; } -/// TODO +/// This macro generates route functions that expect _no_ input. +/// +/// See below for usage. macro_rules! generate_endpoints_with_no_input { ($( + // Syntax: + // Function name => Expected input type (that is empty) $endpoint:ident => $variant:ident ),*) => { paste::paste! { $( - /// TODO - #[allow(unused_mut)] pub(crate) async fn $endpoint( - State(mut handler): State, + State(handler): State, ) -> Result]>, StatusCode> { generate_endpoints_inner!($variant, handler, [<$variant Request>] {}) } @@ -64,7 +67,9 @@ macro_rules! generate_endpoints_with_no_input { }}; } -/// TODO +/// De-duplicated inner function body for: +/// - [`generate_endpoints_with_input`] +/// - [`generate_endpoints_with_no_input`] macro_rules! generate_endpoints_inner { ($variant:ident, $handler:ident, $request:expr) => { paste::paste! { From 2ef6080d5b170e9ab004c5db77fda9be5a4bea25 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 1 Aug 2024 17:17:25 -0400 Subject: [PATCH 84/93] interface: fix `todo!()` --- rpc/interface/src/route/bin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/interface/src/route/bin.rs b/rpc/interface/src/route/bin.rs index 54c8190ef..b17b98c6d 100644 --- a/rpc/interface/src/route/bin.rs +++ b/rpc/interface/src/route/bin.rs @@ -71,7 +71,7 @@ macro_rules! generate_endpoints_inner { let channel = $handler.oneshot(request).await?; // Assert the response from the inner handler is correct. - let RpcResponse::Binary(response) = todo!() else { + let RpcResponse::Binary(response) = channel else { panic!("RPC handler did not return a binary response"); }; let BinResponse::$variant(response) = response else { From 18c9061052067f7c093b965ea79b4919d23dbf43 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 1 Aug 2024 18:14:08 -0400 Subject: [PATCH 85/93] interface: allow customizing HTTP method on route functions --- .../src/route/{json.rs => json_rpc.rs} | 0 rpc/interface/src/route/mod.rs | 2 +- rpc/interface/src/router_builder.rs | 107 ++++++++++-------- 3 files changed, 62 insertions(+), 47 deletions(-) rename rpc/interface/src/route/{json.rs => json_rpc.rs} (100%) diff --git a/rpc/interface/src/route/json.rs b/rpc/interface/src/route/json_rpc.rs similarity index 100% rename from rpc/interface/src/route/json.rs rename to rpc/interface/src/route/json_rpc.rs diff --git a/rpc/interface/src/route/mod.rs b/rpc/interface/src/route/mod.rs index 6bb927ce5..7ff9ab8e4 100644 --- a/rpc/interface/src/route/mod.rs +++ b/rpc/interface/src/route/mod.rs @@ -5,5 +5,5 @@ pub(crate) mod bin; pub(crate) mod fallback; -pub(crate) mod json; +pub(crate) mod json_rpc; pub(crate) mod other; diff --git a/rpc/interface/src/router_builder.rs b/rpc/interface/src/router_builder.rs index 7055c660f..49e969fda 100644 --- a/rpc/interface/src/router_builder.rs +++ b/rpc/interface/src/router_builder.rs @@ -3,10 +3,13 @@ use std::marker::PhantomData; //---------------------------------------------------------------------------------------------------- Use -use axum::{routing::method_routing::get, Router}; +use axum::{ + routing::{method_routing::get, post}, + Router, +}; use crate::{ - route::{bin, fallback, json, other}, + route::{bin, fallback, json_rpc, other}, rpc_handler::RpcHandler, }; @@ -15,8 +18,14 @@ use crate::{ macro_rules! generate_router_builder { ($( // Syntax: - // $BUILDER_FUNCTION_NAME => $ACTUAL_ENDPOINT => $ENDPOINT_FUNCTION - $endpoint_ident:ident => $endpoint_string:literal => $endpoint_fn:expr + // $BUILDER_FUNCTION_NAME => + // $ACTUAL_ENDPOINT_STRING => + // $ENDPOINT_FUNCTION_MODULE::$ENDPOINT_FUNCTION => + // ($HTTP_METHOD(s)) + $endpoint_ident:ident => + $endpoint_string:literal => + $endpoint_module:ident::$endpoint_fn:ident => + ($($http_method:ident),*) ),* $(,)?) => { /// Builder for creating the RPC router. /// @@ -120,7 +129,11 @@ macro_rules! generate_router_builder { #[must_use] pub fn $endpoint_ident(self) -> Self { Self { - router: self.router.route($endpoint_string, $endpoint_fn), + router: self.router.route( + $endpoint_string, + ::axum::routing::method_routing::MethodRouter::new() + $(.$http_method($endpoint_module::$endpoint_fn::))* + ), } } )* @@ -130,49 +143,51 @@ macro_rules! generate_router_builder { generate_router_builder! { // JSON-RPC 2.0 route. - json_rpc => "/json_rpc" => get(json::json_rpc::), + json_rpc => "/json_rpc" => json_rpc::json_rpc => (get, post), + // Other JSON routes. - other_get_height => "/get_height" => get(other::get_height::), - other_getheight => "/getheight" => get(other::get_height::), - other_get_transactions => "/get_transactions" => get(other::get_transactions::), - other_gettransactions => "/gettransactions" => get(other::get_transactions::), - other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => get(other::get_alt_blocks_hashes::), - other_is_key_image_spent => "/is_key_image_spent" => get(other::is_key_image_spent::), - other_send_raw_transaction => "/send_raw_transaction" => get(other::send_raw_transaction::), - other_sendrawtransaction => "/sendrawtransaction" => get(other::send_raw_transaction::), - other_start_mining => "/start_mining" => get(other::start_mining::), - other_stop_mining => "/stop_mining" => get(other::stop_mining::), - other_mining_status => "/mining_status" => get(other::mining_status::), - other_save_bc => "/save_bc" => get(other::save_bc::), - other_get_peer_list => "/get_peer_list" => get(other::get_peer_list::), - other_get_public_nodes => "/get_public_nodes" => get(other::get_public_nodes::), - other_set_log_hash_rate => "/set_log_hash_rate" => get(other::set_log_hash_rate::), - other_set_log_level => "/set_log_level" => get(other::set_log_level::), - other_set_log_categories => "/set_log_categories" => get(other::set_log_categories::), - other_get_transaction_pool => "/get_transaction_pool" => get(other::get_transaction_pool::), - other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => get(other::get_transaction_pool_hashes::), - other_get_transaction_pool_stats => "/get_transaction_pool_stats" => get(other::get_transaction_pool_stats::), - other_set_bootstrap_daemon => "/set_bootstrap_daemon" => get(other::set_bootstrap_daemon::), - other_stop_daemon => "/stop_daemon" => get(other::stop_daemon::), - other_get_net_stats => "/get_net_stats" => get(other::get_net_stats::), - other_get_limit => "/get_limit" => get(other::get_limit::), - other_set_limit => "/set_limit" => get(other::set_limit::), - other_out_peers => "/out_peers" => get(other::out_peers::), - other_in_peers => "/in_peers" => get(other::in_peers::), - other_get_outs => "/get_outs" => get(other::get_outs::), - other_update => "/update" => get(other::update::), - other_pop_blocks => "/pop_blocks" => get(other::pop_blocks::), + other_get_height => "/get_height" => other::get_height => (get, post), + other_getheight => "/getheight" => other::get_height => (get, post), + other_get_transactions => "/get_transactions" => other::get_transactions => (get, post), + other_gettransactions => "/gettransactions" => other::get_transactions => (get, post), + other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => other::get_alt_blocks_hashes => (get, post), + other_is_key_image_spent => "/is_key_image_spent" => other::is_key_image_spent => (get, post), + other_send_raw_transaction => "/send_raw_transaction" => other::send_raw_transaction => (get, post), + other_sendrawtransaction => "/sendrawtransaction" => other::send_raw_transaction => (get, post), + other_start_mining => "/start_mining" => other::start_mining => (get, post), + other_stop_mining => "/stop_mining" => other::stop_mining => (get, post), + other_mining_status => "/mining_status" => other::mining_status => (get, post), + other_save_bc => "/save_bc" => other::save_bc => (get, post), + other_get_peer_list => "/get_peer_list" => other::get_peer_list => (get, post), + other_get_public_nodes => "/get_public_nodes" => other::get_public_nodes => (get, post), + other_set_log_hash_rate => "/set_log_hash_rate" => other::set_log_hash_rate => (get, post), + other_set_log_level => "/set_log_level" => other::set_log_level => (get, post), + other_set_log_categories => "/set_log_categories" => other::set_log_categories => (get, post), + other_get_transaction_pool => "/get_transaction_pool" => other::get_transaction_pool => (get, post), + other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => other::get_transaction_pool_hashes => (get, post), + other_get_transaction_pool_stats => "/get_transaction_pool_stats" => other::get_transaction_pool_stats => (get, post), + other_set_bootstrap_daemon => "/set_bootstrap_daemon" => other::set_bootstrap_daemon => (get, post), + other_stop_daemon => "/stop_daemon" => other::stop_daemon => (get, post), + other_get_net_stats => "/get_net_stats" => other::get_net_stats => (get, post), + other_get_limit => "/get_limit" => other::get_limit => (get, post), + other_set_limit => "/set_limit" => other::set_limit => (get, post), + other_out_peers => "/out_peers" => other::out_peers => (get, post), + other_in_peers => "/in_peers" => other::in_peers => (get, post), + other_get_outs => "/get_outs" => other::get_outs => (get, post), + other_update => "/update" => other::update => (get, post), + other_pop_blocks => "/pop_blocks" => other::pop_blocks => (get, post), + // Binary routes. - binary_get_blocks => "/get_blocks.bin" => get(bin::get_blocks::), - binary_getblocks => "/getblocks.bin" => get(bin::get_blocks::), - binary_get_blocks_by_height => "/get_blocks_by_height.bin" => get(bin::get_blocks_by_height::), - binary_getblocks_by_height => "/getblocks_by_height.bin" => get(bin::get_blocks_by_height::), - binary_get_hashes => "/get_hashes.bin" => get(bin::get_hashes::), - binary_gethashes => "/gethashes.bin" => get(bin::get_hashes::), - binary_get_o_indexes => "/get_o_indexes.bin" => get(bin::get_o_indexes::), - binary_get_outs => "/get_outs.bin" => get(bin::get_outs::), - binary_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => get(bin::get_transaction_pool_hashes::), - binary_get_output_distribution => "/get_output_distribution.bin" => get(bin::get_output_distribution::), + bin_get_blocks => "/get_blocks.bin" => bin::get_blocks => (get, post), + bin_getblocks => "/getblocks.bin" => bin::get_blocks => (get, post), + bin_get_blocks_by_height => "/get_blocks_by_height.bin" => bin::get_blocks_by_height => (get, post), + bin_getblocks_by_height => "/getblocks_by_height.bin" => bin::get_blocks_by_height => (get, post), + bin_get_hashes => "/get_hashes.bin" => bin::get_hashes => (get, post), + bin_gethashes => "/gethashes.bin" => bin::get_hashes => (get, post), + bin_get_o_indexes => "/get_o_indexes.bin" => bin::get_o_indexes => (get, post), + bin_get_outs => "/get_outs.bin" => bin::get_outs => (get, post), + bin_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => bin::get_transaction_pool_hashes => (get, post), + bin_get_output_distribution => "/get_output_distribution.bin" => bin::get_output_distribution => (get, post), } impl Default for RouterBuilder { From cd9b6b0d6d52b6e53acb7660f3e6346185c5d434 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 1 Aug 2024 18:15:49 -0400 Subject: [PATCH 86/93] interface: fix tests --- rpc/interface/src/router_builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/interface/src/router_builder.rs b/rpc/interface/src/router_builder.rs index 49e969fda..d370cf4e6 100644 --- a/rpc/interface/src/router_builder.rs +++ b/rpc/interface/src/router_builder.rs @@ -38,7 +38,7 @@ macro_rules! generate_router_builder { /// Functions that enable routes are separated into 3 groups: /// - `json_rpc` (enables all of JSON RPC 2.0) /// - `other_` (e.g. [`other_get_height`](RouterBuilder::other_get_height)) - /// - `binary_` (e.g. [`binary_get_blocks`](RouterBuilder::binary_get_blocks)) + /// - `bin_` (e.g. [`bin_get_blocks`](RouterBuilder::bin_get_blocks)) /// /// For a list of all `monerod` routes, see /// [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189), @@ -65,7 +65,7 @@ macro_rules! generate_router_builder { /// // - `/get_outs.bin` enabled /// // - A fallback enabled /// let get_outs_bin_and_fallback = RouterBuilder::::new() - /// .binary_get_outs() + /// .bin_get_outs() /// .fallback() /// .build(); /// From d8eb45561972649e06aed6f30371f93e5e10f780 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Thu, 1 Aug 2024 19:57:44 -0400 Subject: [PATCH 87/93] fix derives --- rpc/interface/Cargo.toml | 4 ++-- rpc/interface/src/rpc_error.rs | 3 +++ rpc/interface/src/rpc_handler_dummy.rs | 3 +++ rpc/interface/src/rpc_request.rs | 5 +++++ rpc/interface/src/rpc_response.rs | 5 +++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rpc/interface/Cargo.toml b/rpc/interface/Cargo.toml index 925c4ab5d..a83c0f07e 100644 --- a/rpc/interface/Cargo.toml +++ b/rpc/interface/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte keywords = ["cuprate", "rpc", "interface"] [features] -default = ["dummy"] +default = ["dummy", "serde"] dummy = [] [dependencies] @@ -19,7 +19,7 @@ cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], defau cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false } axum = { version = "0.7.5", features = ["json"], default-features = false } -serde = { workspace = true } +serde = { workspace = true, optional = true } serde_json = { workspace = true, features = ["std"] } tower = { workspace = true } paste = { workspace = true } diff --git a/rpc/interface/src/rpc_error.rs b/rpc/interface/src/rpc_error.rs index b5e32d5e3..92b9cc1bd 100644 --- a/rpc/interface/src/rpc_error.rs +++ b/rpc/interface/src/rpc_error.rs @@ -2,6 +2,8 @@ //---------------------------------------------------------------------------------------------------- Import use axum::http::StatusCode; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; //---------------------------------------------------------------------------------------------------- RpcError /// Possible errors during RPC operation. @@ -15,6 +17,7 @@ use axum::http::StatusCode; /// TODO: This is empty as possible errors will be /// enumerated when the handler functions are created. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum RpcError {} impl From for StatusCode { diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index bfc324198..97b758534 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -4,6 +4,8 @@ use std::task::Poll; use futures::{channel::oneshot::channel, FutureExt}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use tower::Service; use cuprate_helper::asynch::InfallibleOneshotReceiver; @@ -26,6 +28,7 @@ use crate::{ /// This is mostly used for testing purposes and can /// be disabled by disable the `dummy` feature flag. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct RpcHandlerDummy { /// Should this RPC server be [restricted](RpcHandler::restricted)? /// diff --git a/rpc/interface/src/rpc_request.rs b/rpc/interface/src/rpc_request.rs index d3abd07cf..3b66a780e 100644 --- a/rpc/interface/src/rpc_request.rs +++ b/rpc/interface/src/rpc_request.rs @@ -1,6 +1,9 @@ //! RPC requests. //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest}; //---------------------------------------------------------------------------------------------------- RpcRequest @@ -12,6 +15,8 @@ use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherReque /// - Other JSON requests /// /// It is the `Request` type required to be used in an [`RpcHandler`](crate::RpcHandler). +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum RpcRequest { /// JSON-RPC 2.0 requests. JsonRpc(cuprate_json_rpc::Request), diff --git a/rpc/interface/src/rpc_response.rs b/rpc/interface/src/rpc_response.rs index 0ff503602..7e8ecdbe9 100644 --- a/rpc/interface/src/rpc_response.rs +++ b/rpc/interface/src/rpc_response.rs @@ -1,6 +1,9 @@ //! RPC responses. //---------------------------------------------------------------------------------------------------- Import +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse}; //---------------------------------------------------------------------------------------------------- RpcResponse @@ -12,6 +15,8 @@ use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherRes /// - Other JSON responses /// /// It is the `Response` type required to be used in an [`RpcHandler`](crate::RpcHandler). +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub enum RpcResponse { /// JSON RPC 2.0 responses. JsonRpc(cuprate_json_rpc::Response), From 04996d1820b7ba310309d6cd1781dd489465353e Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Mon, 5 Aug 2024 16:03:01 -0400 Subject: [PATCH 88/93] Update rpc/interface/README.md Co-authored-by: Boog900 --- rpc/interface/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 6228eaeba..6d79adcd3 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -39,7 +39,7 @@ The proper usage of this crate is to: 1. Implement a [`RpcHandler`] 2. Use it with [`RouterBuilder`] to generate an [`axum::Router`] with all Monero RPC routes set -4. Do whatever with it +3. Do whatever with it # The [`RpcHandler`] This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s, From bf7583f8a3a53a4d2267e83a799632c1ed1bd74a Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Mon, 5 Aug 2024 16:03:07 -0400 Subject: [PATCH 89/93] Update rpc/interface/README.md Co-authored-by: Boog900 --- rpc/interface/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 6d79adcd3..68dffe02c 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -53,7 +53,7 @@ Said concretely, [`RpcHandler`] is a `tower::Service` where the associated types The [`RpcHandler`] must also hold some state that is required for RPC server operation. -The only state currently need is [`RpcHandler::restricted`], which determines if an RPC +The only state currently needed is [`RpcHandler::restricted`], which determines if an RPC server is restricted or not, and thus, if some endpoints/methods are allowed or not. # Unknown endpoint behavior From cc01145f69e21562afe12be2c5c524def53adfad Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 5 Aug 2024 16:30:06 -0400 Subject: [PATCH 90/93] interface: make `RpcHandler`'s `Future` generic --- rpc/interface/README.md | 7 +++++-- rpc/interface/src/rpc_handler.rs | 17 ++++++++++------- rpc/interface/src/rpc_handler_dummy.rs | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/rpc/interface/README.md b/rpc/interface/README.md index 68dffe02c..3a63ac467 100644 --- a/rpc/interface/README.md +++ b/rpc/interface/README.md @@ -45,12 +45,15 @@ The proper usage of this crate is to: This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s, i.e. the "inner handler". -Said concretely, [`RpcHandler`] is a `tower::Service` where the associated types are from this crate: +Said concretely, `RpcHandler` is a `tower::Service` where the associated types are from this crate: - [`RpcRequest`] - [`RpcResponse`] - [`RpcError`] -The [`RpcHandler`] must also hold some state that is required +`RpcHandler`'s [`Future`](std::future::Future) is generic, _although_, +it must output `Result`. + +The `RpcHandler` must also hold some state that is required for RPC server operation. The only state currently needed is [`RpcHandler::restricted`], which determines if an RPC diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index ccd000c76..eaaf7464d 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -22,7 +22,7 @@ use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcRespo /// - [`RpcRequest`] as the generic `Request` type /// - [`RpcResponse`] as the associated `Response` type /// - [`RpcError`] as the associated `Error` type -/// - `InfallibleOneshotReceiver>` as the associated `Future` type +/// - A generic [`Future`] that outputs `Result` and matches [`RpcHandler::Future2`] /// /// See this crate's `RpcHandlerDummy` for an implementation example of this trait. /// @@ -38,13 +38,16 @@ pub trait RpcHandler: + Send + Sync + 'static - + Service< - RpcRequest, - Response = RpcResponse, - Error = RpcError, - Future = InfallibleOneshotReceiver>, - > + + Service { + /// This is the [`Future`] your [`RpcHandler`] outputs. + /// + /// It is used as the type for [`tower::Service::Future`]. + /// + /// For example, `RpcHandlerDummy`'s [`RpcHandler::Future2`] is: + /// - `InfallibleOneshotReceiver>` + type Future2: Future> + Send + Sync + 'static; + /// Is this [`RpcHandler`] restricted? /// /// If this returns `true`, restricted methods and endpoints such as: diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index 97b758534..455ea2e9d 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -38,6 +38,8 @@ pub struct RpcHandlerDummy { } impl RpcHandler for RpcHandlerDummy { + type Future2 = InfallibleOneshotReceiver>; + fn restricted(&self) -> bool { self.restricted } From 9bb081c8f23b71c09d98a9a53b5f1ebefc76f6af Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 5 Aug 2024 16:34:23 -0400 Subject: [PATCH 91/93] interface: add JSON-RPC notification todo --- rpc/interface/src/route/json_rpc.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs index 9ab2f3d99..9bc98a538 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -23,6 +23,15 @@ pub(crate) async fn json_rpc( State(handler): State, Json(request): Json>, ) -> Result>, StatusCode> { + // TODO: + // + // JSON-RPC notifications (requests without `id`) + // must not be responded too, although the request side-effects + // must remain. How to do this consider this function will always + // return and `axum` will respond? + // + // + // Return early if this RPC server is restricted and // the requested method is only for non-restricted RPC. if request.body.is_restricted() && handler.restricted() { From b3d1cba9eea76086c16940c5540382ba8537a2bc Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 5 Aug 2024 16:39:16 -0400 Subject: [PATCH 92/93] formatting --- rpc/interface/src/route/json_rpc.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rpc/interface/src/route/json_rpc.rs b/rpc/interface/src/route/json_rpc.rs index 9bc98a538..bd35e4375 100644 --- a/rpc/interface/src/route/json_rpc.rs +++ b/rpc/interface/src/route/json_rpc.rs @@ -23,14 +23,12 @@ pub(crate) async fn json_rpc( State(handler): State, Json(request): Json>, ) -> Result>, StatusCode> { - // TODO: + // TODO: // // JSON-RPC notifications (requests without `id`) - // must not be responded too, although the request side-effects - // must remain. How to do this consider this function will always - // return and `axum` will respond? - // - // + // must not be responded too, although, the request's side-effects + // must remain. How to do this considering this function will + // always return and cause `axum` to respond? // Return early if this RPC server is restricted and // the requested method is only for non-restricted RPC. From a4a7817c10ce7d64942a9320741595da41f490e2 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Mon, 5 Aug 2024 19:10:15 -0400 Subject: [PATCH 93/93] interface: use associated type bound for `RpcHandler`'s `Future` --- rpc/interface/src/rpc_handler.rs | 17 +++++++---------- rpc/interface/src/rpc_handler_dummy.rs | 2 -- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/rpc/interface/src/rpc_handler.rs b/rpc/interface/src/rpc_handler.rs index eaaf7464d..3d1c28d47 100644 --- a/rpc/interface/src/rpc_handler.rs +++ b/rpc/interface/src/rpc_handler.rs @@ -22,7 +22,7 @@ use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcRespo /// - [`RpcRequest`] as the generic `Request` type /// - [`RpcResponse`] as the associated `Response` type /// - [`RpcError`] as the associated `Error` type -/// - A generic [`Future`] that outputs `Result` and matches [`RpcHandler::Future2`] +/// - A generic [`Future`] that outputs `Result` /// /// See this crate's `RpcHandlerDummy` for an implementation example of this trait. /// @@ -38,16 +38,13 @@ pub trait RpcHandler: + Send + Sync + 'static - + Service + + Service< + RpcRequest, + Response = RpcResponse, + Error = RpcError, + Future: Future> + Send + Sync + 'static, + > { - /// This is the [`Future`] your [`RpcHandler`] outputs. - /// - /// It is used as the type for [`tower::Service::Future`]. - /// - /// For example, `RpcHandlerDummy`'s [`RpcHandler::Future2`] is: - /// - `InfallibleOneshotReceiver>` - type Future2: Future> + Send + Sync + 'static; - /// Is this [`RpcHandler`] restricted? /// /// If this returns `true`, restricted methods and endpoints such as: diff --git a/rpc/interface/src/rpc_handler_dummy.rs b/rpc/interface/src/rpc_handler_dummy.rs index 455ea2e9d..97b758534 100644 --- a/rpc/interface/src/rpc_handler_dummy.rs +++ b/rpc/interface/src/rpc_handler_dummy.rs @@ -38,8 +38,6 @@ pub struct RpcHandlerDummy { } impl RpcHandler for RpcHandlerDummy { - type Future2 = InfallibleOneshotReceiver>; - fn restricted(&self) -> bool { self.restricted }