helper: add and use cast module (#264)

* helper: add `cast` module

* fix crates

* spacing
This commit is contained in:
hinto-janai 2024-09-02 13:09:52 -04:00 committed by GitHub
parent fdd1689665
commit bec8cc0aa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 163 additions and 58 deletions

3
Cargo.lock generated
View file

@ -646,6 +646,7 @@ version = "0.5.0"
dependencies = [
"bytes",
"cuprate-fixed-bytes",
"cuprate-helper",
"hex",
"paste",
"ref-cast",
@ -713,6 +714,7 @@ version = "0.1.0"
dependencies = [
"bitflags 2.5.0",
"bytes",
"cuprate-helper",
"futures",
"proptest",
"rand",
@ -893,6 +895,7 @@ dependencies = [
"bytes",
"cuprate-epee-encoding",
"cuprate-fixed-bytes",
"cuprate-helper",
"cuprate-levin",
"cuprate-types",
"hex",

View file

@ -11,7 +11,7 @@ proptest = ["dep:proptest", "dep:proptest-derive", "cuprate-types/proptest"]
rayon = ["dep:rayon"]
[dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["std"] }
cuprate-helper = { path = "../../helper", default-features = false, features = ["std", "cast"] }
cuprate-types = { path = "../../types", default-features = false }
cuprate-cryptonight = {path = "../../cryptonight"}

View file

@ -9,6 +9,8 @@ use proptest::{collection::vec, prelude::*};
use monero_serai::transaction::Output;
use cuprate_helper::cast::u64_to_usize;
use super::*;
use crate::decomposed_amount::DECOMPOSED_AMOUNTS;
@ -164,7 +166,7 @@ prop_compose! {
if timebased || lock_height > 500_000_000 {
Timelock::Time(time_for_time_lock)
} else {
Timelock::Block(usize::try_from(lock_height).unwrap())
Timelock::Block(u64_to_usize(lock_height))
}
}
}
@ -179,7 +181,7 @@ prop_compose! {
match ty {
0 => Timelock::None,
1 => Timelock::Time(time_for_time_lock),
_ => Timelock::Block(usize::try_from(lock_height).unwrap())
_ => Timelock::Block(u64_to_usize(lock_height))
}
}
}

View file

@ -14,7 +14,7 @@ use cuprate_consensus_rules::{
miner_tx::MinerTxError,
ConsensusError,
};
use cuprate_helper::asynch::rayon_spawn_async;
use cuprate_helper::{asynch::rayon_spawn_async, cast::u64_to_usize};
use cuprate_types::{
AltBlockInformation, Chain, ChainId, TransactionVerificationData,
VerifiedTransactionInformation,
@ -101,7 +101,7 @@ where
// Check the alt block timestamp is in the correct range.
if let Some(median_timestamp) =
difficulty_cache.median_timestamp(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW.try_into().unwrap())
difficulty_cache.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW))
{
check_timestamp(&prepped_block.block, median_timestamp).map_err(ConsensusError::Block)?
};

View file

@ -9,6 +9,7 @@ use tower::ServiceExt;
use tracing::Instrument;
use cuprate_consensus_rules::blocks::ContextToVerifyBlock;
use cuprate_helper::cast::u64_to_usize;
use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse},
Chain,
@ -168,9 +169,9 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
.weight_cache
.effective_median_block_weight(&current_hf),
top_hash: self.top_block_hash,
median_block_timestamp: self.difficulty_cache.median_timestamp(
usize::try_from(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW).unwrap(),
),
median_block_timestamp: self
.difficulty_cache
.median_timestamp(u64_to_usize(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)),
chain_height: self.chain_height,
current_hf,
next_difficulty: self.difficulty_cache.next_difficulty(&current_hf),

View file

@ -10,14 +10,15 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/consensus"
[features]
# All features on by default.
default = ["std", "atomic", "asynch", "fs", "num", "map", "time", "thread", "constants"]
default = ["std", "atomic", "asynch", "cast", "fs", "num", "map", "time", "thread", "constants"]
std = []
atomic = ["dep:crossbeam"]
asynch = ["dep:futures", "dep:rayon"]
cast = []
constants = []
fs = ["dep:dirs"]
num = []
map = ["dep:monero-serai"]
map = ["cast", "dep:monero-serai"]
time = ["dep:chrono", "std"]
thread = ["std", "dep:target_os_lib"]

84
helper/src/cast.rs Normal file
View file

@ -0,0 +1,84 @@
//! Casting.
//!
//! This modules provides utilities for casting between types.
//!
//! `#[no_std]` compatible.
#[rustfmt::skip]
//============================ SAFETY: DO NOT REMOVE ===========================//
// //
// //
// Only allow building 64-bit targets. //
// This allows us to assume 64-bit invariants in this file. //
#[cfg(not(target_pointer_width = "64"))]
compile_error!("Cuprate is only compatible with 64-bit CPUs");
// //
// //
//============================ SAFETY: DO NOT REMOVE ===========================//
//---------------------------------------------------------------------------------------------------- Free functions
/// Cast [`u64`] to [`usize`].
#[inline(always)]
pub const fn u64_to_usize(u: u64) -> usize {
u as usize
}
/// Cast [`u32`] to [`usize`].
#[inline(always)]
pub const fn u32_to_usize(u: u32) -> usize {
u as usize
}
/// Cast [`usize`] to [`u64`].
#[inline(always)]
pub const fn usize_to_u64(u: usize) -> u64 {
u as u64
}
/// Cast [`i64`] to [`isize`].
#[inline(always)]
pub const fn i64_to_isize(i: i64) -> isize {
i as isize
}
/// Cast [`i32`] to [`isize`].
#[inline(always)]
pub const fn i32_to_isize(i: i32) -> isize {
i as isize
}
/// Cast [`isize`] to [`i64`].
#[inline(always)]
pub const fn isize_to_i64(i: isize) -> i64 {
i as i64
}
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {
use super::*;
#[test]
fn max_unsigned() {
assert_eq!(u32_to_usize(u32::MAX), u32::MAX as usize);
assert_eq!(usize_to_u64(u32_to_usize(u32::MAX)), u32::MAX as u64);
assert_eq!(u64_to_usize(u64::MAX), usize::MAX);
assert_eq!(usize_to_u64(u64_to_usize(u64::MAX)), u64::MAX);
assert_eq!(usize_to_u64(usize::MAX), u64::MAX);
assert_eq!(u64_to_usize(usize_to_u64(usize::MAX)), usize::MAX);
}
#[test]
fn max_signed() {
assert_eq!(i32_to_isize(i32::MAX), i32::MAX as isize);
assert_eq!(isize_to_i64(i32_to_isize(i32::MAX)), i32::MAX as i64);
assert_eq!(i64_to_isize(i64::MAX), isize::MAX);
assert_eq!(isize_to_i64(i64_to_isize(i64::MAX)), i64::MAX);
assert_eq!(isize_to_i64(isize::MAX), i64::MAX);
assert_eq!(i64_to_isize(isize_to_i64(isize::MAX)), isize::MAX);
}
}

View file

@ -40,6 +40,9 @@ pub mod asynch; // async collides
#[cfg(feature = "atomic")]
pub mod atomic;
#[cfg(feature = "cast")]
pub mod cast;
#[cfg(feature = "constants")]
pub mod constants;

View file

@ -7,6 +7,8 @@
//---------------------------------------------------------------------------------------------------- Use
use monero_serai::transaction::Timelock;
use crate::cast::{u64_to_usize, usize_to_u64};
//---------------------------------------------------------------------------------------------------- `(u64, u64) <-> u128`
/// Split a [`u128`] value into 2 64-bit values.
///
@ -77,7 +79,7 @@ pub fn u64_to_timelock(u: u64) -> Timelock {
if u == 0 {
Timelock::None
} else if u < 500_000_000 {
Timelock::Block(usize::try_from(u).unwrap())
Timelock::Block(u64_to_usize(u))
} else {
Timelock::Time(u)
}
@ -97,7 +99,7 @@ pub fn u64_to_timelock(u: u64) -> Timelock {
pub fn timelock_to_u64(timelock: Timelock) -> u64 {
match timelock {
Timelock::None => 0,
Timelock::Block(u) => u64::try_from(u).unwrap(),
Timelock::Block(u) => usize_to_u64(u),
Timelock::Time(u) => u,
}
}

View file

@ -15,6 +15,7 @@ default = ["std"]
std = ["dep:thiserror", "bytes/std", "cuprate-fixed-bytes/std"]
[dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
cuprate-fixed-bytes = { path = "../fixed-bytes", default-features = false }
paste = "1.0.14"

View file

@ -65,6 +65,8 @@ use core::{ops::Deref, str::from_utf8 as str_from_utf8};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
pub mod container_as_blob;
pub mod error;
mod io;
@ -242,7 +244,7 @@ pub fn write_bytes<T: AsRef<[u8]>, B: BufMut>(t: T, w: &mut B) -> Result<()> {
let bytes = t.as_ref();
let len = bytes.len();
write_varint(len.try_into()?, w)?;
write_varint(usize_to_u64(len), w)?;
if w.remaining_mut() < len {
return Err(Error::IO("Not enough capacity to write bytes"));
@ -286,7 +288,7 @@ where
I: Iterator<Item = T> + ExactSizeIterator,
B: BufMut,
{
write_varint(iterator.len().try_into()?, w)?;
write_varint(usize_to_u64(iterator.len()), w)?;
for item in iterator.into_iter() {
item.write(w)?;
}
@ -334,7 +336,7 @@ fn skip_epee_value<B: Buf>(r: &mut B, skipped_objects: &mut u8) -> Result<()> {
if let Some(size) = marker.inner_marker.size() {
let bytes_to_skip = size
.checked_mul(len.try_into()?)
.checked_mul(u64_to_usize(len))
.ok_or(Error::Value("List is too big".to_string()))?;
return advance(bytes_to_skip, r);
};
@ -352,8 +354,8 @@ fn skip_epee_value<B: Buf>(r: &mut B, skipped_objects: &mut u8) -> Result<()> {
| InnerMarker::U8
| InnerMarker::Bool => unreachable!("These types are constant size."),
InnerMarker::String => {
let len = read_varint(r)?;
advance(len.try_into()?, r)?;
let len = u64_to_usize(read_varint(r)?);
advance(len, r)?;
}
InnerMarker::Object => {
*skipped_objects += 1;

View file

@ -7,6 +7,7 @@ use core::fmt::Debug;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use cuprate_fixed_bytes::{ByteArray, ByteArrayVec};
use cuprate_helper::cast::u64_to_usize;
use crate::{
io::{checked_read_primitive, checked_write_primitive},
@ -66,11 +67,11 @@ impl<T: EpeeObject> EpeeValue for Vec<T> {
"Marker is not sequence when a sequence was expected",
));
}
let len = read_varint(r)?;
let len = u64_to_usize(read_varint(r)?);
let individual_marker = Marker::new(marker.inner_marker);
let mut res = Vec::with_capacity(len.try_into()?);
let mut res = Vec::with_capacity(len);
for _ in 0..len {
res.push(T::read(r, &individual_marker)?);
}
@ -167,11 +168,13 @@ impl EpeeValue for Vec<u8> {
return Err(Error::Format("Byte array exceeded max length"));
}
if r.remaining() < len.try_into()? {
let len = u64_to_usize(len);
if r.remaining() < len {
return Err(Error::IO("Not enough bytes to fill object"));
}
let mut res = vec![0; len.try_into()?];
let mut res = vec![0; len];
r.copy_to_slice(&mut res);
Ok(res)
@ -203,11 +206,13 @@ impl EpeeValue for Bytes {
return Err(Error::Format("Byte array exceeded max length"));
}
if r.remaining() < len.try_into()? {
let len = u64_to_usize(len);
if r.remaining() < len {
return Err(Error::IO("Not enough bytes to fill object"));
}
Ok(r.copy_to_bytes(len.try_into()?))
Ok(r.copy_to_bytes(len))
}
fn epee_default_value() -> Option<Self> {
@ -236,11 +241,13 @@ impl EpeeValue for BytesMut {
return Err(Error::Format("Byte array exceeded max length"));
}
if r.remaining() < len.try_into()? {
let len = u64_to_usize(len);
if r.remaining() < len {
return Err(Error::IO("Not enough bytes to fill object"));
}
let mut bytes = BytesMut::zeroed(len.try_into()?);
let mut bytes = BytesMut::zeroed(len);
r.copy_to_slice(&mut bytes);
Ok(bytes)
@ -272,11 +279,13 @@ impl<const N: usize> EpeeValue for ByteArrayVec<N> {
return Err(Error::Format("Byte array exceeded max length"));
}
if r.remaining() < usize::try_from(len)? {
let len = u64_to_usize(len);
if r.remaining() < len {
return Err(Error::IO("Not enough bytes to fill object"));
}
ByteArrayVec::try_from(r.copy_to_bytes(usize::try_from(len)?))
ByteArrayVec::try_from(r.copy_to_bytes(len))
.map_err(|_| Error::Format("Field has invalid length"))
}
@ -302,7 +311,7 @@ impl<const N: usize> EpeeValue for ByteArray<N> {
return Err(Error::Format("Marker does not match expected Marker"));
}
let len: usize = read_varint(r)?.try_into()?;
let len = u64_to_usize(read_varint(r)?);
if len != N {
return Err(Error::Format("Byte array has incorrect length"));
}
@ -370,11 +379,11 @@ impl<const N: usize> EpeeValue for Vec<[u8; N]> {
));
}
let len = read_varint(r)?;
let len = u64_to_usize(read_varint(r)?);
let individual_marker = Marker::new(marker.inner_marker);
let mut res = Vec::with_capacity(len.try_into()?);
let mut res = Vec::with_capacity(len);
for _ in 0..len {
res.push(<[u8; N]>::read(r, &individual_marker)?);
}
@ -406,11 +415,11 @@ macro_rules! epee_seq {
));
}
let len = read_varint(r)?;
let len = u64_to_usize(read_varint(r)?);
let individual_marker = Marker::new(marker.inner_marker.clone());
let mut res = Vec::with_capacity(len.try_into()?);
let mut res = Vec::with_capacity(len);
for _ in 0..len {
res.push(<$val>::read(r, &individual_marker)?);
}

View file

@ -12,6 +12,8 @@ default = []
tracing = ["dep:tracing", "tokio-util/tracing"]
[dependencies]
cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
thiserror = { workspace = true }
bytes = { workspace = true, features = ["std"] }
bitflags = { workspace = true }

View file

@ -20,6 +20,8 @@ use std::{fmt::Debug, marker::PhantomData};
use bytes::{Buf, BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
use cuprate_helper::cast::u64_to_usize;
use crate::{
header::{Flags, HEADER_SIZE},
message::{make_dummy_message, LevinMessage},
@ -114,10 +116,7 @@ impl<C: LevinCommand + Debug> Decoder for LevinBucketCodec<C> {
std::mem::replace(&mut self.state, LevinBucketState::WaitingForBody(head));
}
LevinBucketState::WaitingForBody(head) => {
let body_len = head
.size
.try_into()
.map_err(|_| BucketError::BucketExceededMaxSize)?;
let body_len = u64_to_usize(head.size);
if src.len() < body_len {
src.reserve(body_len - src.len());
return Ok(None);
@ -255,13 +254,11 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
continue;
};
let max_size = if self.bucket_codec.handshake_message_seen {
let max_size = u64_to_usize(if self.bucket_codec.handshake_message_seen {
self.bucket_codec.protocol.max_packet_size
} else {
self.bucket_codec.protocol.max_packet_size_before_handshake
}
.try_into()
.expect("Levin max message size is too large, does not fit into a usize.");
});
if bytes.len().saturating_add(bucket.body.len()) > max_size {
return Err(BucketError::InvalidFragmentedMessage(
@ -300,12 +297,7 @@ impl<T: LevinBody> Decoder for LevinMessageCodec<T> {
}
// Check the fragmented message contains enough bytes to build the message.
if bytes.len().saturating_sub(HEADER_SIZE)
< header
.size
.try_into()
.map_err(|_| BucketError::BucketExceededMaxSize)?
{
if bytes.len().saturating_sub(HEADER_SIZE) < u64_to_usize(header.size) {
return Err(BucketError::InvalidFragmentedMessage(
"Fragmented message does not have enough bytes to fill bucket body",
));

View file

@ -38,6 +38,8 @@ use std::fmt::Debug;
use bytes::{Buf, Bytes};
use thiserror::Error;
use cuprate_helper::cast::usize_to_u64;
pub mod codec;
pub mod header;
pub mod message;
@ -212,7 +214,7 @@ impl<C: LevinCommand> BucketBuilder<C> {
Bucket {
header: BucketHead {
signature: self.signature.unwrap(),
size: body.len().try_into().unwrap(),
size: usize_to_u64(body.len()),
have_to_return_data: ty.have_to_return_data(),
command: self.command.unwrap(),
return_code: self.return_code.unwrap(),

View file

@ -5,6 +5,8 @@
//! for more control over what is actually sent over the wire at certain times.
use bytes::{Bytes, BytesMut};
use cuprate_helper::cast::usize_to_u64;
use crate::{
header::{Flags, HEADER_SIZE},
Bucket, BucketBuilder, BucketError, BucketHead, LevinBody, LevinCommand, Protocol,
@ -106,9 +108,7 @@ pub fn make_fragmented_messages<T: LevinBody>(
new_body.resize(fragment_size - HEADER_SIZE, 0);
bucket.body = new_body.freeze();
bucket.header.size = (fragment_size - HEADER_SIZE)
.try_into()
.expect("Bucket size does not fit into u64");
bucket.header.size = usize_to_u64(fragment_size - HEADER_SIZE);
}
return Ok(vec![bucket]);
@ -118,9 +118,7 @@ pub fn make_fragmented_messages<T: LevinBody>(
// The first fragment will set the START flag, the last will set the END flag.
let fragment_head = BucketHead {
signature: protocol.signature,
size: (fragment_size - HEADER_SIZE)
.try_into()
.expect("Bucket size does not fit into u64"),
size: usize_to_u64(fragment_size - HEADER_SIZE),
have_to_return_data: false,
// Just use a default command.
command: T::Command::from(0),
@ -191,7 +189,7 @@ pub(crate) fn make_dummy_message<T: LevinCommand>(protocol: &Protocol, size: usi
// A header to put on the dummy message.
let header = BucketHead {
signature: protocol.signature,
size: size.try_into().expect("Bucket size does not fit into u64"),
size: usize_to_u64(size),
have_to_return_data: false,
// Just use a default command.
command: T::from(0),

View file

@ -8,6 +8,8 @@ use tokio::{
};
use tokio_util::codec::{FramedRead, FramedWrite};
use cuprate_helper::cast::u64_to_usize;
use cuprate_levin::{
message::make_fragmented_messages, BucketBuilder, BucketError, LevinBody, LevinCommand,
LevinMessageCodec, MessageType, Protocol,
@ -54,7 +56,7 @@ impl LevinBody for TestBody {
_: MessageType,
_: Self::Command,
) -> Result<Self, BucketError> {
let size = body.get_u64_le().try_into().unwrap();
let size = u64_to_usize(body.get_u64_le());
// bucket
Ok(TestBody::Bytes(size, body.copy_to_bytes(size)))
}

View file

@ -15,6 +15,7 @@ cuprate-levin = { path = "../levin" }
cuprate-epee-encoding = { path = "../epee-encoding" }
cuprate-fixed-bytes = { path = "../fixed-bytes" }
cuprate-types = { path = "../../types", default-features = false, features = ["epee"] }
cuprate-helper = { path = "../../helper", default-features = false, features = ["cast"] }
bitflags = { workspace = true, features = ["std"] }
bytes = { workspace = true, features = ["std"] }

View file

@ -99,7 +99,7 @@ impl LevinCommandTrait for LevinCommand {
LevinCommand::FluffyMissingTxsRequest => 1024 * 1024, // 1 MB
LevinCommand::GetTxPoolCompliment => 1024 * 1024 * 4, // 4 MB
LevinCommand::Unknown(_) => usize::MAX.try_into().unwrap_or(u64::MAX),
LevinCommand::Unknown(_) => u64::MAX,
}
}

View file

@ -39,7 +39,7 @@ thread_local = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
[dev-dependencies]
cuprate-helper = { path = "../../helper", features = ["thread"] }
cuprate-helper = { path = "../../helper", features = ["thread", "cast"] }
cuprate-test-utils = { path = "../../test-utils" }
tokio = { workspace = true, features = ["full"] }

View file

@ -442,7 +442,7 @@ mod test {
let mut block = BLOCK_V9_TX3.clone();
block.height = usize::try_from(u32::MAX).unwrap() + 1;
block.height = cuprate_helper::cast::u32_to_usize(u32::MAX) + 1;
add_block(&block, &mut tables).unwrap();
}