Skip to content

Commit 80bfe0a

Browse files
authored
types: JSON representation types (#300)
* add `cuprate_types::json` * docs * `Option` -> flattened enums + prefix structs * output enum * docs * todo!() epee impl * cuprate-rpc-types: add comments * cuprate-rpc-types: common `TxEntry` fields into prefix struct * remove epee * docs * add `hex` module * `From` serai types * cleanup * proofs * tx from impls * fix tx timelock * add block value tests * add ringct types * add tx_v1, tx_rct_3 test * clsag bulletproofs tx test * clsag bulletproofs plus tx test * docs * fix hex bytes * typo * docs
1 parent a003e05 commit 80bfe0a

File tree

14 files changed

+2004
-26
lines changed

14 files changed

+2004
-26
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

helper/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ This allows all workspace crates to share, and aids compile times.
66
If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications.
77

88
## Features
9-
Code can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`.
9+
Modules can be selectively used/compiled with cargo's `--feature` or `features = ["..."]`.
1010

11-
All features on by default.
11+
All features are off by default.
1212

1313
See [`Cargo.toml`](Cargo.toml)'s `[features]` table to see what features there are and what they enable.
1414

15+
Special non-module related features:
16+
- `serde`: Enables serde implementations on applicable types
17+
- `std`: Enables usage of `std`
18+
1519
## `#[no_std]`
1620
Each modules documents whether it requires `std` or not.
1721

rpc/types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ epee = ["dep:cuprate-epee-encoding"]
1616
[dependencies]
1717
cuprate-epee-encoding = { path = "../../net/epee-encoding", optional = true }
1818
cuprate-fixed-bytes = { path = "../../net/fixed-bytes" }
19-
cuprate-types = { path = "../../types" }
19+
cuprate-types = { path = "../../types", default-features = false, features = ["epee", "serde"] }
2020

2121
paste = { workspace = true }
2222
serde = { workspace = true, optional = true }

rpc/types/src/json.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,9 @@ define_request_and_response! {
635635
AccessResponseBase {
636636
blob: String,
637637
block_header: BlockHeader,
638-
json: String, // FIXME: this should be defined in a struct, it has many fields.
638+
/// `cuprate_rpc_types::json::block::Block` should be used
639+
/// to create this JSON string in a type-safe manner.
640+
json: String,
639641
miner_tx_hash: String,
640642
tx_hashes: Vec<String> = default_vec::<String>(), "default_vec",
641643
}

rpc/types/src/misc/tx_entry.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -71,32 +71,24 @@ use cuprate_epee_encoding::{
7171
pub enum TxEntry {
7272
/// This entry exists in the transaction pool.
7373
InPool {
74-
as_hex: String,
75-
as_json: String,
74+
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
75+
#[cfg_attr(feature = "serde", serde(flatten))]
76+
prefix: TxEntryPrefix,
7677
block_height: u64,
7778
block_timestamp: u64,
7879
confirmations: u64,
79-
double_spend_seen: bool,
8080
output_indices: Vec<u64>,
81-
prunable_as_hex: String,
82-
prunable_hash: String,
83-
pruned_as_hex: String,
84-
tx_hash: String,
8581
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
8682
/// Will always be serialized as `true`.
8783
in_pool: bool,
8884
},
8985
/// This entry _does not_ exist in the transaction pool.
9086
NotInPool {
91-
as_hex: String,
92-
as_json: String,
93-
double_spend_seen: bool,
94-
prunable_as_hex: String,
95-
prunable_hash: String,
96-
pruned_as_hex: String,
87+
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
88+
#[cfg_attr(feature = "serde", serde(flatten))]
89+
prefix: TxEntryPrefix,
9790
received_timestamp: u64,
9891
relayed: bool,
99-
tx_hash: String,
10092
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))]
10193
/// Will always be serialized as `false`.
10294
in_pool: bool,
@@ -106,20 +98,29 @@ pub enum TxEntry {
10698
impl Default for TxEntry {
10799
fn default() -> Self {
108100
Self::NotInPool {
109-
as_hex: String::default(),
110-
as_json: String::default(),
111-
double_spend_seen: bool::default(),
112-
prunable_as_hex: String::default(),
113-
prunable_hash: String::default(),
114-
pruned_as_hex: String::default(),
101+
prefix: Default::default(),
115102
received_timestamp: u64::default(),
116103
relayed: bool::default(),
117-
tx_hash: String::default(),
118104
in_pool: false,
119105
}
120106
}
121107
}
122108

109+
/// Common fields in all [`TxEntry`] variants.
110+
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
111+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
112+
pub struct TxEntryPrefix {
113+
as_hex: String,
114+
/// `cuprate_rpc_types::json::tx::Transaction` should be used
115+
/// to create this JSON string in a type-safe manner.
116+
as_json: String,
117+
double_spend_seen: bool,
118+
tx_hash: String,
119+
prunable_as_hex: String,
120+
prunable_hash: String,
121+
pruned_as_hex: String,
122+
}
123+
123124
//---------------------------------------------------------------------------------------------------- Epee
124125
#[cfg(feature = "epee")]
125126
impl EpeeObjectBuilder<TxEntry> for () {

rpc/types/src/other.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ define_request_and_response! {
140140
#[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)]
141141
AccessResponseBase {
142142
txs_as_hex: Vec<String> = default_vec::<String>(), "default_vec",
143+
/// `cuprate_rpc_types::json::tx::Transaction` should be used
144+
/// to create this JSON string in a type-safe manner.
143145
txs_as_json: Vec<String> = default_vec::<String>(), "default_vec",
144146
missed_tx: Vec<String> = default_vec::<String>(), "default_vec",
145147
txs: Vec<TxEntry> = default_vec::<TxEntry>(), "default_vec",

types/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,33 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types"
99
keywords = ["cuprate", "types"]
1010

1111
[features]
12-
default = ["blockchain", "epee", "serde"]
12+
default = ["blockchain", "epee", "serde", "json", "hex"]
1313
blockchain = []
1414
epee = ["dep:cuprate-epee-encoding"]
1515
serde = ["dep:serde"]
1616
proptest = ["dep:proptest", "dep:proptest-derive"]
17+
json = ["hex", "dep:cuprate-helper"]
18+
hex = ["dep:hex"]
1719

1820
[dependencies]
1921
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
22+
cuprate-helper = { path = "../helper", optional = true, features = ["cast"] }
2023
cuprate-fixed-bytes = { path = "../net/fixed-bytes" }
2124

2225
bytes = { workspace = true }
2326
curve25519-dalek = { workspace = true }
2427
monero-serai = { workspace = true }
28+
hex = { workspace = true, features = ["serde", "alloc"], optional = true }
2529
serde = { workspace = true, features = ["derive"], optional = true }
2630
thiserror = { workspace = true }
2731

2832
proptest = { workspace = true, optional = true }
2933
proptest-derive = { workspace = true, optional = true }
3034

3135
[dev-dependencies]
36+
hex-literal = { workspace = true }
37+
pretty_assertions = { workspace = true }
38+
serde_json = { workspace = true, features = ["std"] }
3239

3340
[lints]
3441
workspace = true

types/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ This crate is a kitchen-sink for data types that are shared across Cuprate.
1010
| `serde` | Enables `serde` on types where applicable
1111
| `epee` | Enables `cuprate-epee-encoding` on types where applicable
1212
| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types
13+
| `json` | Enables the `json` module, containing JSON representations of common Monero types
14+
| `hex` | Enables the `hex` module, containing the `HexBytes` type

types/src/hex.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Hexadecimal serde wrappers for arrays.
2+
//!
3+
//! This module provides transparent wrapper types for
4+
//! arrays that (de)serialize from hexadecimal input/output.
5+
6+
#[cfg(feature = "epee")]
7+
use cuprate_epee_encoding::{error, macros::bytes, EpeeValue, Marker};
8+
#[cfg(feature = "serde")]
9+
use serde::{Deserialize, Serialize};
10+
11+
/// Wrapper type for a byte array that (de)serializes from/to hexadecimal strings.
12+
///
13+
/// # Deserialization
14+
/// This struct has a custom deserialization that only applies to certain
15+
/// `N` lengths because [`hex::FromHex`] does not implement for a generic `N`:
16+
/// <https://docs.rs/hex/0.4.3/src/hex/lib.rs.html#220-230>
17+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18+
#[cfg_attr(feature = "serde", derive(Serialize))]
19+
#[cfg_attr(feature = "serde", serde(transparent))]
20+
#[repr(transparent)]
21+
pub struct HexBytes<const N: usize>(
22+
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N],
23+
);
24+
25+
impl<'de, const N: usize> Deserialize<'de> for HexBytes<N>
26+
where
27+
[u8; N]: hex::FromHex,
28+
<[u8; N] as hex::FromHex>::Error: std::fmt::Display,
29+
{
30+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31+
where
32+
D: serde::Deserializer<'de>,
33+
{
34+
Ok(Self(hex::serde::deserialize(deserializer)?))
35+
}
36+
}
37+
38+
#[cfg(feature = "epee")]
39+
impl<const N: usize> EpeeValue for HexBytes<N> {
40+
const MARKER: Marker = <[u8; N] as EpeeValue>::MARKER;
41+
42+
fn read<B: bytes::Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
43+
Ok(Self(<[u8; N] as EpeeValue>::read(r, marker)?))
44+
}
45+
46+
fn write<B: bytes::BufMut>(self, w: &mut B) -> error::Result<()> {
47+
<[u8; N] as EpeeValue>::write(self.0, w)
48+
}
49+
}
50+
51+
// Default is not implemented for arrays >32, so we must do it manually.
52+
impl<const N: usize> Default for HexBytes<N> {
53+
fn default() -> Self {
54+
Self([0; N])
55+
}
56+
}
57+
58+
#[cfg(test)]
59+
mod test {
60+
use super::*;
61+
62+
#[test]
63+
fn hex_bytes_32() {
64+
let hash = [1; 32];
65+
let hex_bytes = HexBytes::<32>(hash);
66+
let expected_json = r#""0101010101010101010101010101010101010101010101010101010101010101""#;
67+
68+
let to_string = serde_json::to_string(&hex_bytes).unwrap();
69+
assert_eq!(to_string, expected_json);
70+
71+
let from_str = serde_json::from_str::<HexBytes<32>>(expected_json).unwrap();
72+
assert_eq!(hex_bytes, from_str);
73+
}
74+
}

0 commit comments

Comments
 (0)