From 03e19399f06f0053961a18b05bd2044b923117a8 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Thu, 10 Oct 2024 16:24:47 -0700 Subject: [PATCH 01/19] ZMQ types crate added --- zmq/types/Cargo.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 zmq/types/Cargo.toml diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml new file mode 100644 index 000000000..5ff23d63f --- /dev/null +++ b/zmq/types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cuprate-zmq-types" +version = "0.1.0" +edition = "2021" +description = "Types for the ZMQ Pub/Sub API" +license = "MIT" +authors = ["dimalinux", "The Monero Project"] +repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" + +[dependencies] + +[dev-dependencies] + +[lints] +workspace = true + From 57fbb6b2911c16c4a4903f76c297437b2cd13767 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Sat, 12 Oct 2024 20:03:59 -0700 Subject: [PATCH 02/19] added TxPoolAdd message type --- Cargo.lock | 20 +++ Cargo.toml | 3 +- zmq/types/Cargo.toml | 5 +- zmq/types/src/bytes.rs | 52 ++++++++ zmq/types/src/lib.rs | 4 + zmq/types/src/message_types.rs | 237 +++++++++++++++++++++++++++++++++ 6 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 zmq/types/src/bytes.rs create mode 100644 zmq/types/src/lib.rs create mode 100644 zmq/types/src/message_types.rs diff --git a/Cargo.lock b/Cargo.lock index 5fea18d04..eb2fad09e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,16 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -951,6 +961,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cuprate-zmq-types" +version = "0.1.0" +dependencies = [ + "assert-json-diff", + "hex", + "serde", + "serde_json", +] + [[package]] name = "cuprated" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index fa348ccdc..e7f55b512 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "rpc/json-rpc", "rpc/types", "rpc/interface", + "zmq/types", ] [profile.release] @@ -321,4 +322,4 @@ non_camel_case_types = "deny" # unused_results = "deny" # non_exhaustive_omitted_patterns = "deny" # missing_docs = "deny" -# missing_copy_implementations = "deny" \ No newline at end of file +# missing_copy_implementations = "deny" diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml index 5ff23d63f..fa3e86ac3 100644 --- a/zmq/types/Cargo.toml +++ b/zmq/types/Cargo.toml @@ -8,9 +8,12 @@ authors = ["dimalinux", "The Monero Project"] repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" [dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["std"] } +hex = { workspace = true, features = ["std", "serde"] } [dev-dependencies] +assert-json-diff = "2.0.2" [lints] workspace = true - diff --git a/zmq/types/src/bytes.rs b/zmq/types/src/bytes.rs new file mode 100644 index 000000000..509112ea1 --- /dev/null +++ b/zmq/types/src/bytes.rs @@ -0,0 +1,52 @@ +use std::fmt; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub(crate) struct Bytes( + #[serde( + serialize_with = "serialize_to_hex", + deserialize_with = "deserialize_from_hex" + )] + [u8; N], +); + +fn serialize_to_hex(bytes: &[u8; N], serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&hex::encode(bytes)) +} + +fn deserialize_from_hex<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let mut bytes = [0_u8; N]; + hex::decode_to_slice(s, &mut bytes).map_err(serde::de::Error::custom)?; + Ok(bytes) +} + +impl fmt::Display for Bytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use serde_json; + + use super::*; + + #[test] + fn test_bytes32_json() { + let json1 = "\"536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933\""; + let array: Bytes<32> = serde_json::from_str(json1).unwrap(); + let json2 = serde_json::to_string(&array).unwrap(); + assert_eq!(json1, json2); + } +} diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs new file mode 100644 index 000000000..ed353b517 --- /dev/null +++ b/zmq/types/src/lib.rs @@ -0,0 +1,4 @@ +use serde_json as _; + +mod bytes; +mod message_types; diff --git a/zmq/types/src/message_types.rs b/zmq/types/src/message_types.rs new file mode 100644 index 000000000..f7b6e3f1c --- /dev/null +++ b/zmq/types/src/message_types.rs @@ -0,0 +1,237 @@ +use serde::{Deserialize, Serialize}; + +use crate::bytes::Bytes; + +#[derive(Serialize, Deserialize)] +struct TxPoolAdd { + version: u8, + unlock_time: u64, + inputs: Vec, + outputs: Vec, + extra: Bytes<44>, + signatures: Vec, + ringct: RingCt, +} + +#[derive(Serialize, Deserialize)] +struct Input { + to_key: ToKey, +} + +#[derive(Serialize, Deserialize)] +struct ToKey { + amount: u64, + key_offsets: Vec, + key_image: Bytes<32>, +} + +#[derive(Serialize, Deserialize)] +struct Output { + amount: u64, + to_tagged_key: ToTaggedKey, +} + +#[derive(Serialize, Deserialize)] +struct ToTaggedKey { + key: Bytes<32>, + view_tag: Bytes<1>, +} + +#[derive(Serialize, Deserialize)] +struct RingCt { + #[serde(rename = "type")] + ringct_type: u8, + encrypted: Vec, + commitments: Vec>, + fee: u64, + prunable: Prunable, +} + +#[derive(Serialize, Deserialize)] +struct Encrypted { + mask: Bytes<32>, + amount: Bytes<32>, +} + +#[derive(Serialize, Deserialize)] +struct Prunable { + range_proofs: Vec, + bulletproofs: Vec, + bulletproofs_plus: Vec, + mlsags: Vec>, + clsags: Vec, + pseudo_outs: Vec>, +} + +#[expect(non_snake_case)] +#[derive(Serialize, Deserialize)] +struct BulletproofPlus { + V: Vec>, + A: Bytes<32>, + A1: Bytes<32>, + B: Bytes<32>, + r1: Bytes<32>, + s1: Bytes<32>, + d1: Bytes<32>, + L: Vec>, + R: Vec>, +} + +#[derive(Serialize, Deserialize)] +struct NotUsed; + +#[expect(non_snake_case)] +#[derive(Serialize, Deserialize)] +struct Clsag { + s: Vec>, + c1: Bytes<32>, + D: Bytes<32>, +} + +#[cfg(test)] +mod tests { + use assert_json_diff::assert_json_eq; + use serde_json::{self, json}; + + use crate::message_types::TxPoolAdd; + + #[test] + fn test_txpooladd_json() { + let json1 = json!([ + { + "version": 2, + "unlock_time": 0, + "inputs": [ + { + "to_key": { + "amount": 0, + "key_offsets": [ + 82773133, + 30793552, + 578803, + 620532, + 114291, + 291870, + 111275, + 86455, + 19769, + 1238, + 15164, + 11374, + 5240, + 3547, + 7423, + 4198 + ], + "key_image": "89c060b57bba20c0b795bda4b618749e04eba5b40b30062b071dff6e8dd9071d" + } + } + ], + "outputs": [ + { + "amount": 0, + "to_tagged_key": { + "key": "05b4ff4c3ced6ba078a078af8fee5916512a1893f2b6d9373fb90e0eb4040095", + "view_tag": "7a" + } + }, + { + "amount": 0, + "to_tagged_key": { + "key": "60250376bca49bf24cef45c12738b86347df10954cd35630e81b90bf01e922af", + "view_tag": "b8" + } + } + ], + "extra": "01154b87b3334ce9f99d04635eae4e31252a20ba22acb96ff0764a03dc91d203ed020901be80cbce0723d0b4", + "signatures": [], + "ringct": { + "type": 6, + "encrypted": [ + { + "mask": "0000000000000000000000000000000000000000000000000000000000000000", + "amount": "a956be1858615454000000000000000000000000000000000000000000000000" + }, + { + "mask": "0000000000000000000000000000000000000000000000000000000000000000", + "amount": "72972be61af1210b000000000000000000000000000000000000000000000000" + } + ], + "commitments": [ + "cc2a17e43f0b183235a06e8582fcaaa7c21a07732077e66d4dcfaa0db691ea20", + "04e3cd1d3430bb7a1d9ede5ce9ec0ef2f6f9dd9fd31fb95c9e0b3148f1a660c8" + ], + "fee": 30660000, + "prunable": { + "range_proofs": [], + "bulletproofs": [], + "bulletproofs_plus": [ + { + "V": [ + "0196c1e9ba57ae053ae19c1bfd49e13146bd4b6e49401582f8a5a6f65ae560d0", + "aecd14b0e2d788315023601947c12d7e9227d8a1a0aee41f0b34fe196d96119f" + ], + "A": "8011fb75ba56d16b1ef1193e1fdfdb81e6b83afd726087427163857e8fcdf08e", + "A1": "ab91ab6863fbdee1fb71791e5297d007269f1b2cc050df40628ee7d0a1a5f3cb", + "B": "df1d082111b51d479b7fa72f6363bb731207c9343a528dc05b5798af56702521", + "r1": "2e212ae9ad704611a39b9b242453d2408045b303738b51d6f88f9dba06233401", + "s1": "36be53973fd971edff1f43cc5d04dda78d2b01f4caeaf38bbe195b04e309b30d", + "d1": "592116ca54b2d3ca0e9f222ffcc5fd63d3c992470473911fc70822f37672350a", + "L": [ + "98f1e11d62b90c665a8a96fb1b10332e37a790ea1e01a9e8ec8de74b7b27b0df", + "3a14689f3d743a3be719df9af28ca2f0f398e3a2731d5d6f342d0485bf81a525", + "bcb9e389fd494db66e4c796ff03795daa131426c0776ded6d37bfae51f29623d", + "5aa7e1f2bfcfcd74ac8305ce59a7baf5a901f84f8fbdd3a2d639e4058f35e98b", + "5939aa7ea012f88a26bab20270ce5b164c1880f793dc249ec215a0783b4d4ca7", + "08286f78d1bb0d7fc2efc7a3ac314707a4a1ac9656656d496180e131c1748496", + "7fc1de780305601aab95fda4b005927a4643f222e28407c31ad46cc935b7a27c" + ], + "R": [ + "69b4f329c0a5f8ae05891ac5ac35b947a7442b66e5b5693c99435deac3a62662", + "a193038cb8dc9d22abe6577fe44271c1693176cb636f9d101723670fb5ca5cda", + "90670e7083e503c2989b6548500234740dabf3451b0bd376979e03ca0cb5e50c", + "6ab149089f73799811f631eab272bd6c8f190f38efff4d49577364956d0148bf", + "62f2178cbdc760a0d3787b5fd42161c3c98394c2ff2b88efc039df59d2116e5d", + "536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933", + "e47c5c8181e692f3ad91733e7d9a52f8b7e3f5016c5e65f789eea367a13f16cd" + ] + } + ], + "mlsags": [], + "clsags": [ + { + "s": [ + "f70840a8d65da85e962d2ce5ed1293ae3de83318b464363db85505d99e317b01", + "b7c1125be139b4ed201ce85b8453920306cac7c5da11e0f8c0fd7702f15c6a06", + "5a04335699f5a816eed1cab79085814dbcf3be5cef51b078b1c3e0210bbba606", + "e4743e114fd6352ea29e0b48ac96688edaba1d5d0634c34301756902eeb1fb0e", + "34aae87ab091082356d2815a7c8e973124245ebc6d163b9f01fbfeb360edcf04", + "d2d0b6ddb44ed42096affec08ea9cd77d2c7cdc5b2e1e964f836d3717640ec00", + "79b34258c8be04ddd955389f7ee3b912286c23492c519a5687b81d770619620e", + "3c889c19693463160d6c7e642c46f5d41db052ee3358c7dcb4826f48bca26607", + "da04927a438fd0d9674e64f0c016f30fde27f251d3466f29dcd5b3d757fec90c", + "f3e08d83b11ca6529bc18748d3f732c325fca8ff79f69f0ed754bcd529898102", + "f00d7125909a9a8cc5283ffc7727fce945e85828459eecb836c7aedca414350e", + "0a635a193af37be1c9519309f25eaf9f37b7bc5892864646d8d2a2187fcec601", + "0c4154d575dff3699bd41f0c354601de6535161755bd2164526076f37e2c6908", + "f7b21e2698333285ea10a95edbe80fe0bb8740c30b35c25bd2002e3693867e02", + "a637f338ff2ed65fa96e5529abc575fc2a35ed1a3f62a9e7be495069d8438800", + "f7c355f1c3a663978c5fe1c9337aabd4085ee537a61eec2c5c1e837cb3728c09" + ], + "c1": "c5dd25e0e32dbefa6ac1d0dc9072620eb97a99224462cdd163287f2b60b9810b", + "D": "c4fa3f939ccf02e4c8842cbd417cf3690421986e558734a0a029f8a86d2791a8" + } + ], + "pseudo_outs": [ + "bcb08920f5476d74294aeb89c8001123bffd2f2ab84e105d553b807674c595ce" + ] + } + } + } + ]); + + let tx_pool_adds: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); + assert_json_eq!(json1, json2); + } +} From e5875edde776243307996208a41232f78df75458 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Sat, 12 Oct 2024 21:34:28 -0700 Subject: [PATCH 03/19] test for Display trait of Bytes --- zmq/types/src/bytes.rs | 15 ++++++++++++--- zmq/types/src/lib.rs | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/zmq/types/src/bytes.rs b/zmq/types/src/bytes.rs index 509112ea1..0280f4b92 100644 --- a/zmq/types/src/bytes.rs +++ b/zmq/types/src/bytes.rs @@ -2,14 +2,16 @@ use std::fmt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Wrapper for fixed-size arrays of `u8` to provide serde serialization +/// and deserialization to and from hex strings. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] -pub(crate) struct Bytes( +pub struct Bytes( #[serde( serialize_with = "serialize_to_hex", deserialize_with = "deserialize_from_hex" )] - [u8; N], + pub [u8; N], ); fn serialize_to_hex(bytes: &[u8; N], serializer: S) -> Result @@ -43,10 +45,17 @@ mod tests { use super::*; #[test] - fn test_bytes32_json() { + fn test_bytes_json() { let json1 = "\"536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933\""; let array: Bytes<32> = serde_json::from_str(json1).unwrap(); let json2 = serde_json::to_string(&array).unwrap(); assert_eq!(json1, json2); } + + #[test] + fn test_bytes_display() { + let hex_str = "98f1e11d62b90c665a8a96fb1b10332e37a790ea1e01a9e8ec8de74b7b27b0df"; + let bytes = Bytes::<32>(hex::decode(hex_str).unwrap().try_into().unwrap()); + assert_eq!(format!("{}", bytes), hex_str); + } } diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs index ed353b517..433a395b3 100644 --- a/zmq/types/src/lib.rs +++ b/zmq/types/src/lib.rs @@ -1,4 +1,4 @@ use serde_json as _; -mod bytes; -mod message_types; +pub mod bytes; +pub mod message_types; From 89b964d267e9fb8fba6db22b78959db1a38507ba Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Sat, 12 Oct 2024 22:10:56 -0700 Subject: [PATCH 04/19] added TxPoolAddMin --- zmq/types/src/bytes.rs | 14 +++- zmq/types/src/message_types.rs | 142 +++++++++++++++++++-------------- 2 files changed, 96 insertions(+), 60 deletions(-) diff --git a/zmq/types/src/bytes.rs b/zmq/types/src/bytes.rs index 0280f4b92..619bf58c1 100644 --- a/zmq/types/src/bytes.rs +++ b/zmq/types/src/bytes.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Wrapper for fixed-size arrays of `u8` to provide serde serialization /// and deserialization to and from hex strings. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(transparent)] pub struct Bytes( #[serde( @@ -14,6 +14,12 @@ pub struct Bytes( pub [u8; N], ); +impl Default for Bytes { + fn default() -> Self { + Self([0; N]) + } +} + fn serialize_to_hex(bytes: &[u8; N], serializer: S) -> Result where S: Serializer, @@ -58,4 +64,10 @@ mod tests { let bytes = Bytes::<32>(hex::decode(hex_str).unwrap().try_into().unwrap()); assert_eq!(format!("{}", bytes), hex_str); } + + #[test] + fn test_bytes_default() { + let bytes = Bytes::<32>::default(); + assert_eq!(bytes.0, [0; 32]); + } } diff --git a/zmq/types/src/message_types.rs b/zmq/types/src/message_types.rs index f7b6e3f1c..9b343536a 100644 --- a/zmq/types/src/message_types.rs +++ b/zmq/types/src/message_types.rs @@ -2,90 +2,98 @@ use serde::{Deserialize, Serialize}; use crate::bytes::Bytes; -#[derive(Serialize, Deserialize)] -struct TxPoolAdd { - version: u8, - unlock_time: u64, - inputs: Vec, - outputs: Vec, - extra: Bytes<44>, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxPoolAdd { + pub version: u8, + pub unlock_time: u64, + pub inputs: Vec, + pub outputs: Vec, + pub extra: Bytes<44>, signatures: Vec, - ringct: RingCt, + pub ringct: RingCt, } -#[derive(Serialize, Deserialize)] -struct Input { - to_key: ToKey, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxPoolAddMin { + pub id: Bytes<32>, + pub blob_size: u64, + pub weight: u64, + pub fee: u64, } -#[derive(Serialize, Deserialize)] -struct ToKey { - amount: u64, - key_offsets: Vec, - key_image: Bytes<32>, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Input { + pub to_key: ToKey, } -#[derive(Serialize, Deserialize)] -struct Output { - amount: u64, - to_tagged_key: ToTaggedKey, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ToKey { + pub amount: u64, + pub key_offsets: Vec, + pub key_image: Bytes<32>, } -#[derive(Serialize, Deserialize)] -struct ToTaggedKey { - key: Bytes<32>, - view_tag: Bytes<1>, +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct Output { + pub amount: u64, + pub to_tagged_key: ToTaggedKey, } -#[derive(Serialize, Deserialize)] -struct RingCt { +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct ToTaggedKey { + pub key: Bytes<32>, + pub view_tag: Bytes<1>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct RingCt { #[serde(rename = "type")] - ringct_type: u8, - encrypted: Vec, - commitments: Vec>, - fee: u64, - prunable: Prunable, + pub ringct_type: u8, + pub encrypted: Vec, + pub commitments: Vec>, + pub fee: u64, + pub prunable: Prunable, } -#[derive(Serialize, Deserialize)] -struct Encrypted { - mask: Bytes<32>, - amount: Bytes<32>, +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +pub struct Encrypted { + pub mask: Bytes<32>, + pub amount: Bytes<32>, } -#[derive(Serialize, Deserialize)] -struct Prunable { +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Prunable { range_proofs: Vec, bulletproofs: Vec, - bulletproofs_plus: Vec, - mlsags: Vec>, - clsags: Vec, - pseudo_outs: Vec>, + pub bulletproofs_plus: Vec, + pub mlsags: Vec>, + pub clsags: Vec, + pub pseudo_outs: Vec>, } #[expect(non_snake_case)] -#[derive(Serialize, Deserialize)] -struct BulletproofPlus { - V: Vec>, - A: Bytes<32>, - A1: Bytes<32>, - B: Bytes<32>, - r1: Bytes<32>, - s1: Bytes<32>, - d1: Bytes<32>, - L: Vec>, - R: Vec>, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct BulletproofPlus { + pub V: Vec>, + pub A: Bytes<32>, + pub A1: Bytes<32>, + pub B: Bytes<32>, + pub r1: Bytes<32>, + pub s1: Bytes<32>, + pub d1: Bytes<32>, + pub L: Vec>, + pub R: Vec>, } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] struct NotUsed; #[expect(non_snake_case)] -#[derive(Serialize, Deserialize)] -struct Clsag { - s: Vec>, - c1: Bytes<32>, - D: Bytes<32>, +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Clsag { + pub s: Vec>, + pub c1: Bytes<32>, + pub D: Bytes<32>, } #[cfg(test)] @@ -93,7 +101,7 @@ mod tests { use assert_json_diff::assert_json_eq; use serde_json::{self, json}; - use crate::message_types::TxPoolAdd; + use super::*; #[test] fn test_txpooladd_json() { @@ -234,4 +242,20 @@ mod tests { let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); assert_json_eq!(json1, json2); } + + #[test] + fn test_txpooladd_min_json() { + let json1 = json!([ + { + "id": "b5086746e805d875cbbbbb49e19aac29d9b75019f656fab8516cdf64ac5cd346", + "blob_size": 1533, + "weight": 1533, + "fee": 30660000 + } + ]); + + let tx_pool_adds: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); + assert_json_eq!(json1, json2); + } } From 40a26bca3c427e79ba1d243acb41d052b1163304 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Mon, 14 Oct 2024 15:44:22 -0700 Subject: [PATCH 05/19] added FullChainMain --- zmq/types/src/message_types.rs | 175 +++++++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 6 deletions(-) diff --git a/zmq/types/src/message_types.rs b/zmq/types/src/message_types.rs index 9b343536a..0d12df56d 100644 --- a/zmq/types/src/message_types.rs +++ b/zmq/types/src/message_types.rs @@ -6,11 +6,11 @@ use crate::bytes::Bytes; pub struct TxPoolAdd { pub version: u8, pub unlock_time: u64, - pub inputs: Vec, + pub inputs: Vec, pub outputs: Vec, pub extra: Bytes<44>, signatures: Vec, - pub ringct: RingCt, + pub ringct: PoolRingCt, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -21,11 +21,32 @@ pub struct TxPoolAddMin { pub fee: u64, } +#[derive(Debug, Serialize, Deserialize)] +pub struct FullChainMain { + pub major_version: u8, + pub minor_version: u8, + pub timestamp: u64, + pub prev_id: Bytes<32>, + pub nonce: u32, + pub miner_tx: MinerTx, + pub tx_hashes: Vec>, +} + #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Input { +pub struct PoolInput { pub to_key: ToKey, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct MinerInput { + pub r#gen: Gen, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Gen { + pub height: u64, +} + #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ToKey { pub amount: u64, @@ -46,15 +67,19 @@ pub struct ToTaggedKey { } #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct RingCt { - #[serde(rename = "type")] - pub ringct_type: u8, +pub struct PoolRingCt { + pub r#type: u8, pub encrypted: Vec, pub commitments: Vec>, pub fee: u64, pub prunable: Prunable, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct MinerRingCt { + pub r#type: u8, +} + #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Encrypted { pub mask: Bytes<32>, @@ -96,6 +121,17 @@ pub struct Clsag { pub D: Bytes<32>, } +#[derive(Debug, Serialize, Deserialize)] +pub struct MinerTx { + pub version: u8, + pub unlock_time: u64, + pub inputs: Vec, + pub outputs: Vec, + pub extra: Bytes<52>, + signatures: Vec, + pub ringct: MinerRingCt, +} + #[cfg(test)] mod tests { use assert_json_diff::assert_json_eq; @@ -258,4 +294,131 @@ mod tests { let json2 = serde_json::to_value(&tx_pool_adds).unwrap(); assert_json_eq!(json1, json2); } + + #[test] + fn test_full_chain_main_json() { + let json1 = json!([ + { + "major_version": 16, + "minor_version": 16, + "timestamp": 1726973843, + "prev_id": "ce3731311b7e4c1e58a2fe902dbb5c60bb2c0decc163d5397fa52a260d7f09c1", + "nonce": 537273946, + "miner_tx": { + "version": 2, + "unlock_time": 3242818, + "inputs": [ + { + "gen": { + "height": 3242758 + } + } + ], + "outputs": [ + { + "amount": 618188180000_u64, + "to_tagged_key": { + "key": "83faf44df7e9fb4cf54a8dd6a63868507d1a1896bdb35ea9110d739d5da6cf21", + "view_tag": "38" + } + } + ], + "extra": "010e3356a86dbb339354afbc693408dfe8648bffd0b276e6a431861eb73643d88d02115162e362c98e2d00000000000000000000", + "signatures": [], + "ringct": { + "type": 0 + } + }, + "tx_hashes": [ + "2c1b67d3f10b21270cac116e6d5278dc4024ee2d727e4ad56d6dedb1abc0270c", + "c2cfec0de23229a2ab80ca464cef66fc1cad53647a444f048834ec236c38c867", + "03c7649af2373c0f739d3c2eff9ee1580986b460d2abdd5e2aa332281e52da7e", + "1e0834cc658599e786040bdcd9b589a5e8d975233b72279d04ece1a3dd5572b0", + "ba65c30150e906a8799ee99bb2e6481873e42ed8b025cf967c5798528ddc81b4", + "6fc7b1da1cf433edafb142173e9ac13fe05142a36d8a72e9efdf7a3b94da11d6", + "847c06dcda4540d45cae868d4d031781bd87d9bfa4b2186a611428f52e68ccee", + "79f87a1b2fc17295d2cf25b6a65dd17fd8630829ee50f9c48f15e4a24e72d872", + "32b4f7ce6d864006b274dbd73fc8058151d0fd2dd0bb4b423120e32451fd59eb", + "430fe7fa00b63b68b301a4e4810bef2b5be1f651dba8c360e86eac61227382e7", + "9f8d2bf5e39071abccb336404ea72ab85cb731500a1d386a3bf537b9046df29d", + "f63893b8226ca28c290cb65541d60c1675dda1e2a77a629b6b2d7c3886240b23", + "ee8608b6e80cce14beaf95f747f4da8e40e40a49ad1adc20038843a6da3df3c6", + "05783765c150ed1e46d6380726e7ca1f788305754e553f5f2d49b9f09aaaf88d", + "20b4b95e62f45b72014d6ab14edb0b31e273cdc8c8d106068dd32ef6e92fc0a2", + "9230fb0a9dce8e2ca7e109ebf3480838251691de8ed73ea91f74723c5cf19bac", + "d59cf84a25f56ec0f1352bb05645efe9b9326598c4f7c5bc39a87eb7a20c48fc", + "465deb73c48a460df71861d61666dabb906648035a1fecfd0e988ee37616c655", + "5767bc633729ba4555561510f3db739431b16744234dcd549a0d346eaa6685b1", + "2c8d9af5d5774de96e67835ac5adbc6ca5579125b08bc907b395645eea6410ec", + "d385c884a0687c3360725dd3a3f6acf6f64bf38d8eeea1644d80bc23b13ee870", + "b2bc7e9fa9c1da08a8b6ee58505611c05bc388fd30aece00e9a0041470f7e950", + "69a4a79b50d42d372e91c6608c2652d1d5ddd343526c387ef6cf1e3c158b1765", + "ef508dfa79bbedd226835c42a9d000a64cc4abe0250c9aa55fd968224e2b45c3", + "0413c3b3fc621c472e10a102d77456db506f0df10a909833aed0c6738fb31eeb", + "e0c52d6d649c2f1abce4c6ffce4dd75a23308afbb6abe33af53da232c40caf5f", + "cd1fd68d2a15002ca6236083ff6ae165c8fd922f410da79640a4342fd8ebd1c8", + "ba746f80ca4ee496f4188ba278f1ed69a913238450d52bd2e2f3d3bf6fdd43d3", + "13c964bc13a55621b7bbbfe9a6d703536d951bfa19eedee93dd1286020959021", + "41a6f8d0df227a401a9bd6f5c0fbc21ed89f515ea5c8434a087e8b880080ee1f", + "41c2b5994284790b1ee158f7b87aa1231c14975d6456a91ff6f93c6f81277965", + "7e6b7f169cc6cab88e652771157cf8c2eb6f69dffb6939a79b34c6554fe6c00b", + "619517d9d138bf95c6b77eb801526b8419616de2b8618ccfd3b6d1c10364bc64", + "52cca64fb20fc2f6d06034a1a2d9b5665972ebc2569ec69f8d473caada309add", + "219c106d09da5a27b339ea0f070da090779b31ef9ccfa90d6d25e7388341eff9", + "e07ce6e96e73cff80c9cc4c1b349ad1ef53cff210b876d4e7afd89fcc8b2e5dd", + "e98f2a00b2892cd65c0252d956d88a4bb8024c7db98ca003c127b097f097f276", + "ed81aa398071fe495e37095e51ff50053e132bd11f27ba9c06ac4bf4063b756f", + "667d29a0cefa311e06fcfc22c98ef75edf81deb6c8a812492eb255a049c826db", + "8b16e8cbc1765247456bd67a3106498f686401b7529dc0f6b03360caf8671135", + "013e443e63259748f6d1a5653374826618ba066b7febcf55c829333f0dd9a6c3", + "517a05d82de59a973eb4d343c45558841c9165ccd75ca7c9d2e1a35f80c26c15", + "af74d5dd44cfed8f40f853a6fc405dae23d547482296f8dbbc13c1aed2c3d8c5", + "b5086746e805d875cbbbbb49e19aac29d9b75019f656fab8516cdf64ac5cd346", + "cfcda18d058656797a1272b384774dcfc26a504a24298aa49ba060eb6b4a19e0", + "1f380660a99030cc45f85ba8ee0e0541035c0fde719c84aa692796328974c9dd", + "53127181a0301a27b3a2749dc997556b211d949a99aa34d1c52d5c54220f49d2", + "5d50a66df97f4decc4ecc3f5030589ef966d5af84a995f7fb14f1c02ae9704db", + "cdab9628acdb57c460e292660e7a07caf2ddbcffdfff92f3e5e4fb12119a11ca", + "e740a098a74d7a66a821c4ac3c5f913a82fc7445b5593cc5fa3e48ad1b4589b1", + "760549176fec210cfe0ff58eabbf2670cf33b4cd3942a3b60a98bf8f328a6d01", + "961b0956aa6303ed8ca1687d93ed46b9aa8a0203ec4ce0cbc2e86b364fbfb613", + "b9db041b2c3bfc6b5b0facb638b0b4643eec76b060039a6b11fb43682ed77a97", + "1011c321eb386b9975e8124bdb130790dcf4ac0021da3103cabbf7dfa18ccea7", + "6a9d3d15be4b25bd544d96bb1d7685e53f9484735bb22994feffb9037009aeeb", + "bf20d6193890cf7fdead9e3b60197564c663b5a62eda782a49d4aa7819bb9665", + "472d28f9d25a95e625eb808ff3827e7f6792009e1ba0b3b21951f3058b65a75d", + "e3931b2b66da07f983d2235d9d0b3a3098008458bdc0c1ad4370fae73e1eaa9e", + "e18a0dea6382c95aa4089a971190683b171e9405c06fd4111924144600f3bcf3", + "1a336bcf24026307821b76b9ca18b178c285c591c5df9906e3ffbd2050ccd356", + "8ca2d0e5ae9b9981bb8b76ba0da383c585664b2a2f4e861d58aab00c9b0cc808", + "e1866c27023ccea276034c4d572eab42713132e4fdb2aafa9488f6d74cd49303", + "3674cfafba4cdea5775a72a82e5d553bf180beab456b3cbaa7b41a1574fe1948", + "9bb400dd317425f40176c3094a5573037b0217e0b60761cb66a8fa15b63b36c3", + "c078048028aca3e9bc40f68f4d42ef25c6af2cef4da20bf3be70dd6a23b82d52", + "c28cc85f945085e70259ed02131ae3f8c5992e789c9c75c2c6e257306beaf26e", + "4c2b121795fe2b90fda84813543952382daa29c7b96edd9f96040df13e48e347", + "63c6fba30b5471fd60e715cbaf4448badafde68dbc42c54d96b56dd2c4bf2d15", + "a4240138ecfe736113581f318f261a01992eaa8fa5b7bd6938d9dbeb65aa85d7", + "b9d088a7b21f655d0cf50f8404e874f4d1655fb5565a354d2c0dd6d113619c66", + "9133e7e98a83f6e10a7fd44c104d9124d93e0d3e920f5c160873b394dd3a2fcb", + "953985dbd0ea6f86746e83be144ec2ff2897ef1f3506eede083b893e98dd63ea", + "83af840c4cad46de96c86fcf700ade32e73260d4a16cefa330cb5a722ef59fdf", + "eea3c0c2b016ea0c269f954fd8172c3d118f08103c9842b81b05290c9faf3780", + "ac43a363fdb81fa4f6df1cb06ba49a5f4eeef411957cf2afad55cbc1e79bc4d1", + "ca72cf7bda22aed15c16ca67e7b6cc57109cdc86d4ffe38fd71210a5380fcada", + "477dc1cd62106d9df6b37f8515579a48d01b310387087c08ce7062a8eb5df98d", + "d47b6dcd3b13288825c954df6c6e30eb683d1f79434beaee7172082f8ae74280", + "9c64ef20c69589c56fcc5f3a0d10f6957ecea248e44acb432aaf16a88eeef946", + "d2aa256bfd61bdb64ac38da6cbc3e77fb315bb9fbaf422087c10345377df44f6", + "8b9623e4513594a6eaeb3475ea7d0eb585dd8f6e20e21c316db0b942fada2336", + "860725ed0bd18c744e6b8b02888ad88be1cf23d7153131b220a0f9fbb76976bf", + "387cc6e807efc263a0ad6a30e6313a27d16abef038264d0afa0e6ad943be55da" + ] + } + ]); + + let full_chain_main: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&full_chain_main).unwrap(); + assert_json_eq!(json1, json2); + } } From 9817bbd52adf6d7ff8364b8ac480b03c67d35d54 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Sat, 19 Oct 2024 23:38:00 -0700 Subject: [PATCH 06/19] added FullMinerData --- zmq/types/src/{bytes.rs => bytes_hex.rs} | 28 ++--- ...message_types.rs => json_message_types.rs} | 105 +++++++++++++++++- zmq/types/src/lib.rs | 5 +- zmq/types/src/u128_hex.rs | 58 ++++++++++ 4 files changed, 175 insertions(+), 21 deletions(-) rename zmq/types/src/{bytes.rs => bytes_hex.rs} (73%) rename zmq/types/src/{message_types.rs => json_message_types.rs} (81%) create mode 100644 zmq/types/src/u128_hex.rs diff --git a/zmq/types/src/bytes.rs b/zmq/types/src/bytes_hex.rs similarity index 73% rename from zmq/types/src/bytes.rs rename to zmq/types/src/bytes_hex.rs index 619bf58c1..687b9eaf7 100644 --- a/zmq/types/src/bytes.rs +++ b/zmq/types/src/bytes_hex.rs @@ -1,15 +1,17 @@ +//! Wrapper for fixed-size arrays of `u8` to provide serde serialization +//! and deserialization to and from hex strings. use std::fmt; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -/// Wrapper for fixed-size arrays of `u8` to provide serde serialization -/// and deserialization to and from hex strings. +/// Bytes serializes to hex strings have no leading 0x and exactly N*2 +/// nibbles (no zeros are stripped). #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[serde(transparent)] pub struct Bytes( #[serde( - serialize_with = "serialize_to_hex", - deserialize_with = "deserialize_from_hex" + serialize_with = "serialize_bytes", + deserialize_with = "deserialize_bytes" )] pub [u8; N], ); @@ -20,14 +22,14 @@ impl Default for Bytes { } } -fn serialize_to_hex(bytes: &[u8; N], serializer: S) -> Result +fn serialize_bytes(bytes: &[u8; N], serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&hex::encode(bytes)) } -fn deserialize_from_hex<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error> +fn deserialize_bytes<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error> where D: Deserializer<'de>, { @@ -50,6 +52,12 @@ mod tests { use super::*; + #[test] + fn test_bytes_default() { + let bytes = Bytes::<32>::default(); + assert_eq!(bytes.0, [0; 32]); + } + #[test] fn test_bytes_json() { let json1 = "\"536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933\""; @@ -62,12 +70,6 @@ mod tests { fn test_bytes_display() { let hex_str = "98f1e11d62b90c665a8a96fb1b10332e37a790ea1e01a9e8ec8de74b7b27b0df"; let bytes = Bytes::<32>(hex::decode(hex_str).unwrap().try_into().unwrap()); - assert_eq!(format!("{}", bytes), hex_str); - } - - #[test] - fn test_bytes_default() { - let bytes = Bytes::<32>::default(); - assert_eq!(bytes.0, [0; 32]); + assert_eq!(format!("{bytes}"), hex_str); } } diff --git a/zmq/types/src/message_types.rs b/zmq/types/src/json_message_types.rs similarity index 81% rename from zmq/types/src/message_types.rs rename to zmq/types/src/json_message_types.rs index 0d12df56d..abe0b32b2 100644 --- a/zmq/types/src/message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -1,7 +1,20 @@ +//! Objects for JSON serialization and deserialization in message bodies of +//! the ZMQ pub/sub interface. Handles JSON for the following subscriptions: +//! * `json-full-txpool_add` +//! * `json-minimal-txpool_add` +//! * `json-full-chain_main` +//! * `json-minimal-chain_main` +//! * `json-full-miner_data` use serde::{Deserialize, Serialize}; -use crate::bytes::Bytes; +use crate::{bytes_hex::Bytes, u128_hex::U128}; +/// ZMQ `json-full-txpool_add` packets contain an array of `TxPoolAdd`. +/// +/// Each `TxPoolAdd` object represents a new transaction in the mempool that was +/// not previously seen in a block. Miner coinbase transactions *are not* +/// included. `do-not-relay` transactions *are* included. Values are not +/// republished during a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { pub version: u8, @@ -9,10 +22,13 @@ pub struct TxPoolAdd { pub inputs: Vec, pub outputs: Vec, pub extra: Bytes<44>, - signatures: Vec, + signatures: Vec, pub ringct: PoolRingCt, } +/// ZMQ `json-minimal-txpool_add` subscriber messages contain an array of +/// `TxPoolAddMin` JSON objects. See `TxPoolAdd` for information on which +/// transactions are published to subscribers. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAddMin { pub id: Bytes<32>, @@ -23,15 +39,41 @@ pub struct TxPoolAddMin { #[derive(Debug, Serialize, Deserialize)] pub struct FullChainMain { + /// The major version of the monero protocol at the next? block height. pub major_version: u8, + /// The minor version of the monero protocol at the next? block height. pub minor_version: u8, + /// Unix epoch time, decided by the miner, at which the block was mined. pub timestamp: u64, + /// Hash of the most recent block on which to mine the next block pub prev_id: Bytes<32>, + /// a cryptographic random one-time number used in mining a Monero block pub nonce: u32, + /// Miner transaction information pub miner_tx: MinerTx, + /// List of hashes of non-coinbase transactions in the block. If there are no + /// other transactions, this will be an empty list. pub tx_hashes: Vec>, } +/// ZMQ `json-full-miner_data` subscriber messages contain a single +/// `FullMinerData` object that provides the necessary data to create a +/// custom block template. There is no min version of this object. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct FullMinerData { + pub major_version: u8, + pub height: u64, + pub prev_id: Bytes<32>, + pub seed_hash: Bytes<32>, + pub difficulty: U128, + pub median_weight: u64, + /// Fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission. + /// Note that not all JSON parsers can handle this large value correctly. + pub already_generated_coins: u64, + /// List of mineable mempool transactions + pub tx_backlog: Vec, +} + #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolInput { pub to_key: ToKey, @@ -88,8 +130,8 @@ pub struct Encrypted { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { - range_proofs: Vec, - bulletproofs: Vec, + range_proofs: Vec, + bulletproofs: Vec, pub bulletproofs_plus: Vec, pub mlsags: Vec>, pub clsags: Vec, @@ -110,8 +152,10 @@ pub struct BulletproofPlus { pub R: Vec>, } +/// Placeholder element type so obsolete fields can be deserialized +/// to the empty vector for backwards compatibility. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -struct NotUsed; +struct Obsolete; #[expect(non_snake_case)] #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -124,14 +168,24 @@ pub struct Clsag { #[derive(Debug, Serialize, Deserialize)] pub struct MinerTx { pub version: u8, + /// The block height when the coinbase transaction becomes spendable pub unlock_time: u64, + /// list of transaction inputs pub inputs: Vec, + /// list of transaction outputs pub outputs: Vec, pub extra: Bytes<52>, - signatures: Vec, + signatures: Vec, pub ringct: MinerRingCt, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TxBacklog { + pub id: Bytes<32>, + pub weight: u64, + pub fee: u64, +} + #[cfg(test)] mod tests { use assert_json_diff::assert_json_eq; @@ -421,4 +475,43 @@ mod tests { let json2 = serde_json::to_value(&full_chain_main).unwrap(); assert_json_eq!(json1, json2); } + + #[test] + fn test_full_miner_data_json() { + let json1 = json!({ + "major_version": 16, + "height": 3242764, + "prev_id": "dc53c24683dca14586fb2909b9aa4a44adb524e010d438e2491e7d8cc1c80831", + "seed_hash": "526577d6e6689ba8736c16ccc76e6ce4ada3b0ceeaa3a2260b96ba188a17d705", + "difficulty": "0x526f2623ce", + "median_weight": 300000, + "already_generated_coins": 18446744073709551615_u64, + "tx_backlog": [ + { + "id": "dbec64651bb4e83d0e9a05c2826bde605a940f12179fab0ab5dc8bc4392c776b", + "weight": 2905, + "fee": 929600000 + }, + { + "id": "ec5728dd1fbd98db1f93d612826e73b95f52cca49f247a6dbc35390f45766a7d", + "weight": 2222, + "fee": 44440000 + }, + { + "id": "41f613b1a470af494e0a705993e305dfaad3e365fcc0b0db0118256fc54559aa", + "weight": 2221, + "fee": 44420000 + }, + { + "id": "34fa33bf96dc2f825fe870e8f5402be6225c1623b345224e0dbc38b6407873de", + "weight": 2217, + "fee": 709440000 + } + ] + }); + + let full_miner_data: FullMinerData = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&full_miner_data).unwrap(); + assert_json_eq!(json1, json2); + } } diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs index 433a395b3..aaef028e1 100644 --- a/zmq/types/src/lib.rs +++ b/zmq/types/src/lib.rs @@ -1,4 +1,5 @@ use serde_json as _; -pub mod bytes; -pub mod message_types; +pub mod bytes_hex; +pub mod json_message_types; +mod u128_hex; diff --git a/zmq/types/src/u128_hex.rs b/zmq/types/src/u128_hex.rs new file mode 100644 index 000000000..793dedd55 --- /dev/null +++ b/zmq/types/src/u128_hex.rs @@ -0,0 +1,58 @@ +//! Wrapper for u128 that serializes/deserializes to/from quoted hex +//! strings in big-endian order with a 0x prefix and no leading zeros. +use std::fmt; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] +#[serde(transparent)] +pub struct U128( + #[serde( + serialize_with = "serialize_u128", + deserialize_with = "deserialize_128" + )] + pub u128, +); + +fn serialize_u128(n: &u128, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&format!("0x{n:x}")) +} + +fn deserialize_128<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let mut s = String::deserialize(deserializer)?; + if s.starts_with("0x") { + s = s[2..].to_string(); + } + u128::from_str_radix(&s, 16).map_err(serde::de::Error::custom) +} + +impl fmt::Display for U128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:x}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_u128_json() { + let json1 = "\"0x123456789abcdef0123456789abcdef0\""; + let n: U128 = serde_json::from_str(json1).unwrap(); + let json2 = serde_json::to_string(&n).unwrap(); + assert_eq!(json1, json2); + } + + #[test] + fn test_u128_display() { + let n = U128(0x526f2623ce); + assert_eq!(format!("{n}"), "0x526f2623ce"); + } +} From 811c921452849dae6453648b08caf090cbf89f22 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Sat, 26 Oct 2024 17:42:48 -0700 Subject: [PATCH 07/19] added ChainMainMin --- zmq/types/src/json_message_types.rs | 53 ++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index abe0b32b2..790a88099 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -1,10 +1,10 @@ //! Objects for JSON serialization and deserialization in message bodies of //! the ZMQ pub/sub interface. Handles JSON for the following subscriptions: -//! * `json-full-txpool_add` -//! * `json-minimal-txpool_add` -//! * `json-full-chain_main` -//! * `json-minimal-chain_main` -//! * `json-full-miner_data` +//! * `json-full-txpool_add` (`Vec`) +//! * `json-minimal-txpool_add` (`Vec`) +//! * `json-full-chain_main` (`Vec`) +//! * `json-minimal-chain_main` (`ChainMainMin`) +//! * `json-full-miner_data` (`MinerData`) use serde::{Deserialize, Serialize}; use crate::{bytes_hex::Bytes, u128_hex::U128}; @@ -18,6 +18,7 @@ use crate::{bytes_hex::Bytes, u128_hex::U128}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { pub version: u8, + // If not 0, this is the block height when a transaction output is spendable. pub unlock_time: u64, pub inputs: Vec, pub outputs: Vec, @@ -32,13 +33,15 @@ pub struct TxPoolAdd { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAddMin { pub id: Bytes<32>, + // size of the full transaction blob pub blob_size: u64, pub weight: u64, + // mining fee included in the transaction in piconeros pub fee: u64, } #[derive(Debug, Serialize, Deserialize)] -pub struct FullChainMain { +pub struct ChainMain { /// The major version of the monero protocol at the next? block height. pub major_version: u8, /// The minor version of the monero protocol at the next? block height. @@ -56,16 +59,25 @@ pub struct FullChainMain { pub tx_hashes: Vec>, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ChainMainMin { + pub first_height: u64, + pub first_prev_id: Bytes<32>, + pub ids: Vec>, +} + /// ZMQ `json-full-miner_data` subscriber messages contain a single /// `FullMinerData` object that provides the necessary data to create a /// custom block template. There is no min version of this object. #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct FullMinerData { +pub struct MinerData { pub major_version: u8, pub height: u64, pub prev_id: Bytes<32>, pub seed_hash: Bytes<32>, + // Least-significant 64 bits of the 128-bit network difficulty pub difficulty: U128, + // Median adjusted block size of latest 100000 blocks pub median_weight: u64, /// Fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission. /// Note that not all JSON parsers can handle this large value correctly. @@ -350,7 +362,7 @@ mod tests { } #[test] - fn test_full_chain_main_json() { + fn test_chain_main_json() { let json1 = json!([ { "major_version": 16, @@ -471,13 +483,28 @@ mod tests { } ]); - let full_chain_main: Vec = serde_json::from_value(json1.clone()).unwrap(); - let json2 = serde_json::to_value(&full_chain_main).unwrap(); + let chain_main: Vec = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&chain_main).unwrap(); + assert_json_eq!(json1, json2); + } + + #[test] + fn test_chain_main_min_json() { + let json1 = json!({ + "first_height": 3242758, + "first_prev_id": "ce3731311b7e4c1e58a2fe902dbb5c60bb2c0decc163d5397fa52a260d7f09c1", + "ids": [ + "ee1238b884e64f7e438223aa8d42d0efc15e7640f1a432448fbad116dc72f1b2" + ] + }); + + let chain_main_min: ChainMainMin = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&chain_main_min).unwrap(); assert_json_eq!(json1, json2); } #[test] - fn test_full_miner_data_json() { + fn test_miner_data_json() { let json1 = json!({ "major_version": 16, "height": 3242764, @@ -510,8 +537,8 @@ mod tests { ] }); - let full_miner_data: FullMinerData = serde_json::from_value(json1.clone()).unwrap(); - let json2 = serde_json::to_value(&full_miner_data).unwrap(); + let miner_data: MinerData = serde_json::from_value(json1.clone()).unwrap(); + let json2 = serde_json::to_value(&miner_data).unwrap(); assert_json_eq!(json1, json2); } } From 7292bba780f2ec1b869f35e2f0b6bc931d0a515d Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Mon, 28 Oct 2024 15:18:35 -0700 Subject: [PATCH 08/19] fixes for tx extra, switched to existing HexBytes --- Cargo.lock | 1 + zmq/types/Cargo.toml | 1 + zmq/types/src/bytes_hex.rs | 75 --------------------------- zmq/types/src/json_message_types.rs | 79 ++++++++++++++++------------- zmq/types/src/lib.rs | 3 +- 5 files changed, 46 insertions(+), 113 deletions(-) delete mode 100644 zmq/types/src/bytes_hex.rs diff --git a/Cargo.lock b/Cargo.lock index eb2fad09e..64a16b7ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -966,6 +966,7 @@ name = "cuprate-zmq-types" version = "0.1.0" dependencies = [ "assert-json-diff", + "cuprate-types", "hex", "serde", "serde_json", diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml index fa3e86ac3..437068ac5 100644 --- a/zmq/types/Cargo.toml +++ b/zmq/types/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["std"] } hex = { workspace = true, features = ["std", "serde"] } +cuprate-types = { path = "../../types", features = ["hex"] } [dev-dependencies] assert-json-diff = "2.0.2" diff --git a/zmq/types/src/bytes_hex.rs b/zmq/types/src/bytes_hex.rs deleted file mode 100644 index 687b9eaf7..000000000 --- a/zmq/types/src/bytes_hex.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Wrapper for fixed-size arrays of `u8` to provide serde serialization -//! and deserialization to and from hex strings. -use std::fmt; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -/// Bytes serializes to hex strings have no leading 0x and exactly N*2 -/// nibbles (no zeros are stripped). -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Bytes( - #[serde( - serialize_with = "serialize_bytes", - deserialize_with = "deserialize_bytes" - )] - pub [u8; N], -); - -impl Default for Bytes { - fn default() -> Self { - Self([0; N]) - } -} - -fn serialize_bytes(bytes: &[u8; N], serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&hex::encode(bytes)) -} - -fn deserialize_bytes<'de, const N: usize, D>(deserializer: D) -> Result<[u8; N], D::Error> -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - let mut bytes = [0_u8; N]; - hex::decode_to_slice(s, &mut bytes).map_err(serde::de::Error::custom)?; - Ok(bytes) -} - -impl fmt::Display for Bytes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(self.0))?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use serde_json; - - use super::*; - - #[test] - fn test_bytes_default() { - let bytes = Bytes::<32>::default(); - assert_eq!(bytes.0, [0; 32]); - } - - #[test] - fn test_bytes_json() { - let json1 = "\"536f91da278f730f2524260d2778dc5959d40a5c724dd789d35bbd309eabd933\""; - let array: Bytes<32> = serde_json::from_str(json1).unwrap(); - let json2 = serde_json::to_string(&array).unwrap(); - assert_eq!(json1, json2); - } - - #[test] - fn test_bytes_display() { - let hex_str = "98f1e11d62b90c665a8a96fb1b10332e37a790ea1e01a9e8ec8de74b7b27b0df"; - let bytes = Bytes::<32>(hex::decode(hex_str).unwrap().try_into().unwrap()); - assert_eq!(format!("{bytes}"), hex_str); - } -} diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 790a88099..115330178 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -5,9 +5,10 @@ //! * `json-full-chain_main` (`Vec`) //! * `json-minimal-chain_main` (`ChainMainMin`) //! * `json-full-miner_data` (`MinerData`) +use cuprate_types::hex::HexBytes; use serde::{Deserialize, Serialize}; -use crate::{bytes_hex::Bytes, u128_hex::U128}; +use crate::u128_hex::U128; /// ZMQ `json-full-txpool_add` packets contain an array of `TxPoolAdd`. /// @@ -18,11 +19,14 @@ use crate::{bytes_hex::Bytes, u128_hex::U128}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { pub version: u8, - // If not 0, this is the block height when a transaction output is spendable. + /// If not 0, this is the block height when a transaction output is spendable. pub unlock_time: u64, pub inputs: Vec, pub outputs: Vec, - pub extra: Bytes<44>, + /// Extra data for the transaction with variable size, but limited to 1060 bytes + /// (after hex decoding). + #[serde(with = "hex::serde")] + pub extra: Vec, signatures: Vec, pub ringct: PoolRingCt, } @@ -32,11 +36,11 @@ pub struct TxPoolAdd { /// transactions are published to subscribers. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAddMin { - pub id: Bytes<32>, - // size of the full transaction blob + pub id: HexBytes<32>, + /// size of the full transaction blob pub blob_size: u64, pub weight: u64, - // mining fee included in the transaction in piconeros + /// mining fee included in the transaction in piconeros pub fee: u64, } @@ -49,21 +53,21 @@ pub struct ChainMain { /// Unix epoch time, decided by the miner, at which the block was mined. pub timestamp: u64, /// Hash of the most recent block on which to mine the next block - pub prev_id: Bytes<32>, + pub prev_id: HexBytes<32>, /// a cryptographic random one-time number used in mining a Monero block pub nonce: u32, /// Miner transaction information pub miner_tx: MinerTx, /// List of hashes of non-coinbase transactions in the block. If there are no /// other transactions, this will be an empty list. - pub tx_hashes: Vec>, + pub tx_hashes: Vec>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ChainMainMin { pub first_height: u64, - pub first_prev_id: Bytes<32>, - pub ids: Vec>, + pub first_prev_id: HexBytes<32>, + pub ids: Vec>, } /// ZMQ `json-full-miner_data` subscriber messages contain a single @@ -73,11 +77,11 @@ pub struct ChainMainMin { pub struct MinerData { pub major_version: u8, pub height: u64, - pub prev_id: Bytes<32>, - pub seed_hash: Bytes<32>, - // Least-significant 64 bits of the 128-bit network difficulty + pub prev_id: HexBytes<32>, + pub seed_hash: HexBytes<32>, + /// Least-significant 64 bits of the 128-bit network difficulty pub difficulty: U128, - // Median adjusted block size of latest 100000 blocks + /// Median adjusted block size of latest 100000 blocks pub median_weight: u64, /// Fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission. /// Note that not all JSON parsers can handle this large value correctly. @@ -103,9 +107,12 @@ pub struct Gen { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ToKey { + /// The amount of the transaction input, in piconeros pub amount: u64, + /// List of integer offsets to outputs pub key_offsets: Vec, - pub key_image: Bytes<32>, + /// Key image for the given input + pub key_image: HexBytes<32>, } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] @@ -116,15 +123,15 @@ pub struct Output { #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct ToTaggedKey { - pub key: Bytes<32>, - pub view_tag: Bytes<1>, + pub key: HexBytes<32>, + pub view_tag: HexBytes<1>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { pub r#type: u8, pub encrypted: Vec, - pub commitments: Vec>, + pub commitments: Vec>, pub fee: u64, pub prunable: Prunable, } @@ -136,8 +143,8 @@ pub struct MinerRingCt { #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Encrypted { - pub mask: Bytes<32>, - pub amount: Bytes<32>, + pub mask: HexBytes<32>, + pub amount: HexBytes<32>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -145,23 +152,23 @@ pub struct Prunable { range_proofs: Vec, bulletproofs: Vec, pub bulletproofs_plus: Vec, - pub mlsags: Vec>, + pub mlsags: Vec>, pub clsags: Vec, - pub pseudo_outs: Vec>, + pub pseudo_outs: Vec>, } #[expect(non_snake_case)] #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct BulletproofPlus { - pub V: Vec>, - pub A: Bytes<32>, - pub A1: Bytes<32>, - pub B: Bytes<32>, - pub r1: Bytes<32>, - pub s1: Bytes<32>, - pub d1: Bytes<32>, - pub L: Vec>, - pub R: Vec>, + pub V: Vec>, + pub A: HexBytes<32>, + pub A1: HexBytes<32>, + pub B: HexBytes<32>, + pub r1: HexBytes<32>, + pub s1: HexBytes<32>, + pub d1: HexBytes<32>, + pub L: Vec>, + pub R: Vec>, } /// Placeholder element type so obsolete fields can be deserialized @@ -172,9 +179,9 @@ struct Obsolete; #[expect(non_snake_case)] #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Clsag { - pub s: Vec>, - pub c1: Bytes<32>, - pub D: Bytes<32>, + pub s: Vec>, + pub c1: HexBytes<32>, + pub D: HexBytes<32>, } #[derive(Debug, Serialize, Deserialize)] @@ -186,14 +193,14 @@ pub struct MinerTx { pub inputs: Vec, /// list of transaction outputs pub outputs: Vec, - pub extra: Bytes<52>, + pub extra: HexBytes<52>, signatures: Vec, pub ringct: MinerRingCt, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxBacklog { - pub id: Bytes<32>, + pub id: HexBytes<32>, pub weight: u64, pub fee: u64, } diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs index aaef028e1..6f39ab204 100644 --- a/zmq/types/src/lib.rs +++ b/zmq/types/src/lib.rs @@ -1,5 +1,4 @@ use serde_json as _; -pub mod bytes_hex; pub mod json_message_types; -mod u128_hex; +pub mod u128_hex; From d4a699845ebb9d125a47bdc70fc22761e6cb17fa Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Mon, 28 Oct 2024 22:20:52 -0700 Subject: [PATCH 09/19] documentation updates --- zmq/types/src/json_message_types.rs | 70 +++++++++++++++++++---------- zmq/types/src/u128_hex.rs | 4 +- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 115330178..c1e1ce25f 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -19,15 +19,21 @@ use crate::u128_hex::U128; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { pub version: u8, - /// If not 0, this is the block height when a transaction output is spendable. + /// if not 0, this is usually the block height when transaction output(s) + /// are spendable; if the value is over 500,000,000, it is the unix epoch + /// block timestamp after when the output(s) are spendable pub unlock_time: u64, + /// list of previous outputs (real and decoys) used as inputs to the transaction pub inputs: Vec, + /// transaction outputs pub outputs: Vec, - /// Extra data for the transaction with variable size, but limited to 1060 bytes - /// (after hex decoding). + /// extra data for the transaction with variable size, but should not exceed + /// 1060 bytes (2120 hex nibbles). #[serde(with = "hex::serde")] pub extra: Vec, + /// obsolete, empty list in JSON signatures: Vec, + /// ring confidential transaction data pub ringct: PoolRingCt, } @@ -39,6 +45,7 @@ pub struct TxPoolAddMin { pub id: HexBytes<32>, /// size of the full transaction blob pub blob_size: u64, + /// metric used to calculate transaction fee pub weight: u64, /// mining fee included in the transaction in piconeros pub fee: u64, @@ -46,20 +53,19 @@ pub struct TxPoolAddMin { #[derive(Debug, Serialize, Deserialize)] pub struct ChainMain { - /// The major version of the monero protocol at the next? block height. + /// major version of the monero protocol at the next? block height pub major_version: u8, - /// The minor version of the monero protocol at the next? block height. + /// minor version of the monero protocol at the next? block height pub minor_version: u8, - /// Unix epoch time, decided by the miner, at which the block was mined. + /// epoch time, decided by the miner, at which the block was mined pub timestamp: u64, - /// Hash of the most recent block on which to mine the next block + /// hash of the most recent block on which to mine the next block pub prev_id: HexBytes<32>, - /// a cryptographic random one-time number used in mining a Monero block + /// cryptographic random one-time number used in mining a Monero block pub nonce: u32, - /// Miner transaction information + /// miner transaction information pub miner_tx: MinerTx, - /// List of hashes of non-coinbase transactions in the block. If there are no - /// other transactions, this will be an empty list. + /// list of non-coinbase transaction IDs in the block (can be empty) pub tx_hashes: Vec>, } @@ -79,14 +85,13 @@ pub struct MinerData { pub height: u64, pub prev_id: HexBytes<32>, pub seed_hash: HexBytes<32>, - /// Least-significant 64 bits of the 128-bit network difficulty + /// least-significant 64 bits of the 128-bit network difficulty pub difficulty: U128, - /// Median adjusted block size of latest 100000 blocks + /// median adjusted block size of latest 100000 blocks pub median_weight: u64, - /// Fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission. - /// Note that not all JSON parsers can handle this large value correctly. + /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission pub already_generated_coins: u64, - /// List of mineable mempool transactions + /// list of mineable mempool transactions pub tx_backlog: Vec, } @@ -95,26 +100,30 @@ pub struct PoolInput { pub to_key: ToKey, } +/// Holds the block height of the coinbase transaction. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerInput { + /// gen is another name for a coinbase transaction pub r#gen: Gen, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Gen { + /// block height when the coinbase transaction was created pub height: u64, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ToKey { - /// The amount of the transaction input, in piconeros - pub amount: u64, - /// List of integer offsets to outputs + /// obsolete field (always 0), amounts are now encrypted + amount: u64, + /// list of integer offsets to outputs pub key_offsets: Vec, - /// Key image for the given input + /// key image for the given input pub key_image: HexBytes<32>, } +// Transaction output data used by both `TxPoolAdd` and `MinerTx` #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Output { pub amount: u64, @@ -124,18 +133,22 @@ pub struct Output { #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct ToTaggedKey { pub key: HexBytes<32>, + /// tag between 0 and 255 that speeds wallet sync times pub view_tag: HexBytes<1>, } +// ring CT type for `TxPoolAdd` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { pub r#type: u8, pub encrypted: Vec, pub commitments: Vec>, + /// mining fee in piconeros pub fee: u64, pub prunable: Prunable, } +// ring CT type for `MinerTx` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerRingCt { pub r#type: u8, @@ -143,16 +156,20 @@ pub struct MinerRingCt { #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Encrypted { - pub mask: HexBytes<32>, + /// obsolete field, but present as zeros in JSON + mask: HexBytes<32>, pub amount: HexBytes<32>, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { + /// obsolete, empty list in JSON range_proofs: Vec, + /// obsolete, empty list in JSON bulletproofs: Vec, pub bulletproofs_plus: Vec, - pub mlsags: Vec>, + /// obsolete, empty list in JSON + mlsags: Vec, pub clsags: Vec, pub pseudo_outs: Vec>, } @@ -186,22 +203,29 @@ pub struct Clsag { #[derive(Debug, Serialize, Deserialize)] pub struct MinerTx { + /// transaction version number pub version: u8, - /// The block height when the coinbase transaction becomes spendable + /// block height when the coinbase transaction becomes spendable (currently + /// 60 blocks above the coinbase transaction height) pub unlock_time: u64, /// list of transaction inputs pub inputs: Vec, /// list of transaction outputs pub outputs: Vec, + /// appears to be 52 bytes in practice, but can I miner set it differently? pub extra: HexBytes<52>, + /// obsolete, empty list in JSON signatures: Vec, pub ringct: MinerRingCt, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxBacklog { + /// transaction ID pub id: HexBytes<32>, + /// metric used to calculate transaction fee pub weight: u64, + /// mining fee in piconeros pub fee: u64, } diff --git a/zmq/types/src/u128_hex.rs b/zmq/types/src/u128_hex.rs index 793dedd55..1f3114707 100644 --- a/zmq/types/src/u128_hex.rs +++ b/zmq/types/src/u128_hex.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub struct U128( #[serde( serialize_with = "serialize_u128", - deserialize_with = "deserialize_128" + deserialize_with = "deserialize_u128" )] pub u128, ); @@ -21,7 +21,7 @@ where serializer.serialize_str(&format!("0x{n:x}")) } -fn deserialize_128<'de, D>(deserializer: D) -> Result +fn deserialize_u128<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { From f76c84d41427c2a84580000c2b3e1fbab722b3b4 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Tue, 29 Oct 2024 15:15:59 -0700 Subject: [PATCH 10/19] MinerRingCt made private --- zmq/types/src/json_message_types.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index c1e1ce25f..85b75537e 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -7,7 +7,6 @@ //! * `json-full-miner_data` (`MinerData`) use cuprate_types::hex::HexBytes; use serde::{Deserialize, Serialize}; - use crate::u128_hex::U128; /// ZMQ `json-full-txpool_add` packets contain an array of `TxPoolAdd`. @@ -42,6 +41,7 @@ pub struct TxPoolAdd { /// transactions are published to subscribers. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAddMin { + /// transaction ID pub id: HexBytes<32>, /// size of the full transaction blob pub blob_size: u64, @@ -77,7 +77,7 @@ pub struct ChainMainMin { } /// ZMQ `json-full-miner_data` subscriber messages contain a single -/// `FullMinerData` object that provides the necessary data to create a +/// `MinerData` object that provides the necessary data to create a /// custom block template. There is no min version of this object. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerData { @@ -137,9 +137,10 @@ pub struct ToTaggedKey { pub view_tag: HexBytes<1>, } -// ring CT type for `TxPoolAdd` +/// RingCT information used inside `TxPoolAdd` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { + // ring CT type; always 6 (`ClsagBulletproofPlus`) at the time of this writing pub r#type: u8, pub encrypted: Vec, pub commitments: Vec>, @@ -148,10 +149,12 @@ pub struct PoolRingCt { pub prunable: Prunable, } -// ring CT type for `MinerTx` +/// RingCT information used inside `MinerTx` (note: miner coinbase transactions +/// don't use RingCT). #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct MinerRingCt { - pub r#type: u8, +struct MinerRingCt { + /// always zero to indicate that RingCT is not used + r#type: u8, } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] @@ -161,6 +164,8 @@ pub struct Encrypted { pub amount: HexBytes<32>, } +/// Data needed to validate transactions that can, optionally, be pruned from +/// older blocks. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { /// obsolete, empty list in JSON @@ -193,6 +198,7 @@ pub struct BulletproofPlus { #[derive(Debug, Clone, Copy, Serialize, Deserialize)] struct Obsolete; +/// CLSAG signature fields #[expect(non_snake_case)] #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Clsag { @@ -212,11 +218,14 @@ pub struct MinerTx { pub inputs: Vec, /// list of transaction outputs pub outputs: Vec, - /// appears to be 52 bytes in practice, but can I miner set it differently? - pub extra: HexBytes<52>, + /// extra data for the transaction with variable size; does the 1060-byte + /// limit apply for miners? + #[serde(with = "hex::serde")] + pub extra: Vec, /// obsolete, empty list in JSON signatures: Vec, - pub ringct: MinerRingCt, + /// only for JSON compatibility; miner's don't use RingCT + ringct: MinerRingCt, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] From d301deb37844296587ce5f8839b95cfa23a61d0d Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Tue, 29 Oct 2024 22:21:44 -0700 Subject: [PATCH 11/19] more documentation, difficulty is u64 --- zmq/types/Cargo.toml | 2 +- zmq/types/src/json_message_types.rs | 68 ++++++++++++++++++++++++----- zmq/types/src/lib.rs | 3 -- zmq/types/src/u128_hex.rs | 58 ------------------------ 4 files changed, 57 insertions(+), 74 deletions(-) delete mode 100644 zmq/types/src/u128_hex.rs diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml index 437068ac5..415581236 100644 --- a/zmq/types/Cargo.toml +++ b/zmq/types/Cargo.toml @@ -9,11 +9,11 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" [dependencies] serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["std"] } hex = { workspace = true, features = ["std", "serde"] } cuprate-types = { path = "../../types", features = ["hex"] } [dev-dependencies] +serde_json = { workspace = true, features = ["std"] } assert-json-diff = "2.0.2" [lints] diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 85b75537e..7ee4d079e 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -7,7 +7,6 @@ //! * `json-full-miner_data` (`MinerData`) use cuprate_types::hex::HexBytes; use serde::{Deserialize, Serialize}; -use crate::u128_hex::U128; /// ZMQ `json-full-txpool_add` packets contain an array of `TxPoolAdd`. /// @@ -17,6 +16,7 @@ use crate::u128_hex::U128; /// republished during a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { + /// TODO: Document pub version: u8, /// if not 0, this is usually the block height when transaction output(s) /// are spendable; if the value is over 500,000,000, it is the unix epoch @@ -69,6 +69,7 @@ pub struct ChainMain { pub tx_hashes: Vec>, } +/// TODO: Document #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ChainMainMin { pub first_height: u64, @@ -81,12 +82,17 @@ pub struct ChainMainMin { /// custom block template. There is no min version of this object. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerData { + /// TODO: document pub major_version: u8, + /// height on which to mine pub height: u64, + /// hash of the most recent block on which to mine the next block pub prev_id: HexBytes<32>, + /// hash of block to use as seed for Random-X proof-of-work pub seed_hash: HexBytes<32>, /// least-significant 64 bits of the 128-bit network difficulty - pub difficulty: U128, + #[serde(with = "hex_difficulty")] + pub difficulty: u64, /// median adjusted block size of latest 100000 blocks pub median_weight: u64, /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission @@ -126,45 +132,54 @@ pub struct ToKey { // Transaction output data used by both `TxPoolAdd` and `MinerTx` #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Output { + /// zero for non-coinbase transactions which use encrypted amounts, + /// amount in piconeros for coinbase transactions pub amount: u64, + /// public key of the output destination pub to_tagged_key: ToTaggedKey, } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct ToTaggedKey { + // key used to indicate the destination of a transaction output pub key: HexBytes<32>, - /// tag between 0 and 255 that speeds wallet sync times + /// 1st byte of a shared secret used to reduce wallet synchronization time pub view_tag: HexBytes<1>, } -/// RingCT information used inside `TxPoolAdd` +/// Ring CT information used inside `TxPoolAdd` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { - // ring CT type; always 6 (`ClsagBulletproofPlus`) at the time of this writing + // ring CT type; 6 (CLSAG Bulletproof Plus) at the time of this writing pub r#type: u8, + // encrypted amount values of the transaction outputs pub encrypted: Vec, + // RingCT commitments, 1 per output pub commitments: Vec>, /// mining fee in piconeros pub fee: u64, + // data to validate the transaction pub prunable: Prunable, } -/// RingCT information used inside `MinerTx` (note: miner coinbase transactions -/// don't use RingCT). +/// Ring CT information used inside `MinerTx` (note: miner coinbase transactions +/// don't use Ring CT). #[derive(Debug, Default, Clone, Serialize, Deserialize)] struct MinerRingCt { - /// always zero to indicate that RingCT is not used + /// always zero to indicate that Ring CT is not used r#type: u8, } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Encrypted { - /// obsolete field, but present as zeros in JSON + /// obsolete field, but present as zeros in JSON; this does not represent + /// the newer deterministically derived mask mask: HexBytes<32>, + /// encrypted amount of the transaction output pub amount: HexBytes<32>, } -/// Data needed to validate transactions that can, optionally, be pruned from +/// Data needed to validate a transaction that can optionally be pruned from /// older blocks. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { @@ -172,15 +187,20 @@ pub struct Prunable { range_proofs: Vec, /// obsolete, empty list in JSON bulletproofs: Vec, + /// list of Bulletproofs+ data used to validate the legitimacy of a Ring CT transaction + /// TODO: Can this be a fixed size array of 1? pub bulletproofs_plus: Vec, /// obsolete, empty list in JSON mlsags: Vec, + /// TODO: Can this be a fixed size array of 1? pub clsags: Vec, + /// TODO: document pub pseudo_outs: Vec>, } -#[expect(non_snake_case)] +/// Bulletproofs+ data used to validate the legitimacy of a Ring CT transaction. #[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[expect(non_snake_case)] pub struct BulletproofPlus { pub V: Vec>, pub A: HexBytes<32>, @@ -224,7 +244,7 @@ pub struct MinerTx { pub extra: Vec, /// obsolete, empty list in JSON signatures: Vec, - /// only for JSON compatibility; miner's don't use RingCT + /// only for JSON compatibility; miner's don't use Ring CT ringct: MinerRingCt, } @@ -238,6 +258,30 @@ pub struct TxBacklog { pub fee: u64, } +mod hex_difficulty { + //! u64 difficulty serialization for monerod compatibility. The difficulty + //! value is inside a string, in big-endian hex, and has a 0x prefix with + //! no leading zeros. + use serde::{Deserialize, Deserializer, Serializer}; + + #[expect(clippy::trivially_copy_pass_by_ref)] + pub(super) fn serialize(difficulty: &u64, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("0x{difficulty:x}")) + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let s = s.strip_prefix("0x").unwrap_or(&s); + u64::from_str_radix(s, 16).map_err(serde::de::Error::custom) + } +} + #[cfg(test)] mod tests { use assert_json_diff::assert_json_eq; diff --git a/zmq/types/src/lib.rs b/zmq/types/src/lib.rs index 6f39ab204..3f9562b6f 100644 --- a/zmq/types/src/lib.rs +++ b/zmq/types/src/lib.rs @@ -1,4 +1 @@ -use serde_json as _; - pub mod json_message_types; -pub mod u128_hex; diff --git a/zmq/types/src/u128_hex.rs b/zmq/types/src/u128_hex.rs deleted file mode 100644 index 1f3114707..000000000 --- a/zmq/types/src/u128_hex.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Wrapper for u128 that serializes/deserializes to/from quoted hex -//! strings in big-endian order with a 0x prefix and no leading zeros. -use std::fmt; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct U128( - #[serde( - serialize_with = "serialize_u128", - deserialize_with = "deserialize_u128" - )] - pub u128, -); - -fn serialize_u128(n: &u128, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&format!("0x{n:x}")) -} - -fn deserialize_u128<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let mut s = String::deserialize(deserializer)?; - if s.starts_with("0x") { - s = s[2..].to_string(); - } - u128::from_str_radix(&s, 16).map_err(serde::de::Error::custom) -} - -impl fmt::Display for U128 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x{:x}", self.0) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_u128_json() { - let json1 = "\"0x123456789abcdef0123456789abcdef0\""; - let n: U128 = serde_json::from_str(json1).unwrap(); - let json2 = serde_json::to_string(&n).unwrap(); - assert_eq!(json1, json2); - } - - #[test] - fn test_u128_display() { - let n = U128(0x526f2623ce); - assert_eq!(format!("{n}"), "0x526f2623ce"); - } -} From d120285fad360d8f988032346650eb39c764b0d6 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Wed, 30 Oct 2024 22:30:35 -0700 Subject: [PATCH 12/19] documented more fields --- zmq/types/src/json_message_types.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 7ee4d079e..6ac110ea7 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize}; /// republished during a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { - /// TODO: Document + /// transaction version number. 2 indicates Ring CT (all variants). pub version: u8, /// if not 0, this is usually the block height when transaction output(s) /// are spendable; if the value is over 500,000,000, it is the unix epoch @@ -51,6 +51,9 @@ pub struct TxPoolAddMin { pub fee: u64, } +/// ZMQ `json-full-chain_main` subscriber messages contain an array of +/// `ChainMain` JSON objects. Each `ChainMain` object represents a new block. +/// Push messages only contain more than one block if a re-org occurred. #[derive(Debug, Serialize, Deserialize)] pub struct ChainMain { /// major version of the monero protocol at the next? block height @@ -69,11 +72,17 @@ pub struct ChainMain { pub tx_hashes: Vec>, } -/// TODO: Document +/// ZMQ `json-minimal-chain_main` subscriber messages contain a single +/// `ChainMainMin` JSON object. Unlike the full version, only the topmost +/// block is sent in the case of a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct ChainMainMin { + /// height of the block pub first_height: u64, + /// block id of the previous block pub first_prev_id: HexBytes<32>, + /// block ID of the current block is the 0th entry; additional block IDs + /// will only be included if this is the topmost block of a re-org. pub ids: Vec>, } @@ -93,7 +102,7 @@ pub struct MinerData { /// least-significant 64 bits of the 128-bit network difficulty #[serde(with = "hex_difficulty")] pub difficulty: u64, - /// median adjusted block size of latest 100000 blocks + /// median adjusted block size of the latest 100000 blocks pub median_weight: u64, /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission pub already_generated_coins: u64, @@ -248,6 +257,7 @@ pub struct MinerTx { ringct: MinerRingCt, } +/// Holds a transaction entry in the `MinerData` `tx_backlog` field. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxBacklog { /// transaction ID @@ -259,9 +269,9 @@ pub struct TxBacklog { } mod hex_difficulty { - //! u64 difficulty serialization for monerod compatibility. The difficulty - //! value is inside a string, in big-endian hex, and has a 0x prefix with - //! no leading zeros. + //! Serializes the u64 difficulty field of `MinerData` in the same ways as + //! monerod. The difficulty value is inside a string, in big-endian hex, and + //! has a 0x prefix with no leading zeros. use serde::{Deserialize, Deserializer, Serializer}; #[expect(clippy::trivially_copy_pass_by_ref)] From d06d4b8e35ff4aaf01460f1daee1332d621c6f36 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Thu, 31 Oct 2024 22:59:13 -0700 Subject: [PATCH 13/19] more documentation --- zmq/types/src/json_message_types.rs | 89 ++++++++++++++++------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 6ac110ea7..99234e294 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -16,13 +16,13 @@ use serde::{Deserialize, Serialize}; /// republished during a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { - /// transaction version number. 2 indicates Ring CT (all variants). + /// transaction version number. 2 indicates Ring CT (all sub-variants). pub version: u8, /// if not 0, this is usually the block height when transaction output(s) /// are spendable; if the value is over 500,000,000, it is the unix epoch /// block timestamp after when the output(s) are spendable pub unlock_time: u64, - /// list of previous outputs (real and decoys) used as inputs to the transaction + /// array of previous outputs (real and decoys) used as inputs to the transaction pub inputs: Vec, /// transaction outputs pub outputs: Vec, @@ -30,7 +30,7 @@ pub struct TxPoolAdd { /// 1060 bytes (2120 hex nibbles). #[serde(with = "hex::serde")] pub extra: Vec, - /// obsolete, empty list in JSON + /// obsolete, empty array in JSON signatures: Vec, /// ring confidential transaction data pub ringct: PoolRingCt, @@ -56,19 +56,19 @@ pub struct TxPoolAddMin { /// Push messages only contain more than one block if a re-org occurred. #[derive(Debug, Serialize, Deserialize)] pub struct ChainMain { - /// major version of the monero protocol at the next? block height + /// major version of the monero protocol at this block's height pub major_version: u8, - /// minor version of the monero protocol at the next? block height + /// minor version of the monero protocol at this block's height pub minor_version: u8, /// epoch time, decided by the miner, at which the block was mined pub timestamp: u64, - /// hash of the most recent block on which to mine the next block + /// block id of the previous block pub prev_id: HexBytes<32>, /// cryptographic random one-time number used in mining a Monero block pub nonce: u32, - /// miner transaction information + /// coinbase transaction information pub miner_tx: MinerTx, - /// list of non-coinbase transaction IDs in the block (can be empty) + /// array of non-coinbase transaction IDs in the block (can be empty) pub tx_hashes: Vec>, } @@ -91,11 +91,11 @@ pub struct ChainMainMin { /// custom block template. There is no min version of this object. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerData { - /// TODO: document + /// major version of the monero protocol for the next mined block pub major_version: u8, /// height on which to mine pub height: u64, - /// hash of the most recent block on which to mine the next block + /// block id of the most recent block on which to mine the next block pub prev_id: HexBytes<32>, /// hash of block to use as seed for Random-X proof-of-work pub seed_hash: HexBytes<32>, @@ -106,51 +106,56 @@ pub struct MinerData { pub median_weight: u64, /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission pub already_generated_coins: u64, - /// list of mineable mempool transactions + /// array of mineable mempool transactions pub tx_backlog: Vec, } +/// Holds a single input for the `TxPoolAdd` `inputs` array. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolInput { pub to_key: ToKey, } +/// Same as `PoolInput` (adds an extra JSON name layer) +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ToKey { + /// obsolete field (always 0), non-coinbase TX amounts are now encrypted + amount: u64, + /// array of integer offsets to the input + pub key_offsets: Vec, + /// key image for the given input + pub key_image: HexBytes<32>, +} + /// Holds the block height of the coinbase transaction. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct MinerInput { - /// gen is another name for a coinbase transaction + /// namespace layer around the block height pub r#gen: Gen, } +/// Yet another namespace layer around the block height in `ChainMain`; gen is +/// another name for a coinbase transaction #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Gen { /// block height when the coinbase transaction was created pub height: u64, } -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct ToKey { - /// obsolete field (always 0), amounts are now encrypted - amount: u64, - /// list of integer offsets to outputs - pub key_offsets: Vec, - /// key image for the given input - pub key_image: HexBytes<32>, -} - -// Transaction output data used by both `TxPoolAdd` and `MinerTx` +/// Transaction output data used by both `TxPoolAdd` and `MinerTx` #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Output { - /// zero for non-coinbase transactions which use encrypted amounts, - /// amount in piconeros for coinbase transactions + /// zero for non-coinbase transactions which use encrypted amounts or + /// an amount in piconeros for coinbase transactions pub amount: u64, /// public key of the output destination pub to_tagged_key: ToTaggedKey, } +/// Holds the public key of an output destination with its view tag. #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct ToTaggedKey { - // key used to indicate the destination of a transaction output + /// key used to indicate the destination of a transaction output pub key: HexBytes<32>, /// 1st byte of a shared secret used to reduce wallet synchronization time pub view_tag: HexBytes<1>, @@ -159,15 +164,15 @@ pub struct ToTaggedKey { /// Ring CT information used inside `TxPoolAdd` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { - // ring CT type; 6 (CLSAG Bulletproof Plus) at the time of this writing + /// ring CT type; 6 (CLSAG Bulletproof Plus) at the time of this writing pub r#type: u8, - // encrypted amount values of the transaction outputs + /// encrypted amount values of the transaction outputs pub encrypted: Vec, - // RingCT commitments, 1 per output + /// Ring CT commitments, 1 per output pub commitments: Vec>, /// mining fee in piconeros pub fee: u64, - // data to validate the transaction + /// data to validate the transaction pub prunable: Prunable, } @@ -192,18 +197,18 @@ pub struct Encrypted { /// older blocks. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { - /// obsolete, empty list in JSON + /// obsolete, empty array in JSON range_proofs: Vec, - /// obsolete, empty list in JSON + /// obsolete, empty array in JSON bulletproofs: Vec, - /// list of Bulletproofs+ data used to validate the legitimacy of a Ring CT transaction - /// TODO: Can this be a fixed size array of 1? + /// array of Bulletproofs+ data used to validate a Ring CT transaction; at + /// the current time this array always has exactly one element pub bulletproofs_plus: Vec, - /// obsolete, empty list in JSON + /// obsolete, empty array in JSON mlsags: Vec, - /// TODO: Can this be a fixed size array of 1? + /// CLSAG signatures (1 per non-decoy transaction input) pub clsags: Vec, - /// TODO: document + /// Ring CT pseudo output commitments (1 per non-decoy transaction input) pub pseudo_outs: Vec>, } @@ -236,6 +241,7 @@ pub struct Clsag { pub D: HexBytes<32>, } +/// Part of the new block information in `ChainMain` #[derive(Debug, Serialize, Deserialize)] pub struct MinerTx { /// transaction version number @@ -243,17 +249,18 @@ pub struct MinerTx { /// block height when the coinbase transaction becomes spendable (currently /// 60 blocks above the coinbase transaction height) pub unlock_time: u64, - /// list of transaction inputs + /// contains block height in inputs[0].gen.height and nothing else; coinbase + /// transactions have no inputs pub inputs: Vec, - /// list of transaction outputs + /// array of transaction outputs pub outputs: Vec, /// extra data for the transaction with variable size; does the 1060-byte /// limit apply for miners? #[serde(with = "hex::serde")] pub extra: Vec, - /// obsolete, empty list in JSON + /// obsolete, empty array in JSON signatures: Vec, - /// only for JSON compatibility; miner's don't use Ring CT + /// only for JSON compatibility; miners' don't use Ring CT ringct: MinerRingCt, } From bf48d4dc444b3dd16f78dca814094aaf59e06739 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Fri, 1 Nov 2024 12:02:32 -0700 Subject: [PATCH 14/19] all fields/types documented --- zmq/types/src/json_message_types.rs | 54 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 99234e294..bbea64124 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -16,18 +16,18 @@ use serde::{Deserialize, Serialize}; /// republished during a re-org. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TxPoolAdd { - /// transaction version number. 2 indicates Ring CT (all sub-variants). + /// transaction version number. `2` indicates Ring CT (all sub-variants). pub version: u8, - /// if not 0, this is usually the block height when transaction output(s) - /// are spendable; if the value is over 500,000,000, it is the unix epoch - /// block timestamp after when the output(s) are spendable + /// if not `0` and less than `500_000_000`, this is the block height when + /// transaction output(s) are spendable; if >= `500_000_000` this is roughly + /// the unix epoch block timestamp when the output(s) are spendable. pub unlock_time: u64, - /// array of previous outputs (real and decoys) used as inputs to the transaction + /// transaction inputs (key images) with separate rings for each input pub inputs: Vec, /// transaction outputs pub outputs: Vec, - /// extra data for the transaction with variable size, but should not exceed - /// 1060 bytes (2120 hex nibbles). + /// extra data for the transaction with variable size, but limited to `1060` + /// bytes (`2120` hex nibbles). #[serde(with = "hex::serde")] pub extra: Vec, /// obsolete, empty array in JSON @@ -68,7 +68,7 @@ pub struct ChainMain { pub nonce: u32, /// coinbase transaction information pub miner_tx: MinerTx, - /// array of non-coinbase transaction IDs in the block (can be empty) + /// non-coinbase transaction IDs in the block (can be empty) pub tx_hashes: Vec>, } @@ -106,7 +106,7 @@ pub struct MinerData { pub median_weight: u64, /// fixed at `u64::MAX` in perpetuity as Monero has already reached tail emission pub already_generated_coins: u64, - /// array of mineable mempool transactions + /// mineable mempool transactions pub tx_backlog: Vec, } @@ -121,7 +121,7 @@ pub struct PoolInput { pub struct ToKey { /// obsolete field (always 0), non-coinbase TX amounts are now encrypted amount: u64, - /// array of integer offsets to the input + /// integer offsets for ring members pub key_offsets: Vec, /// key image for the given input pub key_image: HexBytes<32>, @@ -134,7 +134,7 @@ pub struct MinerInput { pub r#gen: Gen, } -/// Yet another namespace layer around the block height in `ChainMain`; gen is +/// Additional namespace layer around the block height in `ChainMain`; gen is /// another name for a coinbase transaction #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Gen { @@ -155,7 +155,7 @@ pub struct Output { /// Holds the public key of an output destination with its view tag. #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct ToTaggedKey { - /// key used to indicate the destination of a transaction output + /// public key used to indicate the destination of a transaction output pub key: HexBytes<32>, /// 1st byte of a shared secret used to reduce wallet synchronization time pub view_tag: HexBytes<1>, @@ -164,26 +164,27 @@ pub struct ToTaggedKey { /// Ring CT information used inside `TxPoolAdd` #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct PoolRingCt { - /// ring CT type; 6 (CLSAG Bulletproof Plus) at the time of this writing + /// ring CT type; `6` is CLSAG Bulletproof Plus pub r#type: u8, /// encrypted amount values of the transaction outputs pub encrypted: Vec, - /// Ring CT commitments, 1 per output + /// Ring CT commitments, 1 per transaction input pub commitments: Vec>, /// mining fee in piconeros pub fee: u64, - /// data to validate the transaction + /// data to validate the transaction that can be pruned from older blocks pub prunable: Prunable, } -/// Ring CT information used inside `MinerTx` (note: miner coinbase transactions -/// don't use Ring CT). +/// Ring CT information used inside `MinerTx`. Miner coinbase transactions don't +/// use Ring CT, so this only holds a block height. #[derive(Debug, Default, Clone, Serialize, Deserialize)] struct MinerRingCt { /// always zero to indicate that Ring CT is not used r#type: u8, } +/// Holds the encrypted amount of a non-coinbase transaction output. #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] pub struct Encrypted { /// obsolete field, but present as zeros in JSON; this does not represent @@ -201,14 +202,15 @@ pub struct Prunable { range_proofs: Vec, /// obsolete, empty array in JSON bulletproofs: Vec, - /// array of Bulletproofs+ data used to validate a Ring CT transaction; at - /// the current time this array always has exactly one element + /// Bulletproofs+ data used to validate a Ring CT transaction; at the + /// current time this array always has exactly one element pub bulletproofs_plus: Vec, /// obsolete, empty array in JSON mlsags: Vec, - /// CLSAG signatures (1 per non-decoy transaction input) + /// CLSAG signatures; 1 per transaction input pub clsags: Vec, - /// Ring CT pseudo output commitments (1 per non-decoy transaction input) + /// Ring CT pseudo output commitments; 1 per transaction input (*not* + /// output) pub pseudo_outs: Vec>, } @@ -249,13 +251,13 @@ pub struct MinerTx { /// block height when the coinbase transaction becomes spendable (currently /// 60 blocks above the coinbase transaction height) pub unlock_time: u64, - /// contains block height in inputs[0].gen.height and nothing else; coinbase - /// transactions have no inputs + /// contains the block height in `inputs[0].gen.height` and nothing else as + /// coinbase transactions have no inputs pub inputs: Vec, - /// array of transaction outputs + /// transaction outputs pub outputs: Vec, - /// extra data for the transaction with variable size; does the 1060-byte - /// limit apply for miners? + /// extra data for the transaction with variable size; not limited to `1060` + /// bytes like the extra field of non-coinbase transactions #[serde(with = "hex::serde")] pub extra: Vec, /// obsolete, empty array in JSON From 863a77dca5c2b3b08a8ee550e5caad263ca55d10 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Fri, 1 Nov 2024 21:16:40 -0700 Subject: [PATCH 15/19] workspace dependencies for cuprate crates --- Cargo.toml | 1 + zmq/types/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fcaee9f0f..abf607c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ cuprate-types = { path = "types" ,default-feature cuprate-json-rpc = { path = "rpc/json-rpc" ,default-features = false} cuprate-rpc-types = { path = "rpc/types" ,default-features = false} cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false} +cuprate-zmq-types = { path = "zmq/types" ,default-features = false} # External dependencies anyhow = { version = "1.0.89", default-features = false } diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml index 415581236..ad7468685 100644 --- a/zmq/types/Cargo.toml +++ b/zmq/types/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" [dependencies] serde = { workspace = true, features = ["derive"] } hex = { workspace = true, features = ["std", "serde"] } -cuprate-types = { path = "../../types", features = ["hex"] } +cuprate-types = { workspace = true, features = ["hex"] } [dev-dependencies] serde_json = { workspace = true, features = ["std"] } From ce822f99f62cfdb020211dc98ac068f13022570c Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Fri, 1 Nov 2024 21:28:28 -0700 Subject: [PATCH 16/19] better declarations for empty JSON arrays --- zmq/types/src/json_message_types.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index bbea64124..7f3309c8a 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -31,7 +31,7 @@ pub struct TxPoolAdd { #[serde(with = "hex::serde")] pub extra: Vec, /// obsolete, empty array in JSON - signatures: Vec, + signatures: [Obsolete; 0], /// ring confidential transaction data pub ringct: PoolRingCt, } @@ -199,14 +199,14 @@ pub struct Encrypted { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Prunable { /// obsolete, empty array in JSON - range_proofs: Vec, + range_proofs: [Obsolete; 0], /// obsolete, empty array in JSON - bulletproofs: Vec, + bulletproofs: [Obsolete; 0], /// Bulletproofs+ data used to validate a Ring CT transaction; at the /// current time this array always has exactly one element pub bulletproofs_plus: Vec, /// obsolete, empty array in JSON - mlsags: Vec, + mlsags: [Obsolete; 0], /// CLSAG signatures; 1 per transaction input pub clsags: Vec, /// Ring CT pseudo output commitments; 1 per transaction input (*not* @@ -261,7 +261,7 @@ pub struct MinerTx { #[serde(with = "hex::serde")] pub extra: Vec, /// obsolete, empty array in JSON - signatures: Vec, + signatures: [Obsolete; 0], /// only for JSON compatibility; miners' don't use Ring CT ringct: MinerRingCt, } From ef74d2150a971cf28c79606fe530d5a98b1cd95f Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Fri, 1 Nov 2024 21:50:24 -0700 Subject: [PATCH 17/19] added ZMQ section to crates doc --- books/architecture/src/appendix/crates.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/books/architecture/src/appendix/crates.md b/books/architecture/src/appendix/crates.md index ac2780e1f..a0dff4818 100644 --- a/books/architecture/src/appendix/crates.md +++ b/books/architecture/src/appendix/crates.md @@ -54,6 +54,11 @@ cargo doc --open --package cuprate-blockchain | [`cuprate-rpc-interface`](https://doc.cuprate.org/cuprate_rpc_interface) | [`rpc/interface/`](https://github.com/Cuprate/cuprate/tree/main/rpc/interface) | RPC interface & routing | [`cuprate-rpc-handler`](https://doc.cuprate.org/cuprate_rpc_handler) | [`rpc/handler/`](https://github.com/Cuprate/cuprate/tree/main/rpc/handler) | RPC inner handlers +## ZMQ +| Crate | In-tree path | Purpose | +|-------|--------------|---------| +| [`cuprate-zmq-types`](https://doc.cuprate.org/cuprate_zmq_types) | [`zmq/types/`](https://github.com/Cuprate/cuprate/tree/main/zmq/types) | Message types for ZMQ Pub/Sub interface + ## 1-off crates | Crate | In-tree path | Purpose | |-------|--------------|---------| From dbaad2773d4ac03b8ab51df359706ecb698e65f0 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Wed, 20 Nov 2024 12:34:15 -0600 Subject: [PATCH 18/19] authors update Co-authored-by: Boog900 --- zmq/types/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zmq/types/Cargo.toml b/zmq/types/Cargo.toml index ad7468685..78e7d00a4 100644 --- a/zmq/types/Cargo.toml +++ b/zmq/types/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" description = "Types for the ZMQ Pub/Sub API" license = "MIT" -authors = ["dimalinux", "The Monero Project"] +authors = ["dimalinux"] repository = "https://github.com/Cuprate/cuprate/tree/main/zmq/types" [dependencies] From a719f7a48c62050f686394084e137d32f25824d8 Mon Sep 17 00:00:00 2001 From: Dmitry Holodov Date: Wed, 20 Nov 2024 14:07:32 -0600 Subject: [PATCH 19/19] converted 1-item-only vectors into arrays --- zmq/types/src/json_message_types.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/zmq/types/src/json_message_types.rs b/zmq/types/src/json_message_types.rs index 7f3309c8a..2699600f4 100644 --- a/zmq/types/src/json_message_types.rs +++ b/zmq/types/src/json_message_types.rs @@ -202,9 +202,8 @@ pub struct Prunable { range_proofs: [Obsolete; 0], /// obsolete, empty array in JSON bulletproofs: [Obsolete; 0], - /// Bulletproofs+ data used to validate a Ring CT transaction; at the - /// current time this array always has exactly one element - pub bulletproofs_plus: Vec, + /// Bulletproofs+ data used to validate a Ring CT transaction + pub bulletproofs_plus: [BulletproofPlus; 1], /// obsolete, empty array in JSON mlsags: [Obsolete; 0], /// CLSAG signatures; 1 per transaction input @@ -253,7 +252,7 @@ pub struct MinerTx { pub unlock_time: u64, /// contains the block height in `inputs[0].gen.height` and nothing else as /// coinbase transactions have no inputs - pub inputs: Vec, + pub inputs: [MinerInput; 1], /// transaction outputs pub outputs: Vec, /// extra data for the transaction with variable size; not limited to `1060`