diff --git a/Cargo.lock b/Cargo.lock index aa315d8b..c434aed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,10 +438,10 @@ dependencies = [ "curve25519-dalek", "dalek-ff-group", "dirs", + "epee-encoding", "futures", "hex", "monero-consensus", - "monero-epee-bin-serde", "monero-serai", "monero-wire", "multiexp", @@ -604,6 +604,7 @@ dependencies = [ "fixed-bytes", "hex", "paste", + "ref-cast", "sealed", "thiserror", ] @@ -1142,16 +1143,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "monero-epee-bin-serde" -version = "1.0.1" -source = "git+https://github.com/monero-rs/monero-epee-bin-serde.git?rev=fae7a23#fae7a23f8e57f19553c341c0878b4f0fa5a6994d" -dependencies = [ - "byteorder", - "serde", - "serde_bytes", -] - [[package]] name = "monero-generators" version = "0.4.0" @@ -1232,11 +1223,11 @@ dependencies = [ name = "monero-wire" version = "0.1.0" dependencies = [ + "bytes", + "epee-encoding", + "fixed-bytes", "hex", "levin-cuprate", - "monero-epee-bin-serde", - "serde", - "serde_bytes", "thiserror", ] @@ -1587,6 +1578,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "regex-syntax" version = "0.8.2" @@ -1770,15 +1781,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.195" diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 8619a33e..393c4dfd 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -21,7 +21,7 @@ binaries = [ "dep:tracing-subscriber", "dep:serde_json", "dep:serde", - "dep:monero-epee-bin-serde", + "dep:epee-encoding", "dep:monero-wire", "dep:borsh", "dep:dirs", @@ -51,7 +51,7 @@ hex = "0.4" # used in binaries monero-wire = {path="../net/monero-wire", optional = true} -monero-epee-bin-serde = { workspace = true , optional = true} +epee-encoding = { path="../net/epee-encoding" , optional = true} serde_json = {version = "1", optional = true} serde = {version = "1", optional = true, features = ["derive"]} tracing-subscriber = {version = "0.3", optional = true} diff --git a/consensus/src/rpc/connection.rs b/consensus/src/rpc/connection.rs index 7fd8707c..780d160e 100644 --- a/consensus/src/rpc/connection.rs +++ b/consensus/src/rpc/connection.rs @@ -1,3 +1,4 @@ +use std::ops::Deref; use std::{ collections::{HashMap, HashSet}, ops::Range, @@ -15,9 +16,9 @@ use monero_serai::{ rpc::{HttpRpc, Rpc}, transaction::Transaction, }; -use monero_wire::common::{BlockCompleteEntry, TransactionBlobs}; +use monero_wire::common::TransactionBlobs; use rayon::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::json; use tokio::{ sync::RwLock, @@ -174,41 +175,56 @@ impl RpcConnection { ) -> Result)>, tower::BoxError> { tracing::info!("Getting blocks in range: {:?}", range); - #[derive(Serialize)] - pub struct Request { - pub heights: Vec, - } + mod items { + use monero_wire::common::BlockCompleteEntry; - #[derive(Deserialize)] - pub struct Response { - pub blocks: Vec, + pub struct Request { + pub heights: Vec, + } + + epee_encoding::epee_object!( + Request, + heights: Vec, + ); + + pub struct Response { + pub blocks: Vec, + } + + epee_encoding::epee_object!( + Response, + blocks: Vec, + ); } + use items::*; let res = self .con .bin_call( "get_blocks_by_height.bin", - monero_epee_bin_serde::to_bytes(&Request { + epee_encoding::to_bytes(Request { heights: range.collect(), - })?, + })? + .to_vec(), ) .await?; let address = self.address.clone(); rayon_spawn_async(move || { - let blocks: Response = monero_epee_bin_serde::from_bytes(res)?; + let blocks: Response = + epee_encoding::from_bytes(&mut epee_encoding::macros::bytes::Bytes::from(res))?; blocks .blocks .into_par_iter() .map(|b| { - let block = Block::read(&mut b.block.as_slice())?; + let block = Block::read(&mut b.block.deref())?; let txs = match b.txs { TransactionBlobs::Pruned(_) => return Err("node sent pruned txs!".into()), TransactionBlobs::Normal(txs) => txs .into_par_iter() - .map(|tx| Transaction::read(&mut tx.as_slice())) + .map(|tx| Transaction::read(&mut tx.deref())) .collect::>()?, TransactionBlobs::None => vec![], }; @@ -237,29 +253,56 @@ impl RpcConnection { out_ids.values().map(|amt_map| amt_map.len()).sum::() ); - #[derive(Serialize, Copy, Clone)] - struct OutputID { - amount: u64, - index: u64, + mod items { + + #[derive(Copy, Clone)] + pub struct OutputID { + pub amount: u64, + pub index: u64, + } + + epee_encoding::epee_object!( + OutputID, + amount: u64, + index: u64, + ); + + #[derive(Clone)] + pub struct Request { + pub outputs: Vec, + } + + epee_encoding::epee_object!( + Request, + outputs: Vec, + ); + + pub struct OutputRes { + pub height: u64, + pub key: [u8; 32], + pub mask: [u8; 32], + pub txid: [u8; 32], + } + + epee_encoding::epee_object!( + OutputRes, + height: u64, + key: [u8; 32], + mask: [u8; 32], + txid: [u8; 32], + ); + + pub struct Response { + pub outs: Vec, + } + + epee_encoding::epee_object!( + Response, + outs: Vec, + ); } - #[derive(Serialize, Clone)] - struct Request<'a> { - outputs: &'a [OutputID], - } - - #[derive(Deserialize)] - struct OutputRes { - height: u64, - key: [u8; 32], - mask: [u8; 32], - txid: [u8; 32], - } - - #[derive(Deserialize)] - struct Response { - outs: Vec, - } + use items::*; let outputs = rayon_spawn_async(|| { out_ids @@ -281,7 +324,10 @@ impl RpcConnection { .con .bin_call( "get_outs.bin", - monero_epee_bin_serde::to_bytes(&Request { outputs: &outputs })?, + epee_encoding::to_bytes(Request { + outputs: outputs.clone(), + })? + .to_vec(), ) .await?; @@ -289,7 +335,8 @@ impl RpcConnection { let span = tracing::Span::current(); rayon_spawn_async(move || { - let outs: Response = monero_epee_bin_serde::from_bytes(&res)?; + let outs: Response = + epee_encoding::from_bytes(&mut epee_encoding::macros::bytes::Bytes::from(res))?; tracing::info!(parent: &span, "Got outputs len: {}", outs.outs.len()); diff --git a/net/epee-encoding/Cargo.toml b/net/epee-encoding/Cargo.toml index 8132c2f2..523ee3ee 100644 --- a/net/epee-encoding/Cargo.toml +++ b/net/epee-encoding/Cargo.toml @@ -19,6 +19,7 @@ fixed-bytes = { path = "../fixed-bytes", default-features = false } sealed = "0.5.0" paste = "1.0.14" +ref-cast = "1.0.22" bytes = { workspace = true } thiserror = { workspace = true, optional = true} diff --git a/net/epee-encoding/src/container_as_blob.rs b/net/epee-encoding/src/container_as_blob.rs new file mode 100644 index 00000000..1b1bc4b9 --- /dev/null +++ b/net/epee-encoding/src/container_as_blob.rs @@ -0,0 +1,99 @@ +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use ref_cast::RefCast; +use sealed::sealed; + +use crate::{error::*, value::*, EpeeValue, InnerMarker, Marker}; + +#[derive(RefCast)] +#[repr(transparent)] +pub struct ContainerAsBlob(Vec); + +impl From> for ContainerAsBlob { + fn from(value: Vec) -> Self { + ContainerAsBlob(value) + } +} + +impl From> for Vec { + fn from(value: ContainerAsBlob) -> Self { + value.0 + } +} + +impl<'a, T: Containerable + EpeeValue> From<&'a Vec> for &'a ContainerAsBlob { + fn from(value: &'a Vec) -> Self { + ContainerAsBlob::ref_cast(value) + } +} + +#[sealed] +impl EpeeValue for ContainerAsBlob { + const MARKER: Marker = Marker::new(InnerMarker::String); + + fn read(r: &mut B, marker: &Marker) -> Result { + let bytes = Bytes::read(r, marker)?; + if bytes.len() % T::SIZE != 0 { + return Err(Error::Value("Can't convert blob container to Vec type.")); + } + + Ok(ContainerAsBlob( + bytes + .windows(T::SIZE) + .step_by(T::SIZE) + .map(T::from_bytes) + .collect(), + )) + } + + fn should_write(&self) -> bool { + !self.0.is_empty() + } + + fn epee_default_value() -> Option { + Some(ContainerAsBlob(vec![])) + } + + fn write(self, w: &mut B) -> crate::Result<()> { + let mut buf = BytesMut::with_capacity(self.0.len() * T::SIZE); + self.0.iter().for_each(|tt| tt.push_bytes(&mut buf)); + buf.write(w) + } +} + +pub trait Containerable { + const SIZE: usize; + + /// Returns `Self` from bytes. + /// + /// `bytes` is guaranteed to be [`Self::SIZE`] long. + fn from_bytes(bytes: &[u8]) -> Self; + + fn push_bytes(&self, buf: &mut BytesMut); +} + +macro_rules! int_container_able { + ($int:ty ) => { + impl Containerable for $int { + const SIZE: usize = std::mem::size_of::<$int>(); + + fn from_bytes(bytes: &[u8]) -> Self { + <$int>::from_le_bytes(bytes.try_into().unwrap()) + } + + fn push_bytes(&self, buf: &mut BytesMut) { + buf.put_slice(&self.to_le_bytes()) + } + } + }; +} + +int_container_able!(u16); +int_container_able!(u32); +int_container_able!(u64); +int_container_able!(u128); + +int_container_able!(i8); +int_container_able!(i16); +int_container_able!(i32); +int_container_able!(i64); +int_container_able!(i128); diff --git a/net/epee-encoding/src/lib.rs b/net/epee-encoding/src/lib.rs index 611f54d6..702ecc6a 100644 --- a/net/epee-encoding/src/lib.rs +++ b/net/epee-encoding/src/lib.rs @@ -51,7 +51,7 @@ //! //! //! let data = [1, 17, 1, 1, 1, 1, 2, 1, 1, 4, 3, 118, 97, 108, 5, 4, 0, 0, 0, 0, 0, 0, 0]; // the data to decode; -//! let val: Test = from_bytes(&data).unwrap(); +//! let val: Test = from_bytes(&mut &data).unwrap(); //! let data = to_bytes(val).unwrap(); //! //! @@ -79,18 +79,18 @@ //! //! //! let data = [1, 17, 1, 1, 1, 1, 2, 1, 1, 4, 3, 118, 97, 108, 5, 4, 0, 0, 0, 0, 0, 0, 0]; // the data to decode; -//! let val: Test2 = from_bytes(&data).unwrap(); +//! let val: Test2 = from_bytes(&mut &data).unwrap(); //! let data = to_bytes(val).unwrap(); //! //! ``` extern crate alloc; -use alloc::vec::Vec; use core::{ops::Deref, str::from_utf8 as str_from_utf8}; -use bytes::{Buf, BufMut, Bytes}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +pub mod container_as_blob; pub mod error; mod io; pub mod macros; @@ -139,13 +139,13 @@ pub trait EpeeObject: Sized { } /// Read the object `T` from a byte array. -pub fn from_bytes(mut buf: &[u8]) -> Result { - read_head_object(&mut buf) +pub fn from_bytes(buf: &mut B) -> Result { + read_head_object(buf) } /// Turn the object into epee bytes. -pub fn to_bytes(val: T) -> Result> { - let mut buf = Vec::::new(); +pub fn to_bytes(val: T) -> Result { + let mut buf = BytesMut::new(); write_head_object(val, &mut buf)?; Ok(buf) } diff --git a/net/epee-encoding/src/macros.rs b/net/epee-encoding/src/macros.rs index 578a414f..9079a052 100644 --- a/net/epee-encoding/src/macros.rs +++ b/net/epee-encoding/src/macros.rs @@ -3,7 +3,7 @@ pub use paste::paste; #[macro_export] macro_rules! field_name { - ($field: ident, $alt_name: literal) => { + ($field: tt, $alt_name: tt) => { $alt_name }; ($field: ident,) => { @@ -21,11 +21,21 @@ macro_rules! field_ty { }; } +#[macro_export] +macro_rules! try_right_then_left { + ($a:expr, $b:expr) => { + $b + }; + ($a:expr,) => { + $a + }; +} + #[macro_export] macro_rules! epee_object { ( $obj:ident, - $($field: ident $(($alt_name: literal))?: $ty:ty $(= $default:literal)? $(as $ty_as:ty)?, )+ + $($field: ident $(($alt_name: literal))?: $ty:ty $(as $ty_as:ty )? $(= $default:expr)? $(=> $read_fn:expr, $write_fn:expr, $should_write_fn:expr)?, )+ $(!flatten: $($flat_field: ident: $flat_ty:ty ,)+)? ) => { @@ -44,7 +54,9 @@ macro_rules! epee_object { fn add_field(&mut self, name: &str, b: &mut B) -> epee_encoding::error::Result { match name { $(epee_encoding::field_name!($field, $($alt_name)?) => { - if core::mem::replace(&mut self.$field, Some(epee_encoding::read_epee_value(b)?)).is_some() { + if core::mem::replace(&mut self.$field, Some( + epee_encoding::try_right_then_left!(epee_encoding::read_epee_value(b)?, $($read_fn(b)?)?) + )).is_some() { Err(epee_encoding::error::Error::Value("Duplicate field in data"))?; } Ok(true) @@ -65,11 +77,18 @@ macro_rules! epee_object { Ok( $obj { $( - $field: self.$field + $field: { + let epee_default_value = epee_encoding::try_right_then_left!(epee_encoding::EpeeValue::epee_default_value(), $({ + let _ = $should_write_fn; + None + })?); + + self.$field $(.or(Some($default)))? - .or(epee_encoding::EpeeValue::epee_default_value()) + .or(epee_default_value) $(.map(<$ty_as>::into))? - .ok_or(epee_encoding::error::Error::Value("Missing field in data"))?, + .ok_or(epee_encoding::error::Error::Value("Missing field in data"))? + }, )+ $( @@ -90,7 +109,10 @@ macro_rules! epee_object { let mut fields = 0; $( - if $(&self.$field != &$default &&)? epee_encoding::EpeeValue::should_write($(<&$ty_as>::from)?( &self.$field) ) { + let field = epee_encoding::try_right_then_left!(&self.$field, $(<&$ty_as>::from(&self.$field))? ); + + if $((field) != &$default &&)? epee_encoding::try_right_then_left!(epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(field ) + { fields += 1; } )+ @@ -106,8 +128,11 @@ macro_rules! epee_object { fn write_fields(self, w: &mut B) -> epee_encoding::error::Result<()> { $( - if $(&self.$field != &$default &&)? epee_encoding::EpeeValue::should_write($(<&$ty_as>::from)?( &self.$field) ) { - epee_encoding::write_field($(<$ty_as>::from)?(self.$field), epee_encoding::field_name!($field, $($alt_name)?), w)?; + let field = epee_encoding::try_right_then_left!(self.$field, $(<$ty_as>::from(self.$field))? ); + + if $(field != $default &&)? epee_encoding::try_right_then_left!(epee_encoding::EpeeValue::should_write, $($should_write_fn)?)(&field ) + { + epee_encoding::try_right_then_left!(epee_encoding::write_field, $($write_fn)?)((field), epee_encoding::field_name!($field, $($alt_name)?), w)?; } )+ diff --git a/net/epee-encoding/src/value.rs b/net/epee-encoding/src/value.rs index 44907262..75e50b2f 100644 --- a/net/epee-encoding/src/value.rs +++ b/net/epee-encoding/src/value.rs @@ -3,7 +3,7 @@ use alloc::{string::String, vec::Vec}; /// the different possible base epee values. use core::fmt::Debug; -use bytes::{Buf, BufMut, Bytes}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; use sealed::sealed; use fixed_bytes::{ByteArray, ByteArrayVec}; @@ -15,7 +15,7 @@ use crate::{ /// A trait for epee values, this trait is sealed as all possible epee values are /// defined in the lib, to make an [`EpeeValue`] outside the lib you will need to /// use the trait [`EpeeObject`]. -#[sealed] +#[sealed(pub(crate))] pub trait EpeeValue: Sized { const MARKER: Marker; @@ -234,6 +234,42 @@ impl EpeeValue for Bytes { } } +#[sealed::sealed] +impl EpeeValue for BytesMut { + const MARKER: Marker = Marker::new(InnerMarker::String); + + fn read(r: &mut B, marker: &Marker) -> Result { + if marker != &Self::MARKER { + return Err(Error::Format("Marker does not match expected Marker")); + } + + let len = read_varint(r)?; + if len > MAX_STRING_LEN_POSSIBLE { + return Err(Error::Format("Byte array exceeded max length")); + } + + if r.remaining() < len.try_into()? { + return Err(Error::IO("Not enough bytes to fill object")); + } + + let mut bytes = BytesMut::zeroed(len.try_into()?); + r.copy_to_slice(&mut bytes); + + Ok(bytes) + } + + fn write(self, w: &mut B) -> Result<()> { + write_varint(self.len().try_into()?, w)?; + + if w.remaining_mut() < self.len() { + return Err(Error::IO("Not enough capacity to write bytes")); + } + + w.put(self); + Ok(()) + } +} + #[sealed::sealed] impl EpeeValue for ByteArrayVec { const MARKER: Marker = Marker::new(InnerMarker::String); @@ -481,6 +517,8 @@ epee_seq!(f64); epee_seq!(bool); epee_seq!(Vec); epee_seq!(String); +epee_seq!(Bytes); +epee_seq!(BytesMut); #[sealed] impl EpeeValue for Option { diff --git a/net/epee-encoding/tests/alt_name.rs b/net/epee-encoding/tests/alt_name.rs index cce90846..6122c242 100644 --- a/net/epee-encoding/tests/alt_name.rs +++ b/net/epee-encoding/tests/alt_name.rs @@ -27,7 +27,7 @@ fn epee_alt_name() { let val2 = AltName2 { val2: 40, d: 30 }; let bytes = to_bytes(val2).unwrap(); - let val: AltName = from_bytes(&bytes).unwrap(); + let val: AltName = from_bytes(&mut bytes.clone()).unwrap(); let bytes2 = to_bytes(val).unwrap(); diff --git a/net/epee-encoding/tests/duplicate_key.rs b/net/epee-encoding/tests/duplicate_key.rs index d2b54b81..5fe32ac8 100644 --- a/net/epee-encoding/tests/duplicate_key.rs +++ b/net/epee-encoding/tests/duplicate_key.rs @@ -25,7 +25,7 @@ fn duplicate_key() { b'a', 0x0B, 0x00, ]; - assert!(from_bytes::(&data).is_err()); + assert!(from_bytes::(&mut &data[..]).is_err()); } #[test] @@ -35,5 +35,5 @@ fn duplicate_key_with_default() { b'a', 0x0B, 0x00, ]; - assert!(from_bytes::(&data).is_err()); + assert!(from_bytes::(&mut &data[..]).is_err()); } diff --git a/net/epee-encoding/tests/epee_default.rs b/net/epee-encoding/tests/epee_default.rs index e4a480e3..016b68f4 100644 --- a/net/epee-encoding/tests/epee_default.rs +++ b/net/epee-encoding/tests/epee_default.rs @@ -8,7 +8,7 @@ pub struct Optional { epee_object!( Optional, val: u8, - optional_val: i32 = -4, + optional_val: i32 = -4_i32, ); pub struct NotOptional { val: u8, @@ -37,11 +37,11 @@ fn epee_default_does_not_encode() { val: 1, optional_val: -4, }; - let bytes = to_bytes(val).unwrap(); + let mut bytes = to_bytes(val).unwrap().freeze(); - assert!(from_bytes::(&bytes).is_err()); + assert!(from_bytes::(&mut bytes.clone()).is_err()); - let val: Optional = from_bytes(&bytes).unwrap(); + let val: Optional = from_bytes(&mut bytes).unwrap(); assert_eq!(val.optional_val, -4); assert_eq!(val.val, 1); } @@ -52,11 +52,11 @@ fn epee_non_default_does_encode() { val: 8, optional_val: -3, }; - let bytes = to_bytes(val).unwrap(); + let mut bytes = to_bytes(val).unwrap().freeze(); - assert!(from_bytes::(&bytes).is_ok()); + assert!(from_bytes::(&mut bytes.clone()).is_ok()); - let val: Optional = from_bytes(&bytes).unwrap(); + let val: Optional = from_bytes(&mut bytes).unwrap(); assert_eq!(val.optional_val, -3); assert_eq!(val.val, 8) } @@ -64,11 +64,11 @@ fn epee_non_default_does_encode() { #[test] fn epee_value_not_present_with_default() { let val = NotPresent { val: 76 }; - let bytes = to_bytes(val).unwrap(); + let mut bytes = to_bytes(val).unwrap().freeze(); - assert!(from_bytes::(&bytes).is_err()); + assert!(from_bytes::(&mut bytes.clone()).is_err()); - let val: Optional = from_bytes(&bytes).unwrap(); + let val: Optional = from_bytes(&mut bytes).unwrap(); assert_eq!(val.optional_val, -4); assert_eq!(val.val, 76) } diff --git a/net/epee-encoding/tests/flattend.rs b/net/epee-encoding/tests/flattend.rs index b84b389d..fafcd4a6 100644 --- a/net/epee-encoding/tests/flattend.rs +++ b/net/epee-encoding/tests/flattend.rs @@ -43,9 +43,9 @@ fn epee_flatten() { val: 94, val2: vec![4, 5], }; - let bytes = to_bytes(val2.clone()).unwrap(); + let mut bytes = to_bytes(val2.clone()).unwrap(); - let val: Parent = from_bytes(&bytes).unwrap(); + let val: Parent = from_bytes(&mut bytes).unwrap(); assert_eq!(val.child.val2, val2.val2); assert_eq!(val.child.val, val2.val); @@ -95,8 +95,8 @@ epee_object!( fn epee_double_flatten() { let val = Parent12::default(); - let bytes = to_bytes(val.clone()).unwrap(); - let val1: Parent12 = from_bytes(&bytes).unwrap(); + let mut bytes = to_bytes(val.clone()).unwrap(); + let val1: Parent12 = from_bytes(&mut bytes).unwrap(); assert_eq!(val, val1); } diff --git a/net/epee-encoding/tests/options.rs b/net/epee-encoding/tests/options.rs index 4a76aac9..f8cb4969 100644 --- a/net/epee-encoding/tests/options.rs +++ b/net/epee-encoding/tests/options.rs @@ -1,4 +1,5 @@ use epee_encoding::{epee_object, from_bytes, to_bytes}; +use std::ops::Deref; #[derive(Clone)] struct T { @@ -11,9 +12,10 @@ epee_object!( ); #[test] +#[allow(clippy::useless_asref)] fn optional_val_not_in_data() { let bytes: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01\x00"; - let t: T = from_bytes(bytes).unwrap(); + let t: T = from_bytes(&mut bytes.as_ref()).unwrap(); let bytes2 = to_bytes(t.clone()).unwrap(); assert_eq!(bytes, bytes2); assert!(t.val.is_none()); @@ -24,8 +26,8 @@ fn optional_val_in_data() { let bytes = [ 0x01, 0x11, 0x01, 0x1, 0x01, 0x01, 0x02, 0x1, 0x1, 0x04, 0x03, b'v', b'a', b'l', 0x08, 21, ]; - let t: T = from_bytes(&bytes).unwrap(); + let t: T = from_bytes(&mut &bytes[..]).unwrap(); let bytes2 = to_bytes(t.clone()).unwrap(); - assert_eq!(bytes.as_slice(), bytes2.as_slice()); + assert_eq!(bytes.as_slice(), bytes2.deref()); assert_eq!(t.val.unwrap(), 21); } diff --git a/net/epee-encoding/tests/p2p.rs b/net/epee-encoding/tests/p2p.rs index ace7478b..386cc1a1 100644 --- a/net/epee-encoding/tests/p2p.rs +++ b/net/epee-encoding/tests/p2p.rs @@ -51,9 +51,9 @@ epee_object!( fn p2p_handshake() { let bytes = hex::decode("01110101010102010108096e6f64655f646174610c10076d795f706f727406a04600000a6e6574776f726b5f69640a401230f171610441611731008216a1a11007706565725f6964053eb3c096c4471c340d737570706f72745f666c61677306010000000c7061796c6f61645f646174610c181563756d756c61746976655f646966666963756c7479053951f7a79aab4a031b63756d756c61746976655f646966666963756c74795f746f7036340500000000000000000e63757272656e745f68656967687405fa092a00000000000c7072756e696e675f73656564068001000006746f705f69640a806cc497b230ba57a95edb370be8d6870c94e0992937c89b1def3a4cb7726d37ad0b746f705f76657273696f6e0810").unwrap(); - let val: HandshakeR = from_bytes(&bytes).unwrap(); + let val: HandshakeR = from_bytes(&mut bytes.as_slice()).unwrap(); - let bytes = to_bytes(val.clone()).unwrap(); + let mut bytes = to_bytes(val.clone()).unwrap(); - assert_eq!(val, from_bytes(&bytes).unwrap()); + assert_eq!(val, from_bytes(&mut bytes).unwrap()); } diff --git a/net/epee-encoding/tests/rpc.rs b/net/epee-encoding/tests/rpc.rs index caa1914e..4d0848f0 100644 --- a/net/epee-encoding/tests/rpc.rs +++ b/net/epee-encoding/tests/rpc.rs @@ -63,21 +63,21 @@ epee_object!( #[test] fn rpc_get_outs_response() { let bytes = hex::decode("011101010101020101140763726564697473050000000000000000046f7574738c04140668656967687405a100000000000000036b65790a802d392d0be38eb4699c17767e62a063b8d2f989ec15c80e5d2665ab06f8397439046d61736b0a805e8b863c5b267deda13f4bc5d5ec8e59043028380f2431bc8691c15c83e1fea404747869640a80c0646e065a33b849f0d9563673ca48eb0c603fe721dd982720dba463172c246f08756e6c6f636b65640b00067374617475730a084f4b08746f705f686173680a0009756e747275737465640b00").unwrap(); - let val: GetOutsResponse = from_bytes(&bytes).unwrap(); - let bytes = to_bytes(val.clone()).unwrap(); + let val: GetOutsResponse = from_bytes(&mut bytes.as_slice()).unwrap(); + let mut bytes = to_bytes(val.clone()).unwrap(); - assert_eq!(val, from_bytes(&bytes).unwrap()); + assert_eq!(val, from_bytes(&mut bytes).unwrap()); } #[test] fn get_out_indexes_response() { - let bytes = [ + let bytes: [u8; 61] = [ 1, 17, 1, 1, 1, 1, 2, 1, 1, 16, 7, 99, 114, 101, 100, 105, 116, 115, 5, 0, 0, 0, 0, 0, 0, 0, 0, 6, 115, 116, 97, 116, 117, 115, 10, 8, 79, 75, 8, 116, 111, 112, 95, 104, 97, 115, 104, 10, 0, 9, 117, 110, 116, 114, 117, 115, 116, 101, 100, 11, 0, ]; - let val: GetOIndexesResponse = from_bytes(&bytes).unwrap(); - let bytes = to_bytes(val.clone()).unwrap(); + let val: GetOIndexesResponse = from_bytes(&mut bytes.as_slice()).unwrap(); + let mut bytes = to_bytes(val.clone()).unwrap(); - assert_eq!(val, from_bytes(&bytes).unwrap()); + assert_eq!(val, from_bytes(&mut bytes).unwrap()); } diff --git a/net/epee-encoding/tests/seq.rs b/net/epee-encoding/tests/seq.rs index 0989f9a9..9d3189a1 100644 --- a/net/epee-encoding/tests/seq.rs +++ b/net/epee-encoding/tests/seq.rs @@ -28,9 +28,9 @@ fn seq_with_zero_len_can_have_any_marker() { data.push(0x80 | marker); data.push(0); - assert!(from_bytes::(&data).is_ok()); + assert!(from_bytes::(&mut data.as_slice()).is_ok()); - assert!(from_bytes::(&data).is_ok()); + assert!(from_bytes::(&mut data.as_slice()).is_ok()); data.drain(14..); } @@ -48,7 +48,7 @@ fn seq_with_non_zero_len_must_have_correct_marker() { data.push(0x04); // varint length of 1 data.extend_from_slice(&1_i64.to_le_bytes()); - assert!(from_bytes::(&data).is_err()); + assert!(from_bytes::(&mut data.as_slice()).is_err()); data.drain(14..); } @@ -56,5 +56,5 @@ fn seq_with_non_zero_len_must_have_correct_marker() { data.push(0x80 + 1); data.push(0x04); // varint length data.extend_from_slice(&1_i64.to_le_bytes()); - (from_bytes::(&data).unwrap()); + (from_bytes::(&mut data.as_slice()).unwrap()); } diff --git a/net/epee-encoding/tests/stack_overflow.rs b/net/epee-encoding/tests/stack_overflow.rs index c9396848..1f71abf8 100644 --- a/net/epee-encoding/tests/stack_overflow.rs +++ b/net/epee-encoding/tests/stack_overflow.rs @@ -735,7 +735,7 @@ fn stack_overlfow() { 8, 7, 1, 100, 12, 8, 3, 118, 97, 108, 8, 7, ]; - let obj: Result = from_bytes(&bytes); + let obj: Result = from_bytes(&mut bytes.as_slice()); assert!(obj.is_err()) } diff --git a/net/fixed-bytes/src/lib.rs b/net/fixed-bytes/src/lib.rs index c4272c1f..a88e23ef 100644 --- a/net/fixed-bytes/src/lib.rs +++ b/net/fixed-bytes/src/lib.rs @@ -41,6 +41,7 @@ impl Debug for FixedByteError { /// /// Internally this is just a wrapper around [`Bytes`], with the constructors checking that the length is equal to [`N`]. /// This implements [`Deref`] with the target being `[u8; N]`. +#[derive(Debug, Clone, Eq, PartialEq)] pub struct ByteArray(Bytes); impl ByteArray { @@ -49,6 +50,12 @@ impl ByteArray { } } +impl From<[u8; N]> for ByteArray { + fn from(value: [u8; N]) -> Self { + ByteArray(Bytes::copy_from_slice(&value)) + } +} + impl Deref for ByteArray { type Target = [u8; N]; @@ -68,6 +75,18 @@ impl TryFrom for ByteArray { } } +impl TryFrom> for ByteArray { + type Error = FixedByteError; + + fn try_from(value: Vec) -> Result { + if value.len() != N { + return Err(FixedByteError::InvalidLength); + } + Ok(ByteArray(Bytes::from(value))) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] pub struct ByteArrayVec(Bytes); impl ByteArrayVec { diff --git a/net/levin/src/codec.rs b/net/levin/src/codec.rs index 66066487..0c6e8b8f 100644 --- a/net/levin/src/codec.rs +++ b/net/levin/src/codec.rs @@ -17,7 +17,7 @@ use std::marker::PhantomData; -use bytes::{Buf, BufMut, BytesMut}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; use tokio_util::codec::{Decoder, Encoder}; use crate::{ @@ -111,7 +111,7 @@ impl Decoder for LevinBucketCodec { return Ok(Some(Bucket { header, - body: src.copy_to_bytes(body_len).into(), + body: src.copy_to_bytes(body_len), })); } } @@ -138,7 +138,7 @@ impl Encoder> for LevinBucketCodec { enum MessageState { #[default] WaitingForBucket, - WaitingForRestOfFragment(Vec, MessageType, C), + WaitingForRestOfFragment(Vec, MessageType, C), } /// A tokio-codec for levin messages or in other words the decoded body @@ -167,7 +167,7 @@ impl Decoder for LevinMessageCodec { loop { match &mut self.state { MessageState::WaitingForBucket => { - let Some(bucket) = self.bucket_codec.decode(src)? else { + let Some(mut bucket) = self.bucket_codec.decode(src)? else { return Ok(None); }; @@ -199,7 +199,7 @@ impl Decoder for LevinMessageCodec { let _ = std::mem::replace( &mut self.state, MessageState::WaitingForRestOfFragment( - bucket.body.to_vec(), + vec![bucket.body], message_type, bucket.header.command, ), @@ -209,7 +209,7 @@ impl Decoder for LevinMessageCodec { } return Ok(Some(T::decode_message( - &bucket.body, + &mut bucket.body, message_type, bucket.header.command, )?)); @@ -257,16 +257,23 @@ impl Decoder for LevinMessageCodec { )); } - bytes.append(&mut bucket.body.to_vec()); + bytes.push(bucket.body); if flags.is_end_fragment() { - let MessageState::WaitingForRestOfFragment(bytes, ty, command) = + let MessageState::WaitingForRestOfFragment(mut bytes, ty, command) = std::mem::replace(&mut self.state, MessageState::WaitingForBucket) else { unreachable!(); }; - return Ok(Some(T::decode_message(&bytes, ty, command)?)); + // TODO: this doesn't seem very efficient but I can't think of a better way. + bytes.reverse(); + let mut byte_vec: Box = Box::new(bytes.pop().unwrap()); + for bytes in bytes { + byte_vec = Box::new(byte_vec.chain(bytes)); + } + + return Ok(Some(T::decode_message(&mut byte_vec, ty, command)?)); } } } diff --git a/net/levin/src/lib.rs b/net/levin/src/lib.rs index cb0a3ab9..6be0edb3 100644 --- a/net/levin/src/lib.rs +++ b/net/levin/src/lib.rs @@ -41,6 +41,7 @@ pub use header::BucketHead; use std::fmt::Debug; +use bytes::{Buf, Bytes}; use thiserror::Error; const MONERO_PROTOCOL_VERSION: u32 = 1; @@ -102,7 +103,7 @@ pub struct Bucket { /// The bucket header pub header: BucketHead, /// The bucket body - pub body: Vec, + pub body: Bytes, } /// An enum representing if the message is a request, response or notification. @@ -158,7 +159,7 @@ pub struct BucketBuilder { command: Option, return_code: Option, protocol_version: Option, - body: Option>, + body: Option, } impl Default for BucketBuilder { @@ -195,7 +196,7 @@ impl BucketBuilder { self.protocol_version = Some(version) } - pub fn set_body(&mut self, body: Vec) { + pub fn set_body(&mut self, body: Bytes) { self.body = Some(body) } @@ -222,14 +223,14 @@ pub trait LevinBody: Sized { type Command: LevinCommand; /// Decodes the message from the data in the header - fn decode_message( - body: &[u8], + fn decode_message( + body: &mut B, typ: MessageType, command: Self::Command, ) -> Result; /// Encodes the message - fn encode(&self, builder: &mut BucketBuilder) -> Result<(), BucketError>; + fn encode(self, builder: &mut BucketBuilder) -> Result<(), BucketError>; } /// The levin commands. diff --git a/net/monero-wire/Cargo.toml b/net/monero-wire/Cargo.toml index a654061a..5a8f4b86 100644 --- a/net/monero-wire/Cargo.toml +++ b/net/monero-wire/Cargo.toml @@ -9,9 +9,10 @@ repository = "https://github.com/SyntheticBird45/cuprate/tree/main/net/monero-wi [dependencies] levin-cuprate = {path="../levin"} -monero-epee-bin-serde = { workspace = true, features = ["container_as_blob"] } -serde = { workspace = true, features = ["derive", "std"]} -serde_bytes = { workspace = true, features = ["std"]} +epee-encoding = { path = "../epee-encoding" } +fixed-bytes = { path = "../fixed-bytes" } + +bytes = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/net/monero-wire/src/lib.rs b/net/monero-wire/src/lib.rs index 019756a7..f69fd2dc 100644 --- a/net/monero-wire/src/lib.rs +++ b/net/monero-wire/src/lib.rs @@ -24,7 +24,6 @@ pub mod network_address; pub mod p2p; -mod serde_helpers; pub use levin_cuprate::BucketError; pub use network_address::{NetZone, NetworkAddress}; diff --git a/net/monero-wire/src/network_address.rs b/net/monero-wire/src/network_address.rs index 34ed812d..29cf2486 100644 --- a/net/monero-wire/src/network_address.rs +++ b/net/monero-wire/src/network_address.rs @@ -17,10 +17,10 @@ //! Monero network. Core Monero has 4 main addresses: IPv4, IPv6, Tor, //! I2p. Currently this module only has IPv(4/6). //! +use bytes::BufMut; +use epee_encoding::{EpeeObject, EpeeValue}; use std::{hash::Hash, net, net::SocketAddr}; -use serde::{Deserialize, Serialize}; - mod serde_helper; use serde_helper::*; @@ -33,13 +33,23 @@ pub enum NetZone { /// A network address which can be encoded into the format required /// to send to other Monero peers. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(try_from = "TaggedNetworkAddress")] -#[serde(into = "TaggedNetworkAddress")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NetworkAddress { Clear(SocketAddr), } +impl EpeeObject for NetworkAddress { + type Builder = TaggedNetworkAddress; + + fn number_of_fields(&self) -> u64 { + 2 + } + + fn write_fields(self, w: &mut B) -> epee_encoding::Result<()> { + TaggedNetworkAddress::from(self).write(w) + } +} + impl NetworkAddress { pub fn get_zone(&self) -> NetZone { match self { diff --git a/net/monero-wire/src/network_address/serde_helper.rs b/net/monero-wire/src/network_address/serde_helper.rs index 23491617..13555cdc 100644 --- a/net/monero-wire/src/network_address/serde_helper.rs +++ b/net/monero-wire/src/network_address/serde_helper.rs @@ -1,20 +1,53 @@ +use bytes::Buf; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; -use serde::{Deserialize, Serialize}; +use epee_encoding::{epee_object, EpeeObjectBuilder}; use thiserror::Error; use crate::NetworkAddress; -#[derive(Serialize, Deserialize)] -pub(crate) struct TaggedNetworkAddress { - #[serde(rename = "type")] - ty: u8, - addr: AllFieldsNetworkAddress, +#[derive(Default)] +pub struct TaggedNetworkAddress { + ty: Option, + addr: Option, +} + +epee_object!( + TaggedNetworkAddress, + ty: Option, + addr: Option, +); + +impl EpeeObjectBuilder for TaggedNetworkAddress { + fn add_field(&mut self, name: &str, b: &mut B) -> epee_encoding::Result { + match name { + "type" => { + if std::mem::replace(&mut self.ty, Some(epee_encoding::read_epee_value(b)?)) + .is_some() + { + return Err(epee_encoding::Error::Format("Duplicate field in data.")); + } + } + "addr" => { + if std::mem::replace(&mut self.addr, epee_encoding::read_epee_value(b)?).is_some() { + return Err(epee_encoding::Error::Format("Duplicate field in data.")); + } + } + _ => return Ok(false), + } + + Ok(true) + } + + fn finish(self) -> epee_encoding::Result { + self.try_into() + .map_err(|_| epee_encoding::Error::Value("Invalid network address")) + } } #[derive(Error, Debug)] #[error("Invalid network address")] -pub(crate) struct InvalidNetworkAddress; +pub struct InvalidNetworkAddress; impl TryFrom for NetworkAddress { type Error = InvalidNetworkAddress; @@ -22,7 +55,8 @@ impl TryFrom for NetworkAddress { fn try_from(value: TaggedNetworkAddress) -> Result { value .addr - .try_into_network_address(value.ty) + .ok_or(InvalidNetworkAddress)? + .try_into_network_address(value.ty.ok_or(InvalidNetworkAddress)?) .ok_or(InvalidNetworkAddress) } } @@ -32,36 +66,40 @@ impl From for TaggedNetworkAddress { match value { NetworkAddress::Clear(addr) => match addr { SocketAddr::V4(addr) => TaggedNetworkAddress { - ty: 1, - addr: AllFieldsNetworkAddress { + ty: Some(1), + addr: Some(AllFieldsNetworkAddress { m_ip: Some(u32::from_be_bytes(addr.ip().octets())), m_port: Some(addr.port()), ..Default::default() - }, + }), }, SocketAddr::V6(addr) => TaggedNetworkAddress { - ty: 2, - addr: AllFieldsNetworkAddress { + ty: Some(2), + addr: Some(AllFieldsNetworkAddress { addr: Some(addr.ip().octets()), m_port: Some(addr.port()), ..Default::default() - }, + }), }, }, } } } -#[derive(Serialize, Deserialize, Default)] +#[derive(Default)] struct AllFieldsNetworkAddress { - #[serde(skip_serializing_if = "Option::is_none")] m_ip: Option, - #[serde(skip_serializing_if = "Option::is_none")] m_port: Option, - #[serde(skip_serializing_if = "Option::is_none")] addr: Option<[u8; 16]>, } +epee_object!( + AllFieldsNetworkAddress, + m_ip: Option, + m_port: Option, + addr: Option<[u8; 16]>, +); + impl AllFieldsNetworkAddress { fn try_into_network_address(self, ty: u8) -> Option { Some(match ty { diff --git a/net/monero-wire/src/p2p.rs b/net/monero-wire/src/p2p.rs index 86c258e9..31356c5d 100644 --- a/net/monero-wire/src/p2p.rs +++ b/net/monero-wire/src/p2p.rs @@ -16,6 +16,7 @@ //! This module defines a Monero `Message` enum which contains //! every possible Monero network message (levin body) +use bytes::{Buf, Bytes, BytesMut}; use levin_cuprate::{ BucketBuilder, BucketError, LevinBody, LevinCommand as LevinCommandTrait, MessageType, }; @@ -150,23 +151,23 @@ impl From for u32 { } } -fn decode_message( +fn decode_message( ret: impl FnOnce(T) -> Ret, - buf: &[u8], + buf: &mut B, ) -> Result { - let t = monero_epee_bin_serde::from_bytes(buf) - .map_err(|e| BucketError::BodyDecodingError(e.into()))?; + let t = epee_encoding::from_bytes(buf).map_err(|e| BucketError::BodyDecodingError(e.into()))?; Ok(ret(t)) } -fn build_message( +fn build_message( id: LevinCommand, - val: &T, + val: T, builder: &mut BucketBuilder, ) -> Result<(), BucketError> { builder.set_command(id); builder.set_body( - monero_epee_bin_serde::to_bytes(val) + epee_encoding::to_bytes(val) + .map(BytesMut::freeze) .map_err(|e| BucketError::BodyDecodingError(e.into()))?, ); Ok(()) @@ -201,7 +202,7 @@ impl ProtocolMessage { } } - fn decode(buf: &[u8], command: LevinCommand) -> Result { + fn decode(buf: &mut B, command: LevinCommand) -> Result { use LevinCommand as C; Ok(match command { @@ -220,7 +221,7 @@ impl ProtocolMessage { }) } - fn build(&self, builder: &mut BucketBuilder) -> Result<(), BucketError> { + fn build(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { use LevinCommand as C; match self { @@ -236,7 +237,7 @@ impl ProtocolMessage { } ProtocolMessage::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?, ProtocolMessage::ChainEntryResponse(val) => { - build_message(C::ChainResponse, &val, builder)? + build_message(C::ChainResponse, val, builder)? } ProtocolMessage::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?, ProtocolMessage::FluffyMissingTransactionsRequest(val) => { @@ -269,7 +270,7 @@ impl RequestMessage { } } - fn decode(buf: &[u8], command: LevinCommand) -> Result { + fn decode(buf: &mut B, command: LevinCommand) -> Result { use LevinCommand as C; Ok(match command { @@ -281,7 +282,7 @@ impl RequestMessage { }) } - fn build(&self, builder: &mut BucketBuilder) -> Result<(), BucketError> { + fn build(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { use LevinCommand as C; match self { @@ -289,11 +290,11 @@ impl RequestMessage { RequestMessage::TimedSync(val) => build_message(C::TimedSync, val, builder)?, RequestMessage::Ping => { builder.set_command(C::Ping); - builder.set_body(Vec::new()); + builder.set_body(Bytes::new()); } RequestMessage::SupportFlags => { builder.set_command(C::SupportFlags); - builder.set_body(Vec::new()); + builder.set_body(Bytes::new()); } } Ok(()) @@ -319,7 +320,7 @@ impl ResponseMessage { } } - fn decode(buf: &[u8], command: LevinCommand) -> Result { + fn decode(buf: &mut B, command: LevinCommand) -> Result { use LevinCommand as C; Ok(match command { @@ -331,7 +332,7 @@ impl ResponseMessage { }) } - fn build(&self, builder: &mut BucketBuilder) -> Result<(), BucketError> { + fn build(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { use LevinCommand as C; match self { @@ -375,8 +376,8 @@ impl Message { impl LevinBody for Message { type Command = LevinCommand; - fn decode_message( - body: &[u8], + fn decode_message( + body: &mut B, typ: MessageType, command: LevinCommand, ) -> Result { @@ -387,7 +388,7 @@ impl LevinBody for Message { }) } - fn encode(&self, builder: &mut BucketBuilder) -> Result<(), BucketError> { + fn encode(self, builder: &mut BucketBuilder) -> Result<(), BucketError> { match self { Message::Protocol(pro) => { builder.set_message_type(MessageType::Notification); diff --git a/net/monero-wire/src/p2p/admin.rs b/net/monero-wire/src/p2p/admin.rs index 69155607..c47af592 100644 --- a/net/monero-wire/src/p2p/admin.rs +++ b/net/monero-wire/src/p2p/admin.rs @@ -18,12 +18,13 @@ //! Admin message requests must be responded to in order unlike //! protocol messages. -use serde::{Deserialize, Serialize}; +use bytes::Bytes; +use epee_encoding::epee_object; use super::common::{BasicNodeData, CoreSyncData, PeerListEntryBase, PeerSupportFlags}; /// A Handshake Request -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct HandshakeRequest { /// Basic Node Data pub node_data: BasicNodeData, @@ -31,54 +32,87 @@ pub struct HandshakeRequest { pub payload_data: CoreSyncData, } +epee_object!( + HandshakeRequest, + node_data: BasicNodeData, + payload_data: CoreSyncData, +); + /// A Handshake Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct HandshakeResponse { /// Basic Node Data pub node_data: BasicNodeData, /// Core Sync Data pub payload_data: CoreSyncData, /// PeerList - #[serde(default = "Vec::new")] pub local_peerlist_new: Vec, } +epee_object!( + HandshakeResponse, + node_data: BasicNodeData, + payload_data: CoreSyncData, + local_peerlist_new: Vec, +); + /// A TimedSync Request -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TimedSyncRequest { /// Core Sync Data pub payload_data: CoreSyncData, } +epee_object!( + TimedSyncRequest, + payload_data: CoreSyncData, +); + /// A TimedSync Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TimedSyncResponse { /// Core Sync Data pub payload_data: CoreSyncData, /// PeerList - #[serde(default = "Vec::new")] pub local_peerlist_new: Vec, } +epee_object!( + TimedSyncResponse, + payload_data: CoreSyncData, + local_peerlist_new: Vec, +); + /// The status field of an okay ping response -pub const PING_OK_RESPONSE_STATUS_TEXT: &str = "OK"; +pub const PING_OK_RESPONSE_STATUS_TEXT: Bytes = Bytes::from_static("OK".as_bytes()); /// A Ping Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PingResponse { /// Status: should be `PING_OK_RESPONSE_STATUS_TEXT` - pub status: String, + pub status: Bytes, /// Peer ID pub peer_id: u64, } +epee_object!( + PingResponse, + status: Bytes, + peer_id: u64, +); + /// A Support Flags Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SupportFlagsResponse { /// Support Flags pub support_flags: PeerSupportFlags, } +epee_object!( + SupportFlagsResponse, + support_flags: PeerSupportFlags as u32, +); + #[cfg(test)] mod tests { @@ -101,12 +135,13 @@ mod tests { 186, 15, 178, 70, 173, 170, 187, 31, 70, 50, 227, 11, 116, 111, 112, 95, 118, 101, 114, 115, 105, 111, 110, 8, 1, ]; - let handshake: HandshakeRequest = monero_epee_bin_serde::from_bytes(bytes).unwrap(); + let handshake: HandshakeRequest = epee_encoding::from_bytes(&mut &bytes[..]).unwrap(); let basic_node_data = BasicNodeData { my_port: 0, network_id: [ 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, - ], + ] + .into(), peer_id: 9671405426614699871, support_flags: PeerSupportFlags::from(1_u32), rpc_port: 0, @@ -128,9 +163,8 @@ mod tests { assert_eq!(basic_node_data, handshake.node_data); assert_eq!(core_sync_data, handshake.payload_data); - let encoded_bytes = monero_epee_bin_serde::to_bytes(&handshake).unwrap(); - let handshake_2: HandshakeRequest = - monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap(); + let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap(); + let handshake_2: HandshakeRequest = epee_encoding::from_bytes(&mut encoded_bytes).unwrap(); assert_eq!(handshake, handshake_2); } @@ -906,13 +940,14 @@ mod tests { 181, 216, 193, 135, 23, 186, 168, 207, 119, 86, 235, 11, 116, 111, 112, 95, 118, 101, 114, 115, 105, 111, 110, 8, 16, ]; - let handshake: HandshakeResponse = monero_epee_bin_serde::from_bytes(bytes).unwrap(); + let handshake: HandshakeResponse = epee_encoding::from_bytes(&mut &bytes[..]).unwrap(); let basic_node_data = BasicNodeData { my_port: 18080, network_id: [ 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, - ], + ] + .into(), peer_id: 6037804360359455404, support_flags: PeerSupportFlags::from(1_u32), rpc_port: 18089, @@ -935,9 +970,8 @@ mod tests { assert_eq!(core_sync_data, handshake.payload_data); assert_eq!(250, handshake.local_peerlist_new.len()); - let encoded_bytes = monero_epee_bin_serde::to_bytes(&handshake).unwrap(); - let handshake_2: HandshakeResponse = - monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap(); + let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap(); + let handshake_2: HandshakeResponse = epee_encoding::from_bytes(&mut encoded_bytes).unwrap(); assert_eq!(handshake, handshake_2); } diff --git a/net/monero-wire/src/p2p/common.rs b/net/monero-wire/src/p2p/common.rs index adfa8e46..3d37c2dc 100644 --- a/net/monero-wire/src/p2p/common.rs +++ b/net/monero-wire/src/p2p/common.rs @@ -15,15 +15,13 @@ //! Common types that are used across multiple messages. -use serde::{Deserialize, Serialize}; +use bytes::{Buf, BufMut, Bytes}; +use epee_encoding::{epee_object, EpeeValue, InnerMarker}; +use fixed_bytes::ByteArray; -use crate::{ - serde_helpers::{default_false, default_zero, serde_vec_bytes}, - NetworkAddress, -}; +use crate::NetworkAddress; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PeerSupportFlags(u32); impl From for PeerSupportFlags { @@ -38,6 +36,12 @@ impl From for u32 { } } +impl<'a> From<&'a PeerSupportFlags> for &'a u32 { + fn from(value: &'a PeerSupportFlags) -> Self { + &value.0 + } +} + impl PeerSupportFlags { //const FLUFFY_BLOCKS: u32 = 0b0000_0001; @@ -53,51 +57,65 @@ impl From for PeerSupportFlags { } /// Basic Node Data, information on the connected peer -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct BasicNodeData { /// Port pub my_port: u32, /// The Network Id - pub network_id: [u8; 16], + pub network_id: ByteArray<16>, /// Peer ID pub peer_id: u64, /// The Peers Support Flags /// (If this is not in the message the default is 0) - #[serde(default = "default_zero")] pub support_flags: PeerSupportFlags, /// RPC Port /// (If this is not in the message the default is 0) - #[serde(default = "default_zero")] pub rpc_port: u16, /// RPC Credits Per Hash /// (If this is not in the message the default is 0) - #[serde(default = "default_zero")] pub rpc_credits_per_hash: u32, } +epee_object! { + BasicNodeData, + my_port: u32, + network_id: ByteArray<16>, + peer_id: u64, + support_flags: PeerSupportFlags as u32 = 0_u32, + rpc_port: u16 = 0_u16, + rpc_credits_per_hash: u32 = 0_u32, +} + /// Core Sync Data, information on the sync state of a peer -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CoreSyncData { /// Cumulative Difficulty Low /// The lower 64 bits of the 128 bit cumulative difficulty pub cumulative_difficulty: u64, /// Cumulative Difficulty High /// The upper 64 bits of the 128 bit cumulative difficulty - #[serde(default = "default_zero")] pub cumulative_difficulty_top64: u64, /// Current Height of the peer pub current_height: u64, /// Pruning Seed of the peer /// (If this is not in the message the default is 0) - #[serde(default = "default_zero")] pub pruning_seed: u32, /// Hash of the top block - pub top_id: [u8; 32], + pub top_id: ByteArray<32>, /// Version of the top block - #[serde(default = "default_zero")] pub top_version: u8, } +epee_object! { + CoreSyncData, + cumulative_difficulty: u64, + cumulative_difficulty_top64: u64 = 0_u64, + current_height: u64, + pruning_seed: u32 = 0_u32, + top_id: ByteArray<32>, + top_version: u8 = 0_u8, +} + impl CoreSyncData { pub fn new( cumulative_difficulty_128: u128, @@ -113,7 +131,7 @@ impl CoreSyncData { cumulative_difficulty_top64, current_height, pruning_seed, - top_id, + top_id: top_id.into(), top_version, } } @@ -127,26 +145,32 @@ impl CoreSyncData { /// PeerListEntryBase, information kept on a peer which will be entered /// in a peer list/store. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct PeerListEntryBase { /// The Peer Address pub adr: NetworkAddress, /// The Peer ID pub id: u64, /// The last Time The Peer Was Seen - #[serde(default = "default_zero")] pub last_seen: i64, /// The Pruning Seed - #[serde(default = "default_zero")] pub pruning_seed: u32, /// The RPC port - #[serde(default = "default_zero")] pub rpc_port: u16, /// The RPC credits per hash - #[serde(default = "default_zero")] pub rpc_credits_per_hash: u32, } +epee_object! { + PeerListEntryBase, + adr: NetworkAddress, + id: u64, + last_seen: i64 = 0_i64, + pruning_seed: u32 = 0_u32, + rpc_port: u16 = 0_u16, + rpc_credits_per_hash: u32 = 0_u32, +} + impl std::hash::Hash for PeerListEntryBase { fn hash(&self, state: &mut H) { // We only hash the adr so we can look this up in a HashSet. @@ -155,22 +179,24 @@ impl std::hash::Hash for PeerListEntryBase { } /// A pruned tx with the hash of the missing prunable data -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct PrunedTxBlobEntry { /// The Tx - #[serde(with = "serde_bytes")] - pub tx: Vec, + pub tx: Bytes, /// The Prunable Tx Hash - pub prunable_hash: [u8; 32], + pub prunable_hash: ByteArray<32>, } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(untagged)] +epee_object!( + PrunedTxBlobEntry, + tx: Bytes, + prunable_hash: ByteArray<32>, +); + +#[derive(Clone, Debug, PartialEq, Eq)] pub enum TransactionBlobs { Pruned(Vec), - #[serde(with = "serde_vec_bytes")] - Normal(Vec>), - #[serde(skip_serializing)] + Normal(Vec), None, } @@ -183,7 +209,7 @@ impl TransactionBlobs { } } - pub fn take_normal(self) -> Option>> { + pub fn take_normal(self) -> Option> { match self { TransactionBlobs::Normal(txs) => Some(txs), TransactionBlobs::Pruned(_) => None, @@ -202,30 +228,57 @@ impl TransactionBlobs { pub fn is_empty(&self) -> bool { self.len() == 0 } - - fn none() -> TransactionBlobs { - TransactionBlobs::None - } } /// A Block that can contain transactions -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct BlockCompleteEntry { /// True if tx data is pruned - #[serde(default = "default_false")] pub pruned: bool, /// The Block - #[serde(with = "serde_bytes")] - pub block: Vec, + pub block: Bytes, /// The Block Weight/Size - #[serde(default = "default_zero")] pub block_weight: u64, /// The blocks txs - #[serde(skip_serializing_if = "TransactionBlobs::is_empty")] - #[serde(default = "TransactionBlobs::none")] pub txs: TransactionBlobs, } +epee_object!( + BlockCompleteEntry, + pruned: bool = false, + block: Bytes, + block_weight: u64 = 0_u64, + txs: TransactionBlobs = TransactionBlobs::None => tx_blob_read, tx_blob_write, should_write_tx_blobs, +); + +fn tx_blob_read(b: &mut B) -> epee_encoding::Result { + let marker = epee_encoding::read_marker(b)?; + match marker.inner_marker { + InnerMarker::Object => Ok(TransactionBlobs::Pruned(Vec::read(b, &marker)?)), + InnerMarker::String => Ok(TransactionBlobs::Normal(Vec::read(b, &marker)?)), + _ => Err(epee_encoding::Error::Value("Invalid marker for tx blobs")), + } +} + +fn tx_blob_write( + val: TransactionBlobs, + field_name: &str, + w: &mut B, +) -> epee_encoding::Result<()> { + if should_write_tx_blobs(&val) { + match val { + TransactionBlobs::Normal(bytes) => epee_encoding::write_field(bytes, field_name, w)?, + TransactionBlobs::Pruned(obj) => epee_encoding::write_field(obj, field_name, w)?, + TransactionBlobs::None => (), + } + } + Ok(()) +} + +fn should_write_tx_blobs(val: &TransactionBlobs) -> bool { + !val.is_empty() +} + #[cfg(test)] mod tests { diff --git a/net/monero-wire/src/p2p/protocol.rs b/net/monero-wire/src/p2p/protocol.rs index f2810a2e..1362e0ad 100644 --- a/net/monero-wire/src/p2p/protocol.rs +++ b/net/monero-wire/src/p2p/protocol.rs @@ -18,16 +18,15 @@ //! Protocol message requests don't have to be responded to in order unlike //! admin messages. -use serde::{Deserialize, Serialize}; -use serde_bytes::ByteBuf; +use bytes::Bytes; -use monero_epee_bin_serde::container_as_blob; +use epee_encoding::{container_as_blob::ContainerAsBlob, epee_object}; +use fixed_bytes::{ByteArray, ByteArrayVec}; use super::common::BlockCompleteEntry; -use crate::serde_helpers::*; /// A block that SHOULD have transactions -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NewBlock { /// Block with txs pub b: BlockCompleteEntry, @@ -35,63 +34,80 @@ pub struct NewBlock { pub current_blockchain_height: u64, } +epee_object!( + NewBlock, + b: BlockCompleteEntry, + current_blockchain_height: u64, +); + /// New Tx Pool Transactions -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NewTransactions { /// Tx Blobs - pub txs: Vec, + pub txs: Vec, /// Dandelionpp true if fluff - backwards compatible mode is fluff - #[serde(default = "default_true")] pub dandelionpp_fluff: bool, /// Padding - #[serde(rename = "_")] - #[serde(with = "serde_bytes")] - pub padding: Vec, + pub padding: Bytes, } +epee_object!( + NewTransactions, + txs: Vec, + dandelionpp_fluff: bool = true, + padding("_"): Bytes, +); + /// A Request For Blocks -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct GetObjectsRequest { /// Block hashes we want - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] - pub blocks: Vec<[u8; 32]>, + pub blocks: ByteArrayVec<32>, /// Pruned - #[serde(default = "default_false")] pub pruned: bool, } +epee_object!( + GetObjectsRequest, + blocks: ByteArrayVec<32>, + pruned: bool = false, +); + /// A Blocks Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct GetObjectsResponse { /// Blocks - // We dont need to give this a default value as there always is at least 1 block pub blocks: Vec, /// Missed IDs - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] - pub missed_ids: Vec<[u8; 32]>, + pub missed_ids: ByteArrayVec<32>, /// The height of the peers blockchain pub current_blockchain_height: u64, } +epee_object!( + GetObjectsResponse, + blocks: Vec, + missed_ids: ByteArrayVec<32>, + current_blockchain_height: u64, +); + /// A Chain Request -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ChainRequest { /// Block IDs - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] - pub block_ids: Vec<[u8; 32]>, + pub block_ids: ByteArrayVec<32>, /// Prune - #[serde(default = "default_false")] pub prune: bool, } +epee_object!( + ChainRequest, + block_ids: ByteArrayVec<32>, + prune: bool = false, +); + /// A Chain Response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ChainResponse { /// Start Height pub start_height: u64, @@ -100,26 +116,28 @@ pub struct ChainResponse { /// Cumulative Difficulty Low pub cumulative_difficulty: u64, /// Cumulative Difficulty High - #[serde(default = "default_zero")] pub cumulative_difficulty_top64: u64, /// Block IDs - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] - pub m_block_ids: Vec<[u8; 32]>, + pub m_block_ids: ByteArrayVec<32>, /// Block Weights - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] pub m_block_weights: Vec, /// The first Block in the response - #[serde(default = "Vec::new")] - #[serde(with = "serde_bytes")] - pub first_block: Vec, + pub first_block: Bytes, } +epee_object!( + ChainResponse, + start_height: u64, + total_height: u64, + cumulative_difficulty: u64, + cumulative_difficulty_top64: u64 = 0_u64, + m_block_ids: ByteArrayVec<32>, + m_block_weights: Vec as ContainerAsBlob, + first_block: Bytes, +); + /// A Block that doesn't have transactions unless requested -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NewFluffyBlock { /// Block which might have transactions pub b: BlockCompleteEntry, @@ -127,30 +145,42 @@ pub struct NewFluffyBlock { pub current_blockchain_height: u64, } +epee_object!( + NewFluffyBlock, + b: BlockCompleteEntry, + current_blockchain_height: u64, +); + /// A request for Txs we are missing from our TxPool -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FluffyMissingTransactionsRequest { /// The Block we are missing the Txs in - pub block_hash: [u8; 32], + pub block_hash: ByteArray<32>, /// The current blockchain height pub current_blockchain_height: u64, /// The Tx Indices - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] pub missing_tx_indices: Vec, } +epee_object!( + FluffyMissingTransactionsRequest, + block_hash: ByteArray<32>, + current_blockchain_height: u64, + missing_tx_indices: Vec as ContainerAsBlob, +); + /// TxPoolCompliment -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct GetTxPoolCompliment { /// Tx Hashes - #[serde(default = "Vec::new")] - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(with = "container_as_blob")] - pub hashes: Vec<[u8; 32]>, + pub hashes: ByteArrayVec<32>, } +epee_object!( + GetTxPoolCompliment, + hashes: ByteArrayVec<32>, +); + #[cfg(test)] mod tests { @@ -667,13 +697,13 @@ mod tests { 248, 248, 91, 110, 107, 144, 12, 175, 253, 21, 121, 28, ]; - let new_transactions: NewTransactions = monero_epee_bin_serde::from_bytes(bytes).unwrap(); + let new_transactions: NewTransactions = epee_encoding::from_bytes(&mut &bytes[..]).unwrap(); assert_eq!(4, new_transactions.txs.len()); - let encoded_bytes = monero_epee_bin_serde::to_bytes(&new_transactions).unwrap(); + let mut encoded_bytes = epee_encoding::to_bytes(new_transactions.clone()).unwrap(); let new_transactions_2: NewTransactions = - monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap(); + epee_encoding::from_bytes(&mut encoded_bytes).unwrap(); assert_eq!(new_transactions, new_transactions_2); } @@ -1019,11 +1049,10 @@ mod tests { 101, 110, 116, 95, 98, 108, 111, 99, 107, 99, 104, 97, 105, 110, 95, 104, 101, 105, 103, 104, 116, 5, 209, 45, 42, 0, 0, 0, 0, 0, ]; - let fluffy_block: NewFluffyBlock = monero_epee_bin_serde::from_bytes(bytes).unwrap(); + let fluffy_block: NewFluffyBlock = epee_encoding::from_bytes(&mut &bytes[..]).unwrap(); - let encoded_bytes = monero_epee_bin_serde::to_bytes(&fluffy_block).unwrap(); - let fluffy_block_2: NewFluffyBlock = - monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap(); + let mut encoded_bytes = epee_encoding::to_bytes(fluffy_block.clone()).unwrap(); + let fluffy_block_2: NewFluffyBlock = epee_encoding::from_bytes(&mut encoded_bytes).unwrap(); assert_eq!(fluffy_block, fluffy_block_2); } diff --git a/net/monero-wire/src/serde_helpers.rs b/net/monero-wire/src/serde_helpers.rs deleted file mode 100644 index 5b61876e..00000000 --- a/net/monero-wire/src/serde_helpers.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub(crate) fn default_false() -> bool { - false -} - -pub(crate) fn default_true() -> bool { - true -} - -pub(crate) fn default_zero>() -> T { - 0.try_into() - .map_err(|_| "Couldn't fit 0 into integer type!") - .unwrap() -} - -pub(crate) mod serde_vec_bytes { - use serde::{Deserialize, Deserializer, Serializer}; - use serde_bytes::ByteBuf; - - pub fn deserialize<'de, D>(d: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - Ok(Vec::::deserialize(d)? - .into_iter() - .map(ByteBuf::into_vec) - .collect()) - } - - pub fn serialize(t: &[Vec], s: S) -> Result - where - S: Serializer, - { - s.collect_seq(t.iter()) - } -} diff --git a/p2p/monero-p2p/tests/handshake.rs b/p2p/monero-p2p/tests/handshake.rs index fb53ac60..4f0f2287 100644 --- a/p2p/monero-p2p/tests/handshake.rs +++ b/p2p/monero-p2p/tests/handshake.rs @@ -32,7 +32,7 @@ async fn handshake_cuprate_to_cuprate() { let our_basic_node_data_1 = BasicNodeData { my_port: 0, - network_id: Network::Mainnet.network_id(), + network_id: Network::Mainnet.network_id().into(), peer_id: 87980, // TODO: This fails if the support flags are empty (0) support_flags: PeerSupportFlags::from(1_u32), @@ -113,7 +113,7 @@ async fn handshake() { let our_basic_node_data = BasicNodeData { my_port: 0, - network_id: Network::Mainnet.network_id(), + network_id: Network::Mainnet.network_id().into(), peer_id: 87980, support_flags: PeerSupportFlags::from(1_u32), rpc_port: 0,