Skip to content

Commit

Permalink
types: JSON representation types (#300)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hinto-janai authored Oct 5, 2024
1 parent a003e05 commit 80bfe0a
Show file tree
Hide file tree
Showing 14 changed files with 2,004 additions and 26 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions helper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ This allows all workspace crates to share, and aids compile times.
If a 3rd party's crate/functions/types are small enough, it could be moved here to trim dependencies and allow easy modifications.

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

All features on by default.
All features are off by default.

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

Special non-module related features:
- `serde`: Enables serde implementations on applicable types
- `std`: Enables usage of `std`

## `#[no_std]`
Each modules documents whether it requires `std` or not.

Expand Down
2 changes: 1 addition & 1 deletion rpc/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +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" }
cuprate-types = { path = "../../types", default-features = false, features = ["epee", "serde"] }

paste = { workspace = true }
serde = { workspace = true, optional = true }
Expand Down
4 changes: 3 additions & 1 deletion rpc/types/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,9 @@ define_request_and_response! {
AccessResponseBase {
blob: String,
block_header: BlockHeader,
json: String, // FIXME: this should be defined in a struct, it has many fields.
/// `cuprate_rpc_types::json::block::Block` should be used
/// to create this JSON string in a type-safe manner.
json: String,
miner_tx_hash: String,
tx_hashes: Vec<String> = default_vec::<String>(), "default_vec",
}
Expand Down
43 changes: 22 additions & 21 deletions rpc/types/src/misc/tx_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,32 +71,24 @@ use cuprate_epee_encoding::{
pub enum TxEntry {
/// This entry exists in the transaction pool.
InPool {
as_hex: String,
as_json: String,
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TxEntryPrefix,
block_height: u64,
block_timestamp: u64,
confirmations: u64,
double_spend_seen: bool,
output_indices: Vec<u64>,
prunable_as_hex: String,
prunable_hash: String,
pruned_as_hex: String,
tx_hash: String,
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
/// Will always be serialized as `true`.
in_pool: bool,
},
/// This entry _does not_ exist in the transaction pool.
NotInPool {
as_hex: String,
as_json: String,
double_spend_seen: bool,
prunable_as_hex: String,
prunable_hash: String,
pruned_as_hex: String,
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TxEntryPrefix,
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,
Expand All @@ -106,20 +98,29 @@ pub enum TxEntry {
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(),
prefix: Default::default(),
received_timestamp: u64::default(),
relayed: bool::default(),
tx_hash: String::default(),
in_pool: false,
}
}
}

/// Common fields in all [`TxEntry`] variants.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TxEntryPrefix {
as_hex: String,
/// `cuprate_rpc_types::json::tx::Transaction` should be used
/// to create this JSON string in a type-safe manner.
as_json: String,
double_spend_seen: bool,
tx_hash: String,
prunable_as_hex: String,
prunable_hash: String,
pruned_as_hex: String,
}

//---------------------------------------------------------------------------------------------------- Epee
#[cfg(feature = "epee")]
impl EpeeObjectBuilder<TxEntry> for () {
Expand Down
2 changes: 2 additions & 0 deletions rpc/types/src/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ define_request_and_response! {
#[doc = serde_doc_test!(GET_TRANSACTIONS_RESPONSE)]
AccessResponseBase {
txs_as_hex: Vec<String> = default_vec::<String>(), "default_vec",
/// `cuprate_rpc_types::json::tx::Transaction` should be used
/// to create this JSON string in a type-safe manner.
txs_as_json: Vec<String> = default_vec::<String>(), "default_vec",
missed_tx: Vec<String> = default_vec::<String>(), "default_vec",
txs: Vec<TxEntry> = default_vec::<TxEntry>(), "default_vec",
Expand Down
9 changes: 8 additions & 1 deletion types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,33 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/types"
keywords = ["cuprate", "types"]

[features]
default = ["blockchain", "epee", "serde"]
default = ["blockchain", "epee", "serde", "json", "hex"]
blockchain = []
epee = ["dep:cuprate-epee-encoding"]
serde = ["dep:serde"]
proptest = ["dep:proptest", "dep:proptest-derive"]
json = ["hex", "dep:cuprate-helper"]
hex = ["dep:hex"]

[dependencies]
cuprate-epee-encoding = { path = "../net/epee-encoding", optional = true }
cuprate-helper = { path = "../helper", optional = true, features = ["cast"] }
cuprate-fixed-bytes = { path = "../net/fixed-bytes" }

bytes = { workspace = true }
curve25519-dalek = { workspace = true }
monero-serai = { workspace = true }
hex = { workspace = true, features = ["serde", "alloc"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
thiserror = { workspace = true }

proptest = { workspace = true, optional = true }
proptest-derive = { workspace = true, optional = true }

[dev-dependencies]
hex-literal = { workspace = true }
pretty_assertions = { workspace = true }
serde_json = { workspace = true, features = ["std"] }

[lints]
workspace = true
2 changes: 2 additions & 0 deletions types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ This crate is a kitchen-sink for data types that are shared across Cuprate.
| `serde` | Enables `serde` on types where applicable
| `epee` | Enables `cuprate-epee-encoding` on types where applicable
| `proptest` | Enables `proptest::arbitrary::Arbitrary` on some types
| `json` | Enables the `json` module, containing JSON representations of common Monero types
| `hex` | Enables the `hex` module, containing the `HexBytes` type
74 changes: 74 additions & 0 deletions types/src/hex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Hexadecimal serde wrappers for arrays.
//!
//! This module provides transparent wrapper types for
//! arrays that (de)serialize from hexadecimal input/output.

#[cfg(feature = "epee")]
use cuprate_epee_encoding::{error, macros::bytes, EpeeValue, Marker};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Wrapper type for a byte array that (de)serializes from/to hexadecimal strings.
///
/// # Deserialization
/// This struct has a custom deserialization that only applies to certain
/// `N` lengths because [`hex::FromHex`] does not implement for a generic `N`:
/// <https://docs.rs/hex/0.4.3/src/hex/lib.rs.html#220-230>
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[repr(transparent)]
pub struct HexBytes<const N: usize>(
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))] pub [u8; N],
);

impl<'de, const N: usize> Deserialize<'de> for HexBytes<N>
where
[u8; N]: hex::FromHex,
<[u8; N] as hex::FromHex>::Error: std::fmt::Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self(hex::serde::deserialize(deserializer)?))
}
}

#[cfg(feature = "epee")]
impl<const N: usize> EpeeValue for HexBytes<N> {
const MARKER: Marker = <[u8; N] as EpeeValue>::MARKER;

fn read<B: bytes::Buf>(r: &mut B, marker: &Marker) -> error::Result<Self> {
Ok(Self(<[u8; N] as EpeeValue>::read(r, marker)?))
}

fn write<B: bytes::BufMut>(self, w: &mut B) -> error::Result<()> {
<[u8; N] as EpeeValue>::write(self.0, w)
}
}

// Default is not implemented for arrays >32, so we must do it manually.
impl<const N: usize> Default for HexBytes<N> {
fn default() -> Self {
Self([0; N])
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn hex_bytes_32() {
let hash = [1; 32];
let hex_bytes = HexBytes::<32>(hash);
let expected_json = r#""0101010101010101010101010101010101010101010101010101010101010101""#;

let to_string = serde_json::to_string(&hex_bytes).unwrap();
assert_eq!(to_string, expected_json);

let from_str = serde_json::from_str::<HexBytes<32>>(expected_json).unwrap();
assert_eq!(hex_bytes, from_str);
}
}
Loading

0 comments on commit 80bfe0a

Please sign in to comment.