net: use epee_encoding instead of monero-epee-bin-serde

This gives us more control than what serde provides. This
PR also moves to use `Bytes` where possible to allow
zero-copy parsing of network messages.
This commit is contained in:
Boog900 2024-01-30 16:09:54 +00:00
parent 2b65be4b18
commit 83b59c557c
No known key found for this signature in database
GPG key ID: 5401367FB7302004
30 changed files with 709 additions and 338 deletions

48
Cargo.lock generated
View file

@ -438,10 +438,10 @@ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"dalek-ff-group", "dalek-ff-group",
"dirs", "dirs",
"epee-encoding",
"futures", "futures",
"hex", "hex",
"monero-consensus", "monero-consensus",
"monero-epee-bin-serde",
"monero-serai", "monero-serai",
"monero-wire", "monero-wire",
"multiexp", "multiexp",
@ -604,6 +604,7 @@ dependencies = [
"fixed-bytes", "fixed-bytes",
"hex", "hex",
"paste", "paste",
"ref-cast",
"sealed", "sealed",
"thiserror", "thiserror",
] ]
@ -1142,16 +1143,6 @@ dependencies = [
"tracing", "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]] [[package]]
name = "monero-generators" name = "monero-generators"
version = "0.4.0" version = "0.4.0"
@ -1232,11 +1223,11 @@ dependencies = [
name = "monero-wire" name = "monero-wire"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytes",
"epee-encoding",
"fixed-bytes",
"hex", "hex",
"levin-cuprate", "levin-cuprate",
"monero-epee-bin-serde",
"serde",
"serde_bytes",
"thiserror", "thiserror",
] ]
@ -1587,6 +1578,26 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.2" version = "0.8.2"
@ -1770,15 +1781,6 @@ dependencies = [
"serde_derive", "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]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.195" version = "1.0.195"

View file

@ -21,7 +21,7 @@ binaries = [
"dep:tracing-subscriber", "dep:tracing-subscriber",
"dep:serde_json", "dep:serde_json",
"dep:serde", "dep:serde",
"dep:monero-epee-bin-serde", "dep:epee-encoding",
"dep:monero-wire", "dep:monero-wire",
"dep:borsh", "dep:borsh",
"dep:dirs", "dep:dirs",
@ -51,7 +51,7 @@ hex = "0.4"
# used in binaries # used in binaries
monero-wire = {path="../net/monero-wire", optional = true} 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_json = {version = "1", optional = true}
serde = {version = "1", optional = true, features = ["derive"]} serde = {version = "1", optional = true, features = ["derive"]}
tracing-subscriber = {version = "0.3", optional = true} tracing-subscriber = {version = "0.3", optional = true}

View file

@ -1,3 +1,4 @@
use std::ops::Deref;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
ops::Range, ops::Range,
@ -15,9 +16,9 @@ use monero_serai::{
rpc::{HttpRpc, Rpc}, rpc::{HttpRpc, Rpc},
transaction::Transaction, transaction::Transaction,
}; };
use monero_wire::common::{BlockCompleteEntry, TransactionBlobs}; use monero_wire::common::TransactionBlobs;
use rayon::prelude::*; use rayon::prelude::*;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use tokio::{ use tokio::{
sync::RwLock, sync::RwLock,
@ -174,41 +175,56 @@ impl RpcConnection {
) -> Result<Vec<(Block, Vec<Transaction>)>, tower::BoxError> { ) -> Result<Vec<(Block, Vec<Transaction>)>, tower::BoxError> {
tracing::info!("Getting blocks in range: {:?}", range); tracing::info!("Getting blocks in range: {:?}", range);
#[derive(Serialize)] mod items {
pub struct Request { use monero_wire::common::BlockCompleteEntry;
pub heights: Vec<u64>,
}
#[derive(Deserialize)] pub struct Request {
pub struct Response { pub heights: Vec<u64>,
pub blocks: Vec<BlockCompleteEntry>, }
epee_encoding::epee_object!(
Request,
heights: Vec<u64>,
);
pub struct Response {
pub blocks: Vec<BlockCompleteEntry>,
}
epee_encoding::epee_object!(
Response,
blocks: Vec<BlockCompleteEntry>,
);
} }
use items::*;
let res = self let res = self
.con .con
.bin_call( .bin_call(
"get_blocks_by_height.bin", "get_blocks_by_height.bin",
monero_epee_bin_serde::to_bytes(&Request { epee_encoding::to_bytes(Request {
heights: range.collect(), heights: range.collect(),
})?, })?
.to_vec(),
) )
.await?; .await?;
let address = self.address.clone(); let address = self.address.clone();
rayon_spawn_async(move || { 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
.blocks .blocks
.into_par_iter() .into_par_iter()
.map(|b| { .map(|b| {
let block = Block::read(&mut b.block.as_slice())?; let block = Block::read(&mut b.block.deref())?;
let txs = match b.txs { let txs = match b.txs {
TransactionBlobs::Pruned(_) => return Err("node sent pruned txs!".into()), TransactionBlobs::Pruned(_) => return Err("node sent pruned txs!".into()),
TransactionBlobs::Normal(txs) => txs TransactionBlobs::Normal(txs) => txs
.into_par_iter() .into_par_iter()
.map(|tx| Transaction::read(&mut tx.as_slice())) .map(|tx| Transaction::read(&mut tx.deref()))
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
TransactionBlobs::None => vec![], TransactionBlobs::None => vec![],
}; };
@ -237,29 +253,56 @@ impl RpcConnection {
out_ids.values().map(|amt_map| amt_map.len()).sum::<usize>() out_ids.values().map(|amt_map| amt_map.len()).sum::<usize>()
); );
#[derive(Serialize, Copy, Clone)] mod items {
struct OutputID {
amount: u64, #[derive(Copy, Clone)]
index: u64, 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<OutputID>,
}
epee_encoding::epee_object!(
Request,
outputs: Vec<OutputID>,
);
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<OutputRes>,
}
epee_encoding::epee_object!(
Response,
outs: Vec<OutputRes>,
);
} }
#[derive(Serialize, Clone)] use items::*;
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<OutputRes>,
}
let outputs = rayon_spawn_async(|| { let outputs = rayon_spawn_async(|| {
out_ids out_ids
@ -281,7 +324,10 @@ impl RpcConnection {
.con .con
.bin_call( .bin_call(
"get_outs.bin", "get_outs.bin",
monero_epee_bin_serde::to_bytes(&Request { outputs: &outputs })?, epee_encoding::to_bytes(Request {
outputs: outputs.clone(),
})?
.to_vec(),
) )
.await?; .await?;
@ -289,7 +335,8 @@ impl RpcConnection {
let span = tracing::Span::current(); let span = tracing::Span::current();
rayon_spawn_async(move || { 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()); tracing::info!(parent: &span, "Got outputs len: {}", outs.outs.len());

View file

@ -19,6 +19,7 @@ fixed-bytes = { path = "../fixed-bytes", default-features = false }
sealed = "0.5.0" sealed = "0.5.0"
paste = "1.0.14" paste = "1.0.14"
ref-cast = "1.0.22"
bytes = { workspace = true } bytes = { workspace = true }
thiserror = { workspace = true, optional = true} thiserror = { workspace = true, optional = true}

View file

@ -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<T: Containerable + EpeeValue>(Vec<T>);
impl<T: Containerable + EpeeValue> From<Vec<T>> for ContainerAsBlob<T> {
fn from(value: Vec<T>) -> Self {
ContainerAsBlob(value)
}
}
impl<T: Containerable + EpeeValue> From<ContainerAsBlob<T>> for Vec<T> {
fn from(value: ContainerAsBlob<T>) -> Self {
value.0
}
}
impl<'a, T: Containerable + EpeeValue> From<&'a Vec<T>> for &'a ContainerAsBlob<T> {
fn from(value: &'a Vec<T>) -> Self {
ContainerAsBlob::ref_cast(value)
}
}
#[sealed]
impl<T: Containerable + EpeeValue> EpeeValue for ContainerAsBlob<T> {
const MARKER: Marker = Marker::new(InnerMarker::String);
fn read<B: Buf>(r: &mut B, marker: &Marker) -> Result<Self> {
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<Self> {
Some(ContainerAsBlob(vec![]))
}
fn write<B: BufMut>(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);

View file

@ -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 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(); //! 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 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(); //! let data = to_bytes(val).unwrap();
//! //!
//! ``` //! ```
extern crate alloc; extern crate alloc;
use alloc::vec::Vec;
use core::{ops::Deref, str::from_utf8 as str_from_utf8}; 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; pub mod error;
mod io; mod io;
pub mod macros; pub mod macros;
@ -139,13 +139,13 @@ pub trait EpeeObject: Sized {
} }
/// Read the object `T` from a byte array. /// Read the object `T` from a byte array.
pub fn from_bytes<T: EpeeObject>(mut buf: &[u8]) -> Result<T> { pub fn from_bytes<T: EpeeObject, B: Buf>(buf: &mut B) -> Result<T> {
read_head_object(&mut buf) read_head_object(buf)
} }
/// Turn the object into epee bytes. /// Turn the object into epee bytes.
pub fn to_bytes<T: EpeeObject>(val: T) -> Result<Vec<u8>> { pub fn to_bytes<T: EpeeObject>(val: T) -> Result<BytesMut> {
let mut buf = Vec::<u8>::new(); let mut buf = BytesMut::new();
write_head_object(val, &mut buf)?; write_head_object(val, &mut buf)?;
Ok(buf) Ok(buf)
} }

View file

@ -3,7 +3,7 @@ pub use paste::paste;
#[macro_export] #[macro_export]
macro_rules! field_name { macro_rules! field_name {
($field: ident, $alt_name: literal) => { ($field: tt, $alt_name: tt) => {
$alt_name $alt_name
}; };
($field: ident,) => { ($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_export]
macro_rules! epee_object { macro_rules! epee_object {
( (
$obj:ident, $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 ,)+)? $(!flatten: $($flat_field: ident: $flat_ty:ty ,)+)?
) => { ) => {
@ -44,7 +54,9 @@ macro_rules! epee_object {
fn add_field<B: epee_encoding::macros::bytes::Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::error::Result<bool> { fn add_field<B: epee_encoding::macros::bytes::Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::error::Result<bool> {
match name { match name {
$(epee_encoding::field_name!($field, $($alt_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"))?; Err(epee_encoding::error::Error::Value("Duplicate field in data"))?;
} }
Ok(true) Ok(true)
@ -65,11 +77,18 @@ macro_rules! epee_object {
Ok( Ok(
$obj { $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(Some($default)))?
.or(epee_encoding::EpeeValue::epee_default_value()) .or(epee_default_value)
$(.map(<$ty_as>::into))? $(.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; 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; fields += 1;
} }
)+ )+
@ -106,8 +128,11 @@ macro_rules! epee_object {
fn write_fields<B: epee_encoding::macros::bytes::BufMut>(self, w: &mut B) -> epee_encoding::error::Result<()> { fn write_fields<B: epee_encoding::macros::bytes::BufMut>(self, w: &mut B) -> epee_encoding::error::Result<()> {
$( $(
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))? );
epee_encoding::write_field($(<$ty_as>::from)?(self.$field), epee_encoding::field_name!($field, $($alt_name)?), w)?;
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)?;
} }
)+ )+

View file

@ -3,7 +3,7 @@ use alloc::{string::String, vec::Vec};
/// the different possible base epee values. /// the different possible base epee values.
use core::fmt::Debug; use core::fmt::Debug;
use bytes::{Buf, BufMut, Bytes}; use bytes::{Buf, BufMut, Bytes, BytesMut};
use sealed::sealed; use sealed::sealed;
use fixed_bytes::{ByteArray, ByteArrayVec}; 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 /// 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 /// defined in the lib, to make an [`EpeeValue`] outside the lib you will need to
/// use the trait [`EpeeObject`]. /// use the trait [`EpeeObject`].
#[sealed] #[sealed(pub(crate))]
pub trait EpeeValue: Sized { pub trait EpeeValue: Sized {
const MARKER: Marker; 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<B: Buf>(r: &mut B, marker: &Marker) -> Result<Self> {
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<B: BufMut>(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] #[sealed::sealed]
impl<const N: usize> EpeeValue for ByteArrayVec<N> { impl<const N: usize> EpeeValue for ByteArrayVec<N> {
const MARKER: Marker = Marker::new(InnerMarker::String); const MARKER: Marker = Marker::new(InnerMarker::String);
@ -481,6 +517,8 @@ epee_seq!(f64);
epee_seq!(bool); epee_seq!(bool);
epee_seq!(Vec<u8>); epee_seq!(Vec<u8>);
epee_seq!(String); epee_seq!(String);
epee_seq!(Bytes);
epee_seq!(BytesMut);
#[sealed] #[sealed]
impl<T: EpeeValue> EpeeValue for Option<T> { impl<T: EpeeValue> EpeeValue for Option<T> {

View file

@ -27,7 +27,7 @@ fn epee_alt_name() {
let val2 = AltName2 { val2: 40, d: 30 }; let val2 = AltName2 { val2: 40, d: 30 };
let bytes = to_bytes(val2).unwrap(); 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(); let bytes2 = to_bytes(val).unwrap();

View file

@ -25,7 +25,7 @@ fn duplicate_key() {
b'a', 0x0B, 0x00, b'a', 0x0B, 0x00,
]; ];
assert!(from_bytes::<T>(&data).is_err()); assert!(from_bytes::<T, _>(&mut &data[..]).is_err());
} }
#[test] #[test]
@ -35,5 +35,5 @@ fn duplicate_key_with_default() {
b'a', 0x0B, 0x00, b'a', 0x0B, 0x00,
]; ];
assert!(from_bytes::<TT>(&data).is_err()); assert!(from_bytes::<TT, _>(&mut &data[..]).is_err());
} }

View file

@ -8,7 +8,7 @@ pub struct Optional {
epee_object!( epee_object!(
Optional, Optional,
val: u8, val: u8,
optional_val: i32 = -4, optional_val: i32 = -4_i32,
); );
pub struct NotOptional { pub struct NotOptional {
val: u8, val: u8,
@ -37,11 +37,11 @@ fn epee_default_does_not_encode() {
val: 1, val: 1,
optional_val: -4, optional_val: -4,
}; };
let bytes = to_bytes(val).unwrap(); let mut bytes = to_bytes(val).unwrap().freeze();
assert!(from_bytes::<NotOptional>(&bytes).is_err()); assert!(from_bytes::<NotOptional, _>(&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.optional_val, -4);
assert_eq!(val.val, 1); assert_eq!(val.val, 1);
} }
@ -52,11 +52,11 @@ fn epee_non_default_does_encode() {
val: 8, val: 8,
optional_val: -3, optional_val: -3,
}; };
let bytes = to_bytes(val).unwrap(); let mut bytes = to_bytes(val).unwrap().freeze();
assert!(from_bytes::<NotOptional>(&bytes).is_ok()); assert!(from_bytes::<NotOptional, _>(&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.optional_val, -3);
assert_eq!(val.val, 8) assert_eq!(val.val, 8)
} }
@ -64,11 +64,11 @@ fn epee_non_default_does_encode() {
#[test] #[test]
fn epee_value_not_present_with_default() { fn epee_value_not_present_with_default() {
let val = NotPresent { val: 76 }; let val = NotPresent { val: 76 };
let bytes = to_bytes(val).unwrap(); let mut bytes = to_bytes(val).unwrap().freeze();
assert!(from_bytes::<NotOptional>(&bytes).is_err()); assert!(from_bytes::<NotOptional, _>(&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.optional_val, -4);
assert_eq!(val.val, 76) assert_eq!(val.val, 76)
} }

View file

@ -43,9 +43,9 @@ fn epee_flatten() {
val: 94, val: 94,
val2: vec![4, 5], 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.val2, val2.val2);
assert_eq!(val.child.val, val2.val); assert_eq!(val.child.val, val2.val);
@ -95,8 +95,8 @@ epee_object!(
fn epee_double_flatten() { fn epee_double_flatten() {
let val = Parent12::default(); let val = Parent12::default();
let bytes = to_bytes(val.clone()).unwrap(); let mut bytes = to_bytes(val.clone()).unwrap();
let val1: Parent12 = from_bytes(&bytes).unwrap(); let val1: Parent12 = from_bytes(&mut bytes).unwrap();
assert_eq!(val, val1); assert_eq!(val, val1);
} }

View file

@ -1,4 +1,5 @@
use epee_encoding::{epee_object, from_bytes, to_bytes}; use epee_encoding::{epee_object, from_bytes, to_bytes};
use std::ops::Deref;
#[derive(Clone)] #[derive(Clone)]
struct T { struct T {
@ -11,9 +12,10 @@ epee_object!(
); );
#[test] #[test]
#[allow(clippy::useless_asref)]
fn optional_val_not_in_data() { fn optional_val_not_in_data() {
let bytes: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01\x00"; 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(); let bytes2 = to_bytes(t.clone()).unwrap();
assert_eq!(bytes, bytes2); assert_eq!(bytes, bytes2);
assert!(t.val.is_none()); assert!(t.val.is_none());
@ -24,8 +26,8 @@ fn optional_val_in_data() {
let bytes = [ let bytes = [
0x01, 0x11, 0x01, 0x1, 0x01, 0x01, 0x02, 0x1, 0x1, 0x04, 0x03, b'v', b'a', b'l', 0x08, 21, 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(); 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); assert_eq!(t.val.unwrap(), 21);
} }

View file

@ -51,9 +51,9 @@ epee_object!(
fn p2p_handshake() { fn p2p_handshake() {
let bytes = hex::decode("01110101010102010108096e6f64655f646174610c10076d795f706f727406a04600000a6e6574776f726b5f69640a401230f171610441611731008216a1a11007706565725f6964053eb3c096c4471c340d737570706f72745f666c61677306010000000c7061796c6f61645f646174610c181563756d756c61746976655f646966666963756c7479053951f7a79aab4a031b63756d756c61746976655f646966666963756c74795f746f7036340500000000000000000e63757272656e745f68656967687405fa092a00000000000c7072756e696e675f73656564068001000006746f705f69640a806cc497b230ba57a95edb370be8d6870c94e0992937c89b1def3a4cb7726d37ad0b746f705f76657273696f6e0810").unwrap(); 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());
} }

View file

@ -63,21 +63,21 @@ epee_object!(
#[test] #[test]
fn rpc_get_outs_response() { fn rpc_get_outs_response() {
let bytes = hex::decode("011101010101020101140763726564697473050000000000000000046f7574738c04140668656967687405a100000000000000036b65790a802d392d0be38eb4699c17767e62a063b8d2f989ec15c80e5d2665ab06f8397439046d61736b0a805e8b863c5b267deda13f4bc5d5ec8e59043028380f2431bc8691c15c83e1fea404747869640a80c0646e065a33b849f0d9563673ca48eb0c603fe721dd982720dba463172c246f08756e6c6f636b65640b00067374617475730a084f4b08746f705f686173680a0009756e747275737465640b00").unwrap(); let bytes = hex::decode("011101010101020101140763726564697473050000000000000000046f7574738c04140668656967687405a100000000000000036b65790a802d392d0be38eb4699c17767e62a063b8d2f989ec15c80e5d2665ab06f8397439046d61736b0a805e8b863c5b267deda13f4bc5d5ec8e59043028380f2431bc8691c15c83e1fea404747869640a80c0646e065a33b849f0d9563673ca48eb0c603fe721dd982720dba463172c246f08756e6c6f636b65640b00067374617475730a084f4b08746f705f686173680a0009756e747275737465640b00").unwrap();
let val: GetOutsResponse = from_bytes(&bytes).unwrap(); let val: GetOutsResponse = 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());
} }
#[test] #[test]
fn get_out_indexes_response() { 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, 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, 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, 104, 10, 0, 9, 117, 110, 116, 114, 117, 115, 116, 101, 100, 11, 0,
]; ];
let val: GetOIndexesResponse = from_bytes(&bytes).unwrap(); let val: GetOIndexesResponse = 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());
} }

View file

@ -28,9 +28,9 @@ fn seq_with_zero_len_can_have_any_marker() {
data.push(0x80 | marker); data.push(0x80 | marker);
data.push(0); data.push(0);
assert!(from_bytes::<ObjSeq>(&data).is_ok()); assert!(from_bytes::<ObjSeq, _>(&mut data.as_slice()).is_ok());
assert!(from_bytes::<ValSeq>(&data).is_ok()); assert!(from_bytes::<ValSeq, _>(&mut data.as_slice()).is_ok());
data.drain(14..); 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.push(0x04); // varint length of 1
data.extend_from_slice(&1_i64.to_le_bytes()); data.extend_from_slice(&1_i64.to_le_bytes());
assert!(from_bytes::<ValSeq>(&data).is_err()); assert!(from_bytes::<ValSeq, _>(&mut data.as_slice()).is_err());
data.drain(14..); data.drain(14..);
} }
@ -56,5 +56,5 @@ fn seq_with_non_zero_len_must_have_correct_marker() {
data.push(0x80 + 1); data.push(0x80 + 1);
data.push(0x04); // varint length data.push(0x04); // varint length
data.extend_from_slice(&1_i64.to_le_bytes()); data.extend_from_slice(&1_i64.to_le_bytes());
(from_bytes::<ValSeq>(&data).unwrap()); (from_bytes::<ValSeq, _>(&mut data.as_slice()).unwrap());
} }

View file

@ -735,7 +735,7 @@ fn stack_overlfow() {
8, 7, 1, 100, 12, 8, 3, 118, 97, 108, 8, 7, 8, 7, 1, 100, 12, 8, 3, 118, 97, 108, 8, 7,
]; ];
let obj: Result<Q, _> = from_bytes(&bytes); let obj: Result<Q, _> = from_bytes(&mut bytes.as_slice());
assert!(obj.is_err()) assert!(obj.is_err())
} }

View file

@ -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`]. /// 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]`. /// This implements [`Deref`] with the target being `[u8; N]`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ByteArray<const N: usize>(Bytes); pub struct ByteArray<const N: usize>(Bytes);
impl<const N: usize> ByteArray<N> { impl<const N: usize> ByteArray<N> {
@ -49,6 +50,12 @@ impl<const N: usize> ByteArray<N> {
} }
} }
impl<const N: usize> From<[u8; N]> for ByteArray<N> {
fn from(value: [u8; N]) -> Self {
ByteArray(Bytes::copy_from_slice(&value))
}
}
impl<const N: usize> Deref for ByteArray<N> { impl<const N: usize> Deref for ByteArray<N> {
type Target = [u8; N]; type Target = [u8; N];
@ -68,6 +75,18 @@ impl<const N: usize> TryFrom<Bytes> for ByteArray<N> {
} }
} }
impl<const N: usize> TryFrom<Vec<u8>> for ByteArray<N> {
type Error = FixedByteError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() != N {
return Err(FixedByteError::InvalidLength);
}
Ok(ByteArray(Bytes::from(value)))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ByteArrayVec<const N: usize>(Bytes); pub struct ByteArrayVec<const N: usize>(Bytes);
impl<const N: usize> ByteArrayVec<N> { impl<const N: usize> ByteArrayVec<N> {

View file

@ -17,7 +17,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, Bytes, BytesMut};
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
use crate::{ use crate::{
@ -111,7 +111,7 @@ impl<C: LevinCommand> Decoder for LevinBucketCodec<C> {
return Ok(Some(Bucket { return Ok(Some(Bucket {
header, header,
body: src.copy_to_bytes(body_len).into(), body: src.copy_to_bytes(body_len),
})); }));
} }
} }
@ -138,7 +138,7 @@ impl<C: LevinCommand> Encoder<Bucket<C>> for LevinBucketCodec<C> {
enum MessageState<C> { enum MessageState<C> {
#[default] #[default]
WaitingForBucket, WaitingForBucket,
WaitingForRestOfFragment(Vec<u8>, MessageType, C), WaitingForRestOfFragment(Vec<Bytes>, MessageType, C),
} }
/// A tokio-codec for levin messages or in other words the decoded body /// A tokio-codec for levin messages or in other words the decoded body
@ -167,7 +167,7 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
loop { loop {
match &mut self.state { match &mut self.state {
MessageState::WaitingForBucket => { MessageState::WaitingForBucket => {
let Some(bucket) = self.bucket_codec.decode(src)? else { let Some(mut bucket) = self.bucket_codec.decode(src)? else {
return Ok(None); return Ok(None);
}; };
@ -199,7 +199,7 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
let _ = std::mem::replace( let _ = std::mem::replace(
&mut self.state, &mut self.state,
MessageState::WaitingForRestOfFragment( MessageState::WaitingForRestOfFragment(
bucket.body.to_vec(), vec![bucket.body],
message_type, message_type,
bucket.header.command, bucket.header.command,
), ),
@ -209,7 +209,7 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
} }
return Ok(Some(T::decode_message( return Ok(Some(T::decode_message(
&bucket.body, &mut bucket.body,
message_type, message_type,
bucket.header.command, bucket.header.command,
)?)); )?));
@ -257,16 +257,23 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
)); ));
} }
bytes.append(&mut bucket.body.to_vec()); bytes.push(bucket.body);
if flags.is_end_fragment() { 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) std::mem::replace(&mut self.state, MessageState::WaitingForBucket)
else { else {
unreachable!(); 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<dyn Buf> = 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)?));
} }
} }
} }

View file

@ -41,6 +41,7 @@ pub use header::BucketHead;
use std::fmt::Debug; use std::fmt::Debug;
use bytes::{Buf, Bytes};
use thiserror::Error; use thiserror::Error;
const MONERO_PROTOCOL_VERSION: u32 = 1; const MONERO_PROTOCOL_VERSION: u32 = 1;
@ -102,7 +103,7 @@ pub struct Bucket<C> {
/// The bucket header /// The bucket header
pub header: BucketHead<C>, pub header: BucketHead<C>,
/// The bucket body /// The bucket body
pub body: Vec<u8>, pub body: Bytes,
} }
/// An enum representing if the message is a request, response or notification. /// An enum representing if the message is a request, response or notification.
@ -158,7 +159,7 @@ pub struct BucketBuilder<C> {
command: Option<C>, command: Option<C>,
return_code: Option<i32>, return_code: Option<i32>,
protocol_version: Option<u32>, protocol_version: Option<u32>,
body: Option<Vec<u8>>, body: Option<Bytes>,
} }
impl<C> Default for BucketBuilder<C> { impl<C> Default for BucketBuilder<C> {
@ -195,7 +196,7 @@ impl<C: LevinCommand> BucketBuilder<C> {
self.protocol_version = Some(version) self.protocol_version = Some(version)
} }
pub fn set_body(&mut self, body: Vec<u8>) { pub fn set_body(&mut self, body: Bytes) {
self.body = Some(body) self.body = Some(body)
} }
@ -222,14 +223,14 @@ pub trait LevinBody: Sized {
type Command: LevinCommand; type Command: LevinCommand;
/// Decodes the message from the data in the header /// Decodes the message from the data in the header
fn decode_message( fn decode_message<B: Buf>(
body: &[u8], body: &mut B,
typ: MessageType, typ: MessageType,
command: Self::Command, command: Self::Command,
) -> Result<Self, BucketError>; ) -> Result<Self, BucketError>;
/// Encodes the message /// Encodes the message
fn encode(&self, builder: &mut BucketBuilder<Self::Command>) -> Result<(), BucketError>; fn encode(self, builder: &mut BucketBuilder<Self::Command>) -> Result<(), BucketError>;
} }
/// The levin commands. /// The levin commands.

View file

@ -9,9 +9,10 @@ repository = "https://github.com/SyntheticBird45/cuprate/tree/main/net/monero-wi
[dependencies] [dependencies]
levin-cuprate = {path="../levin"} levin-cuprate = {path="../levin"}
monero-epee-bin-serde = { workspace = true, features = ["container_as_blob"] } epee-encoding = { path = "../epee-encoding" }
serde = { workspace = true, features = ["derive", "std"]} fixed-bytes = { path = "../fixed-bytes" }
serde_bytes = { workspace = true, features = ["std"]}
bytes = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
[dev-dependencies] [dev-dependencies]

View file

@ -24,7 +24,6 @@
pub mod network_address; pub mod network_address;
pub mod p2p; pub mod p2p;
mod serde_helpers;
pub use levin_cuprate::BucketError; pub use levin_cuprate::BucketError;
pub use network_address::{NetZone, NetworkAddress}; pub use network_address::{NetZone, NetworkAddress};

View file

@ -17,10 +17,10 @@
//! Monero network. Core Monero has 4 main addresses: IPv4, IPv6, Tor, //! Monero network. Core Monero has 4 main addresses: IPv4, IPv6, Tor,
//! I2p. Currently this module only has IPv(4/6). //! 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 std::{hash::Hash, net, net::SocketAddr};
use serde::{Deserialize, Serialize};
mod serde_helper; mod serde_helper;
use serde_helper::*; use serde_helper::*;
@ -33,13 +33,23 @@ pub enum NetZone {
/// A network address which can be encoded into the format required /// A network address which can be encoded into the format required
/// to send to other Monero peers. /// to send to other Monero peers.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(try_from = "TaggedNetworkAddress")]
#[serde(into = "TaggedNetworkAddress")]
pub enum NetworkAddress { pub enum NetworkAddress {
Clear(SocketAddr), Clear(SocketAddr),
} }
impl EpeeObject for NetworkAddress {
type Builder = TaggedNetworkAddress;
fn number_of_fields(&self) -> u64 {
2
}
fn write_fields<B: BufMut>(self, w: &mut B) -> epee_encoding::Result<()> {
TaggedNetworkAddress::from(self).write(w)
}
}
impl NetworkAddress { impl NetworkAddress {
pub fn get_zone(&self) -> NetZone { pub fn get_zone(&self) -> NetZone {
match self { match self {

View file

@ -1,20 +1,53 @@
use bytes::Buf;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use serde::{Deserialize, Serialize}; use epee_encoding::{epee_object, EpeeObjectBuilder};
use thiserror::Error; use thiserror::Error;
use crate::NetworkAddress; use crate::NetworkAddress;
#[derive(Serialize, Deserialize)] #[derive(Default)]
pub(crate) struct TaggedNetworkAddress { pub struct TaggedNetworkAddress {
#[serde(rename = "type")] ty: Option<u8>,
ty: u8, addr: Option<AllFieldsNetworkAddress>,
addr: AllFieldsNetworkAddress, }
epee_object!(
TaggedNetworkAddress,
ty: Option<u8>,
addr: Option<AllFieldsNetworkAddress>,
);
impl EpeeObjectBuilder<NetworkAddress> for TaggedNetworkAddress {
fn add_field<B: Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::Result<bool> {
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<NetworkAddress> {
self.try_into()
.map_err(|_| epee_encoding::Error::Value("Invalid network address"))
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("Invalid network address")] #[error("Invalid network address")]
pub(crate) struct InvalidNetworkAddress; pub struct InvalidNetworkAddress;
impl TryFrom<TaggedNetworkAddress> for NetworkAddress { impl TryFrom<TaggedNetworkAddress> for NetworkAddress {
type Error = InvalidNetworkAddress; type Error = InvalidNetworkAddress;
@ -22,7 +55,8 @@ impl TryFrom<TaggedNetworkAddress> for NetworkAddress {
fn try_from(value: TaggedNetworkAddress) -> Result<Self, Self::Error> { fn try_from(value: TaggedNetworkAddress) -> Result<Self, Self::Error> {
value value
.addr .addr
.try_into_network_address(value.ty) .ok_or(InvalidNetworkAddress)?
.try_into_network_address(value.ty.ok_or(InvalidNetworkAddress)?)
.ok_or(InvalidNetworkAddress) .ok_or(InvalidNetworkAddress)
} }
} }
@ -32,36 +66,40 @@ impl From<NetworkAddress> for TaggedNetworkAddress {
match value { match value {
NetworkAddress::Clear(addr) => match addr { NetworkAddress::Clear(addr) => match addr {
SocketAddr::V4(addr) => TaggedNetworkAddress { SocketAddr::V4(addr) => TaggedNetworkAddress {
ty: 1, ty: Some(1),
addr: AllFieldsNetworkAddress { addr: Some(AllFieldsNetworkAddress {
m_ip: Some(u32::from_be_bytes(addr.ip().octets())), m_ip: Some(u32::from_be_bytes(addr.ip().octets())),
m_port: Some(addr.port()), m_port: Some(addr.port()),
..Default::default() ..Default::default()
}, }),
}, },
SocketAddr::V6(addr) => TaggedNetworkAddress { SocketAddr::V6(addr) => TaggedNetworkAddress {
ty: 2, ty: Some(2),
addr: AllFieldsNetworkAddress { addr: Some(AllFieldsNetworkAddress {
addr: Some(addr.ip().octets()), addr: Some(addr.ip().octets()),
m_port: Some(addr.port()), m_port: Some(addr.port()),
..Default::default() ..Default::default()
}, }),
}, },
}, },
} }
} }
} }
#[derive(Serialize, Deserialize, Default)] #[derive(Default)]
struct AllFieldsNetworkAddress { struct AllFieldsNetworkAddress {
#[serde(skip_serializing_if = "Option::is_none")]
m_ip: Option<u32>, m_ip: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
m_port: Option<u16>, m_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
addr: Option<[u8; 16]>, addr: Option<[u8; 16]>,
} }
epee_object!(
AllFieldsNetworkAddress,
m_ip: Option<u32>,
m_port: Option<u16>,
addr: Option<[u8; 16]>,
);
impl AllFieldsNetworkAddress { impl AllFieldsNetworkAddress {
fn try_into_network_address(self, ty: u8) -> Option<NetworkAddress> { fn try_into_network_address(self, ty: u8) -> Option<NetworkAddress> {
Some(match ty { Some(match ty {

View file

@ -16,6 +16,7 @@
//! This module defines a Monero `Message` enum which contains //! This module defines a Monero `Message` enum which contains
//! every possible Monero network message (levin body) //! every possible Monero network message (levin body)
use bytes::{Buf, Bytes, BytesMut};
use levin_cuprate::{ use levin_cuprate::{
BucketBuilder, BucketError, LevinBody, LevinCommand as LevinCommandTrait, MessageType, BucketBuilder, BucketError, LevinBody, LevinCommand as LevinCommandTrait, MessageType,
}; };
@ -150,23 +151,23 @@ impl From<LevinCommand> for u32 {
} }
} }
fn decode_message<T: serde::de::DeserializeOwned, Ret>( fn decode_message<B: Buf, T: epee_encoding::EpeeObject, Ret>(
ret: impl FnOnce(T) -> Ret, ret: impl FnOnce(T) -> Ret,
buf: &[u8], buf: &mut B,
) -> Result<Ret, BucketError> { ) -> Result<Ret, BucketError> {
let t = monero_epee_bin_serde::from_bytes(buf) let t = epee_encoding::from_bytes(buf).map_err(|e| BucketError::BodyDecodingError(e.into()))?;
.map_err(|e| BucketError::BodyDecodingError(e.into()))?;
Ok(ret(t)) Ok(ret(t))
} }
fn build_message<T: serde::Serialize>( fn build_message<T: epee_encoding::EpeeObject>(
id: LevinCommand, id: LevinCommand,
val: &T, val: T,
builder: &mut BucketBuilder<LevinCommand>, builder: &mut BucketBuilder<LevinCommand>,
) -> Result<(), BucketError> { ) -> Result<(), BucketError> {
builder.set_command(id); builder.set_command(id);
builder.set_body( 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()))?, .map_err(|e| BucketError::BodyDecodingError(e.into()))?,
); );
Ok(()) Ok(())
@ -201,7 +202,7 @@ impl ProtocolMessage {
} }
} }
fn decode(buf: &[u8], command: LevinCommand) -> Result<Self, BucketError> { fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C; use LevinCommand as C;
Ok(match command { Ok(match command {
@ -220,7 +221,7 @@ impl ProtocolMessage {
}) })
} }
fn build(&self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> { fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
use LevinCommand as C; use LevinCommand as C;
match self { match self {
@ -236,7 +237,7 @@ impl ProtocolMessage {
} }
ProtocolMessage::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?, ProtocolMessage::ChainRequest(val) => build_message(C::ChainRequest, val, builder)?,
ProtocolMessage::ChainEntryResponse(val) => { 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::NewFluffyBlock(val) => build_message(C::NewFluffyBlock, val, builder)?,
ProtocolMessage::FluffyMissingTransactionsRequest(val) => { ProtocolMessage::FluffyMissingTransactionsRequest(val) => {
@ -269,7 +270,7 @@ impl RequestMessage {
} }
} }
fn decode(buf: &[u8], command: LevinCommand) -> Result<Self, BucketError> { fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C; use LevinCommand as C;
Ok(match command { Ok(match command {
@ -281,7 +282,7 @@ impl RequestMessage {
}) })
} }
fn build(&self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> { fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
use LevinCommand as C; use LevinCommand as C;
match self { match self {
@ -289,11 +290,11 @@ impl RequestMessage {
RequestMessage::TimedSync(val) => build_message(C::TimedSync, val, builder)?, RequestMessage::TimedSync(val) => build_message(C::TimedSync, val, builder)?,
RequestMessage::Ping => { RequestMessage::Ping => {
builder.set_command(C::Ping); builder.set_command(C::Ping);
builder.set_body(Vec::new()); builder.set_body(Bytes::new());
} }
RequestMessage::SupportFlags => { RequestMessage::SupportFlags => {
builder.set_command(C::SupportFlags); builder.set_command(C::SupportFlags);
builder.set_body(Vec::new()); builder.set_body(Bytes::new());
} }
} }
Ok(()) Ok(())
@ -319,7 +320,7 @@ impl ResponseMessage {
} }
} }
fn decode(buf: &[u8], command: LevinCommand) -> Result<Self, BucketError> { fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C; use LevinCommand as C;
Ok(match command { Ok(match command {
@ -331,7 +332,7 @@ impl ResponseMessage {
}) })
} }
fn build(&self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> { fn build(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
use LevinCommand as C; use LevinCommand as C;
match self { match self {
@ -375,8 +376,8 @@ impl Message {
impl LevinBody for Message { impl LevinBody for Message {
type Command = LevinCommand; type Command = LevinCommand;
fn decode_message( fn decode_message<B: Buf>(
body: &[u8], body: &mut B,
typ: MessageType, typ: MessageType,
command: LevinCommand, command: LevinCommand,
) -> Result<Self, BucketError> { ) -> Result<Self, BucketError> {
@ -387,7 +388,7 @@ impl LevinBody for Message {
}) })
} }
fn encode(&self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> { fn encode(self, builder: &mut BucketBuilder<LevinCommand>) -> Result<(), BucketError> {
match self { match self {
Message::Protocol(pro) => { Message::Protocol(pro) => {
builder.set_message_type(MessageType::Notification); builder.set_message_type(MessageType::Notification);

View file

@ -18,12 +18,13 @@
//! Admin message requests must be responded to in order unlike //! Admin message requests must be responded to in order unlike
//! protocol messages. //! protocol messages.
use serde::{Deserialize, Serialize}; use bytes::Bytes;
use epee_encoding::epee_object;
use super::common::{BasicNodeData, CoreSyncData, PeerListEntryBase, PeerSupportFlags}; use super::common::{BasicNodeData, CoreSyncData, PeerListEntryBase, PeerSupportFlags};
/// A Handshake Request /// A Handshake Request
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandshakeRequest { pub struct HandshakeRequest {
/// Basic Node Data /// Basic Node Data
pub node_data: BasicNodeData, pub node_data: BasicNodeData,
@ -31,54 +32,87 @@ pub struct HandshakeRequest {
pub payload_data: CoreSyncData, pub payload_data: CoreSyncData,
} }
epee_object!(
HandshakeRequest,
node_data: BasicNodeData,
payload_data: CoreSyncData,
);
/// A Handshake Response /// A Handshake Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandshakeResponse { pub struct HandshakeResponse {
/// Basic Node Data /// Basic Node Data
pub node_data: BasicNodeData, pub node_data: BasicNodeData,
/// Core Sync Data /// Core Sync Data
pub payload_data: CoreSyncData, pub payload_data: CoreSyncData,
/// PeerList /// PeerList
#[serde(default = "Vec::new")]
pub local_peerlist_new: Vec<PeerListEntryBase>, pub local_peerlist_new: Vec<PeerListEntryBase>,
} }
epee_object!(
HandshakeResponse,
node_data: BasicNodeData,
payload_data: CoreSyncData,
local_peerlist_new: Vec<PeerListEntryBase>,
);
/// A TimedSync Request /// A TimedSync Request
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimedSyncRequest { pub struct TimedSyncRequest {
/// Core Sync Data /// Core Sync Data
pub payload_data: CoreSyncData, pub payload_data: CoreSyncData,
} }
epee_object!(
TimedSyncRequest,
payload_data: CoreSyncData,
);
/// A TimedSync Response /// A TimedSync Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimedSyncResponse { pub struct TimedSyncResponse {
/// Core Sync Data /// Core Sync Data
pub payload_data: CoreSyncData, pub payload_data: CoreSyncData,
/// PeerList /// PeerList
#[serde(default = "Vec::new")]
pub local_peerlist_new: Vec<PeerListEntryBase>, pub local_peerlist_new: Vec<PeerListEntryBase>,
} }
epee_object!(
TimedSyncResponse,
payload_data: CoreSyncData,
local_peerlist_new: Vec<PeerListEntryBase>,
);
/// The status field of an okay ping response /// 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 /// A Ping Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PingResponse { pub struct PingResponse {
/// Status: should be `PING_OK_RESPONSE_STATUS_TEXT` /// Status: should be `PING_OK_RESPONSE_STATUS_TEXT`
pub status: String, pub status: Bytes,
/// Peer ID /// Peer ID
pub peer_id: u64, pub peer_id: u64,
} }
epee_object!(
PingResponse,
status: Bytes,
peer_id: u64,
);
/// A Support Flags Response /// A Support Flags Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SupportFlagsResponse { pub struct SupportFlagsResponse {
/// Support Flags /// Support Flags
pub support_flags: PeerSupportFlags, pub support_flags: PeerSupportFlags,
} }
epee_object!(
SupportFlagsResponse,
support_flags: PeerSupportFlags as u32,
);
#[cfg(test)] #[cfg(test)]
mod tests { 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, 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, 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 { let basic_node_data = BasicNodeData {
my_port: 0, my_port: 0,
network_id: [ network_id: [
18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16,
], ]
.into(),
peer_id: 9671405426614699871, peer_id: 9671405426614699871,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,
@ -128,9 +163,8 @@ mod tests {
assert_eq!(basic_node_data, handshake.node_data); assert_eq!(basic_node_data, handshake.node_data);
assert_eq!(core_sync_data, handshake.payload_data); assert_eq!(core_sync_data, handshake.payload_data);
let encoded_bytes = monero_epee_bin_serde::to_bytes(&handshake).unwrap(); let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap();
let handshake_2: HandshakeRequest = let handshake_2: HandshakeRequest = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap();
assert_eq!(handshake, handshake_2); 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, 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, 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 { let basic_node_data = BasicNodeData {
my_port: 18080, my_port: 18080,
network_id: [ network_id: [
18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16, 18, 48, 241, 113, 97, 4, 65, 97, 23, 49, 0, 130, 22, 161, 161, 16,
], ]
.into(),
peer_id: 6037804360359455404, peer_id: 6037804360359455404,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 18089, rpc_port: 18089,
@ -935,9 +970,8 @@ mod tests {
assert_eq!(core_sync_data, handshake.payload_data); assert_eq!(core_sync_data, handshake.payload_data);
assert_eq!(250, handshake.local_peerlist_new.len()); assert_eq!(250, handshake.local_peerlist_new.len());
let encoded_bytes = monero_epee_bin_serde::to_bytes(&handshake).unwrap(); let mut encoded_bytes = epee_encoding::to_bytes(handshake.clone()).unwrap();
let handshake_2: HandshakeResponse = let handshake_2: HandshakeResponse = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap();
assert_eq!(handshake, handshake_2); assert_eq!(handshake, handshake_2);
} }

View file

@ -15,15 +15,13 @@
//! Common types that are used across multiple messages. //! 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::{ use crate::NetworkAddress;
serde_helpers::{default_false, default_zero, serde_vec_bytes},
NetworkAddress,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[serde(transparent)]
pub struct PeerSupportFlags(u32); pub struct PeerSupportFlags(u32);
impl From<u32> for PeerSupportFlags { impl From<u32> for PeerSupportFlags {
@ -38,6 +36,12 @@ impl From<PeerSupportFlags> for u32 {
} }
} }
impl<'a> From<&'a PeerSupportFlags> for &'a u32 {
fn from(value: &'a PeerSupportFlags) -> Self {
&value.0
}
}
impl PeerSupportFlags { impl PeerSupportFlags {
//const FLUFFY_BLOCKS: u32 = 0b0000_0001; //const FLUFFY_BLOCKS: u32 = 0b0000_0001;
@ -53,51 +57,65 @@ impl From<u8> for PeerSupportFlags {
} }
/// Basic Node Data, information on the connected peer /// Basic Node Data, information on the connected peer
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct BasicNodeData { pub struct BasicNodeData {
/// Port /// Port
pub my_port: u32, pub my_port: u32,
/// The Network Id /// The Network Id
pub network_id: [u8; 16], pub network_id: ByteArray<16>,
/// Peer ID /// Peer ID
pub peer_id: u64, pub peer_id: u64,
/// The Peers Support Flags /// The Peers Support Flags
/// (If this is not in the message the default is 0) /// (If this is not in the message the default is 0)
#[serde(default = "default_zero")]
pub support_flags: PeerSupportFlags, pub support_flags: PeerSupportFlags,
/// RPC Port /// RPC Port
/// (If this is not in the message the default is 0) /// (If this is not in the message the default is 0)
#[serde(default = "default_zero")]
pub rpc_port: u16, pub rpc_port: u16,
/// RPC Credits Per Hash /// RPC Credits Per Hash
/// (If this is not in the message the default is 0) /// (If this is not in the message the default is 0)
#[serde(default = "default_zero")]
pub rpc_credits_per_hash: u32, 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 /// 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 { pub struct CoreSyncData {
/// Cumulative Difficulty Low /// Cumulative Difficulty Low
/// The lower 64 bits of the 128 bit cumulative difficulty /// The lower 64 bits of the 128 bit cumulative difficulty
pub cumulative_difficulty: u64, pub cumulative_difficulty: u64,
/// Cumulative Difficulty High /// Cumulative Difficulty High
/// The upper 64 bits of the 128 bit cumulative difficulty /// The upper 64 bits of the 128 bit cumulative difficulty
#[serde(default = "default_zero")]
pub cumulative_difficulty_top64: u64, pub cumulative_difficulty_top64: u64,
/// Current Height of the peer /// Current Height of the peer
pub current_height: u64, pub current_height: u64,
/// Pruning Seed of the peer /// Pruning Seed of the peer
/// (If this is not in the message the default is 0) /// (If this is not in the message the default is 0)
#[serde(default = "default_zero")]
pub pruning_seed: u32, pub pruning_seed: u32,
/// Hash of the top block /// Hash of the top block
pub top_id: [u8; 32], pub top_id: ByteArray<32>,
/// Version of the top block /// Version of the top block
#[serde(default = "default_zero")]
pub top_version: u8, 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 { impl CoreSyncData {
pub fn new( pub fn new(
cumulative_difficulty_128: u128, cumulative_difficulty_128: u128,
@ -113,7 +131,7 @@ impl CoreSyncData {
cumulative_difficulty_top64, cumulative_difficulty_top64,
current_height, current_height,
pruning_seed, pruning_seed,
top_id, top_id: top_id.into(),
top_version, top_version,
} }
} }
@ -127,26 +145,32 @@ impl CoreSyncData {
/// PeerListEntryBase, information kept on a peer which will be entered /// PeerListEntryBase, information kept on a peer which will be entered
/// in a peer list/store. /// in a peer list/store.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PeerListEntryBase { pub struct PeerListEntryBase {
/// The Peer Address /// The Peer Address
pub adr: NetworkAddress, pub adr: NetworkAddress,
/// The Peer ID /// The Peer ID
pub id: u64, pub id: u64,
/// The last Time The Peer Was Seen /// The last Time The Peer Was Seen
#[serde(default = "default_zero")]
pub last_seen: i64, pub last_seen: i64,
/// The Pruning Seed /// The Pruning Seed
#[serde(default = "default_zero")]
pub pruning_seed: u32, pub pruning_seed: u32,
/// The RPC port /// The RPC port
#[serde(default = "default_zero")]
pub rpc_port: u16, pub rpc_port: u16,
/// The RPC credits per hash /// The RPC credits per hash
#[serde(default = "default_zero")]
pub rpc_credits_per_hash: u32, 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 { impl std::hash::Hash for PeerListEntryBase {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// We only hash the adr so we can look this up in a HashSet. // 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 /// 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 { pub struct PrunedTxBlobEntry {
/// The Tx /// The Tx
#[serde(with = "serde_bytes")] pub tx: Bytes,
pub tx: Vec<u8>,
/// The Prunable Tx Hash /// The Prunable Tx Hash
pub prunable_hash: [u8; 32], pub prunable_hash: ByteArray<32>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] epee_object!(
#[serde(untagged)] PrunedTxBlobEntry,
tx: Bytes,
prunable_hash: ByteArray<32>,
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TransactionBlobs { pub enum TransactionBlobs {
Pruned(Vec<PrunedTxBlobEntry>), Pruned(Vec<PrunedTxBlobEntry>),
#[serde(with = "serde_vec_bytes")] Normal(Vec<Bytes>),
Normal(Vec<Vec<u8>>),
#[serde(skip_serializing)]
None, None,
} }
@ -183,7 +209,7 @@ impl TransactionBlobs {
} }
} }
pub fn take_normal(self) -> Option<Vec<Vec<u8>>> { pub fn take_normal(self) -> Option<Vec<Bytes>> {
match self { match self {
TransactionBlobs::Normal(txs) => Some(txs), TransactionBlobs::Normal(txs) => Some(txs),
TransactionBlobs::Pruned(_) => None, TransactionBlobs::Pruned(_) => None,
@ -202,30 +228,57 @@ impl TransactionBlobs {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
} }
fn none() -> TransactionBlobs {
TransactionBlobs::None
}
} }
/// A Block that can contain transactions /// A Block that can contain transactions
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockCompleteEntry { pub struct BlockCompleteEntry {
/// True if tx data is pruned /// True if tx data is pruned
#[serde(default = "default_false")]
pub pruned: bool, pub pruned: bool,
/// The Block /// The Block
#[serde(with = "serde_bytes")] pub block: Bytes,
pub block: Vec<u8>,
/// The Block Weight/Size /// The Block Weight/Size
#[serde(default = "default_zero")]
pub block_weight: u64, pub block_weight: u64,
/// The blocks txs /// The blocks txs
#[serde(skip_serializing_if = "TransactionBlobs::is_empty")]
#[serde(default = "TransactionBlobs::none")]
pub txs: TransactionBlobs, 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: Buf>(b: &mut B) -> epee_encoding::Result<TransactionBlobs> {
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<B: BufMut>(
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)] #[cfg(test)]
mod tests { mod tests {

View file

@ -18,16 +18,15 @@
//! Protocol message requests don't have to be responded to in order unlike //! Protocol message requests don't have to be responded to in order unlike
//! admin messages. //! admin messages.
use serde::{Deserialize, Serialize}; use bytes::Bytes;
use serde_bytes::ByteBuf;
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 super::common::BlockCompleteEntry;
use crate::serde_helpers::*;
/// A block that SHOULD have transactions /// A block that SHOULD have transactions
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewBlock { pub struct NewBlock {
/// Block with txs /// Block with txs
pub b: BlockCompleteEntry, pub b: BlockCompleteEntry,
@ -35,63 +34,80 @@ pub struct NewBlock {
pub current_blockchain_height: u64, pub current_blockchain_height: u64,
} }
epee_object!(
NewBlock,
b: BlockCompleteEntry,
current_blockchain_height: u64,
);
/// New Tx Pool Transactions /// New Tx Pool Transactions
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewTransactions { pub struct NewTransactions {
/// Tx Blobs /// Tx Blobs
pub txs: Vec<ByteBuf>, pub txs: Vec<Bytes>,
/// Dandelionpp true if fluff - backwards compatible mode is fluff /// Dandelionpp true if fluff - backwards compatible mode is fluff
#[serde(default = "default_true")]
pub dandelionpp_fluff: bool, pub dandelionpp_fluff: bool,
/// Padding /// Padding
#[serde(rename = "_")] pub padding: Bytes,
#[serde(with = "serde_bytes")]
pub padding: Vec<u8>,
} }
epee_object!(
NewTransactions,
txs: Vec<Bytes>,
dandelionpp_fluff: bool = true,
padding("_"): Bytes,
);
/// A Request For Blocks /// A Request For Blocks
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetObjectsRequest { pub struct GetObjectsRequest {
/// Block hashes we want /// Block hashes we want
#[serde(default = "Vec::new")] pub blocks: ByteArrayVec<32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub blocks: Vec<[u8; 32]>,
/// Pruned /// Pruned
#[serde(default = "default_false")]
pub pruned: bool, pub pruned: bool,
} }
epee_object!(
GetObjectsRequest,
blocks: ByteArrayVec<32>,
pruned: bool = false,
);
/// A Blocks Response /// A Blocks Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetObjectsResponse { pub struct GetObjectsResponse {
/// Blocks /// Blocks
// We dont need to give this a default value as there always is at least 1 block
pub blocks: Vec<BlockCompleteEntry>, pub blocks: Vec<BlockCompleteEntry>,
/// Missed IDs /// Missed IDs
#[serde(default = "Vec::new")] pub missed_ids: ByteArrayVec<32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub missed_ids: Vec<[u8; 32]>,
/// The height of the peers blockchain /// The height of the peers blockchain
pub current_blockchain_height: u64, pub current_blockchain_height: u64,
} }
epee_object!(
GetObjectsResponse,
blocks: Vec<BlockCompleteEntry>,
missed_ids: ByteArrayVec<32>,
current_blockchain_height: u64,
);
/// A Chain Request /// A Chain Request
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainRequest { pub struct ChainRequest {
/// Block IDs /// Block IDs
#[serde(default = "Vec::new")] pub block_ids: ByteArrayVec<32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub block_ids: Vec<[u8; 32]>,
/// Prune /// Prune
#[serde(default = "default_false")]
pub prune: bool, pub prune: bool,
} }
epee_object!(
ChainRequest,
block_ids: ByteArrayVec<32>,
prune: bool = false,
);
/// A Chain Response /// A Chain Response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainResponse { pub struct ChainResponse {
/// Start Height /// Start Height
pub start_height: u64, pub start_height: u64,
@ -100,26 +116,28 @@ pub struct ChainResponse {
/// Cumulative Difficulty Low /// Cumulative Difficulty Low
pub cumulative_difficulty: u64, pub cumulative_difficulty: u64,
/// Cumulative Difficulty High /// Cumulative Difficulty High
#[serde(default = "default_zero")]
pub cumulative_difficulty_top64: u64, pub cumulative_difficulty_top64: u64,
/// Block IDs /// Block IDs
#[serde(default = "Vec::new")] pub m_block_ids: ByteArrayVec<32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub m_block_ids: Vec<[u8; 32]>,
/// Block Weights /// Block Weights
#[serde(default = "Vec::new")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub m_block_weights: Vec<u64>, pub m_block_weights: Vec<u64>,
/// The first Block in the response /// The first Block in the response
#[serde(default = "Vec::new")] pub first_block: Bytes,
#[serde(with = "serde_bytes")]
pub first_block: Vec<u8>,
} }
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<u64> as ContainerAsBlob<u64>,
first_block: Bytes,
);
/// A Block that doesn't have transactions unless requested /// A Block that doesn't have transactions unless requested
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewFluffyBlock { pub struct NewFluffyBlock {
/// Block which might have transactions /// Block which might have transactions
pub b: BlockCompleteEntry, pub b: BlockCompleteEntry,
@ -127,30 +145,42 @@ pub struct NewFluffyBlock {
pub current_blockchain_height: u64, pub current_blockchain_height: u64,
} }
epee_object!(
NewFluffyBlock,
b: BlockCompleteEntry,
current_blockchain_height: u64,
);
/// A request for Txs we are missing from our TxPool /// 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 { pub struct FluffyMissingTransactionsRequest {
/// The Block we are missing the Txs in /// The Block we are missing the Txs in
pub block_hash: [u8; 32], pub block_hash: ByteArray<32>,
/// The current blockchain height /// The current blockchain height
pub current_blockchain_height: u64, pub current_blockchain_height: u64,
/// The Tx Indices /// The Tx Indices
#[serde(default = "Vec::new")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub missing_tx_indices: Vec<u64>, pub missing_tx_indices: Vec<u64>,
} }
epee_object!(
FluffyMissingTransactionsRequest,
block_hash: ByteArray<32>,
current_blockchain_height: u64,
missing_tx_indices: Vec<u64> as ContainerAsBlob<u64>,
);
/// TxPoolCompliment /// TxPoolCompliment
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetTxPoolCompliment { pub struct GetTxPoolCompliment {
/// Tx Hashes /// Tx Hashes
#[serde(default = "Vec::new")] pub hashes: ByteArrayVec<32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "container_as_blob")]
pub hashes: Vec<[u8; 32]>,
} }
epee_object!(
GetTxPoolCompliment,
hashes: ByteArrayVec<32>,
);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -667,13 +697,13 @@ mod tests {
248, 248, 91, 110, 107, 144, 12, 175, 253, 21, 121, 28, 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()); 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 = 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); 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, 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, 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 mut encoded_bytes = epee_encoding::to_bytes(fluffy_block.clone()).unwrap();
let fluffy_block_2: NewFluffyBlock = let fluffy_block_2: NewFluffyBlock = epee_encoding::from_bytes(&mut encoded_bytes).unwrap();
monero_epee_bin_serde::from_bytes(encoded_bytes).unwrap();
assert_eq!(fluffy_block, fluffy_block_2); assert_eq!(fluffy_block, fluffy_block_2);
} }

View file

@ -1,35 +0,0 @@
pub(crate) fn default_false() -> bool {
false
}
pub(crate) fn default_true() -> bool {
true
}
pub(crate) fn default_zero<T: TryFrom<u8>>() -> 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<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Vec::<ByteBuf>::deserialize(d)?
.into_iter()
.map(ByteBuf::into_vec)
.collect())
}
pub fn serialize<S>(t: &[Vec<u8>], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.collect_seq(t.iter())
}
}

View file

@ -32,7 +32,7 @@ async fn handshake_cuprate_to_cuprate() {
let our_basic_node_data_1 = BasicNodeData { let our_basic_node_data_1 = BasicNodeData {
my_port: 0, my_port: 0,
network_id: Network::Mainnet.network_id(), network_id: Network::Mainnet.network_id().into(),
peer_id: 87980, peer_id: 87980,
// TODO: This fails if the support flags are empty (0) // TODO: This fails if the support flags are empty (0)
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
@ -113,7 +113,7 @@ async fn handshake() {
let our_basic_node_data = BasicNodeData { let our_basic_node_data = BasicNodeData {
my_port: 0, my_port: 0,
network_id: Network::Mainnet.network_id(), network_id: Network::Mainnet.network_id().into(),
peer_id: 87980, peer_id: 87980,
support_flags: PeerSupportFlags::from(1_u32), support_flags: PeerSupportFlags::from(1_u32),
rpc_port: 0, rpc_port: 0,