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",
"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"

View file

@ -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}

View file

@ -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<Vec<(Block, Vec<Transaction>)>, tower::BoxError> {
tracing::info!("Getting blocks in range: {:?}", range);
#[derive(Serialize)]
pub struct Request {
pub heights: Vec<u64>,
}
mod items {
use monero_wire::common::BlockCompleteEntry;
#[derive(Deserialize)]
pub struct Response {
pub blocks: Vec<BlockCompleteEntry>,
pub struct Request {
pub heights: Vec<u64>,
}
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
.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::<Result<_, _>>()?,
TransactionBlobs::None => vec![],
};
@ -237,29 +253,56 @@ impl RpcConnection {
out_ids.values().map(|amt_map| amt_map.len()).sum::<usize>()
);
#[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<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)]
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>,
}
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());

View file

@ -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}

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 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<T: EpeeObject>(mut buf: &[u8]) -> Result<T> {
read_head_object(&mut buf)
pub fn from_bytes<T: EpeeObject, B: Buf>(buf: &mut B) -> Result<T> {
read_head_object(buf)
}
/// Turn the object into epee bytes.
pub fn to_bytes<T: EpeeObject>(val: T) -> Result<Vec<u8>> {
let mut buf = Vec::<u8>::new();
pub fn to_bytes<T: EpeeObject>(val: T) -> Result<BytesMut> {
let mut buf = BytesMut::new();
write_head_object(val, &mut buf)?;
Ok(buf)
}

View file

@ -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<B: epee_encoding::macros::bytes::Buf>(&mut self, name: &str, b: &mut B) -> epee_encoding::error::Result<bool> {
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<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) ) {
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)?;
}
)+

View file

@ -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<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]
impl<const N: usize> EpeeValue for ByteArrayVec<N> {
const MARKER: Marker = Marker::new(InnerMarker::String);
@ -481,6 +517,8 @@ epee_seq!(f64);
epee_seq!(bool);
epee_seq!(Vec<u8>);
epee_seq!(String);
epee_seq!(Bytes);
epee_seq!(BytesMut);
#[sealed]
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 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();

View file

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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -28,9 +28,9 @@ fn seq_with_zero_len_can_have_any_marker() {
data.push(0x80 | marker);
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..);
}
@ -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::<ValSeq>(&data).is_err());
assert!(from_bytes::<ValSeq, _>(&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::<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,
];
let obj: Result<Q, _> = from_bytes(&bytes);
let obj: Result<Q, _> = from_bytes(&mut bytes.as_slice());
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`].
/// This implements [`Deref`] with the target being `[u8; N]`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ByteArray<const N: usize>(Bytes);
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> {
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);
impl<const N: usize> ByteArrayVec<N> {

View file

@ -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<C: LevinCommand> Decoder for LevinBucketCodec<C> {
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<C: LevinCommand> Encoder<Bucket<C>> for LevinBucketCodec<C> {
enum MessageState<C> {
#[default]
WaitingForBucket,
WaitingForRestOfFragment(Vec<u8>, MessageType, C),
WaitingForRestOfFragment(Vec<Bytes>, MessageType, C),
}
/// 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 {
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<T: LevinBody> Decoder for LevinMessageCodec<T> {
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<T: LevinBody> Decoder for LevinMessageCodec<T> {
}
return Ok(Some(T::decode_message(
&bucket.body,
&mut bucket.body,
message_type,
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() {
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<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 bytes::{Buf, Bytes};
use thiserror::Error;
const MONERO_PROTOCOL_VERSION: u32 = 1;
@ -102,7 +103,7 @@ pub struct Bucket<C> {
/// The bucket header
pub header: BucketHead<C>,
/// The bucket body
pub body: Vec<u8>,
pub body: Bytes,
}
/// An enum representing if the message is a request, response or notification.
@ -158,7 +159,7 @@ pub struct BucketBuilder<C> {
command: Option<C>,
return_code: Option<i32>,
protocol_version: Option<u32>,
body: Option<Vec<u8>>,
body: Option<Bytes>,
}
impl<C> Default for BucketBuilder<C> {
@ -195,7 +196,7 @@ impl<C: LevinCommand> BucketBuilder<C> {
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)
}
@ -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<B: Buf>(
body: &mut B,
typ: MessageType,
command: Self::Command,
) -> Result<Self, BucketError>;
/// 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.

View file

@ -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]

View file

@ -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};

View file

@ -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<B: BufMut>(self, w: &mut B) -> epee_encoding::Result<()> {
TaggedNetworkAddress::from(self).write(w)
}
}
impl NetworkAddress {
pub fn get_zone(&self) -> NetZone {
match self {

View file

@ -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<u8>,
addr: Option<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)]
#[error("Invalid network address")]
pub(crate) struct InvalidNetworkAddress;
pub struct InvalidNetworkAddress;
impl TryFrom<TaggedNetworkAddress> for NetworkAddress {
type Error = InvalidNetworkAddress;
@ -22,7 +55,8 @@ impl TryFrom<TaggedNetworkAddress> for NetworkAddress {
fn try_from(value: TaggedNetworkAddress) -> Result<Self, Self::Error> {
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<NetworkAddress> 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<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
m_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
addr: Option<[u8; 16]>,
}
epee_object!(
AllFieldsNetworkAddress,
m_ip: Option<u32>,
m_port: Option<u16>,
addr: Option<[u8; 16]>,
);
impl AllFieldsNetworkAddress {
fn try_into_network_address(self, ty: u8) -> Option<NetworkAddress> {
Some(match ty {

View file

@ -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<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,
buf: &[u8],
buf: &mut B,
) -> Result<Ret, BucketError> {
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<T: serde::Serialize>(
fn build_message<T: epee_encoding::EpeeObject>(
id: LevinCommand,
val: &T,
val: T,
builder: &mut BucketBuilder<LevinCommand>,
) -> 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<Self, BucketError> {
fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C;
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;
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<Self, BucketError> {
fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C;
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;
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<Self, BucketError> {
fn decode<B: Buf>(buf: &mut B, command: LevinCommand) -> Result<Self, BucketError> {
use LevinCommand as C;
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;
match self {
@ -375,8 +376,8 @@ impl Message {
impl LevinBody for Message {
type Command = LevinCommand;
fn decode_message(
body: &[u8],
fn decode_message<B: Buf>(
body: &mut B,
typ: MessageType,
command: LevinCommand,
) -> 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 {
Message::Protocol(pro) => {
builder.set_message_type(MessageType::Notification);

View file

@ -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<PeerListEntryBase>,
}
epee_object!(
HandshakeResponse,
node_data: BasicNodeData,
payload_data: CoreSyncData,
local_peerlist_new: Vec<PeerListEntryBase>,
);
/// 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<PeerListEntryBase>,
}
epee_object!(
TimedSyncResponse,
payload_data: CoreSyncData,
local_peerlist_new: Vec<PeerListEntryBase>,
);
/// 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);
}

View file

@ -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<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 {
//const FLUFFY_BLOCKS: u32 = 0b0000_0001;
@ -53,51 +57,65 @@ impl From<u8> 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<H: std::hash::Hasher>(&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<u8>,
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<PrunedTxBlobEntry>),
#[serde(with = "serde_vec_bytes")]
Normal(Vec<Vec<u8>>),
#[serde(skip_serializing)]
Normal(Vec<Bytes>),
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 {
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<u8>,
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: 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)]
mod tests {

View file

@ -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<ByteBuf>,
pub txs: Vec<Bytes>,
/// 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<u8>,
pub padding: Bytes,
}
epee_object!(
NewTransactions,
txs: Vec<Bytes>,
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<BlockCompleteEntry>,
/// 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<BlockCompleteEntry>,
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<u64>,
/// The first Block in the response
#[serde(default = "Vec::new")]
#[serde(with = "serde_bytes")]
pub first_block: Vec<u8>,
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<u64> as ContainerAsBlob<u64>,
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<u64>,
}
epee_object!(
FluffyMissingTransactionsRequest,
block_hash: ByteArray<32>,
current_blockchain_height: u64,
missing_tx_indices: Vec<u64> as ContainerAsBlob<u64>,
);
/// 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);
}

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 {
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,