Introduce ToU64 conversion trait

We already explicitly do not support 16 bit machines.

Also, because Rust supports `u182`s one cannot infallibly convert from a
`usize` to a `u64`. This is unergonomic and results in a ton of casts.

We can instead limit our code to running only on machines where `usize`
is less that or equal to 64 bits then the infallible conversion is
possible.

Since 128 bit machines are not a thing yet this does not in reality
introduce any limitations on the library.

Add a "private" trait to the `internals` crate to do infallible
conversion to a `u64` from `usize`.

Implement it for all unsigned integers smaller than `u64` as well so
we have the option to use the trait instead of `u32::from(foo)`.
This commit is contained in:
Tobin C. Harding 2024-06-27 14:43:51 +10:00
parent 5dd20ddfb0
commit 579b76b7cb
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
14 changed files with 71 additions and 34 deletions

View File

@ -9,7 +9,7 @@ use core::{convert, fmt, mem};
use std::error;
use hashes::{sha256, siphash24};
use internals::impl_array_newtype;
use internals::{impl_array_newtype, ToU64 as _};
use io::{BufRead, Write};
use crate::consensus::encode::{self, Decodable, Encodable, VarInt};
@ -287,7 +287,7 @@ impl Encodable for BlockTransactionsRequest {
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = self.block_hash.consensus_encode(w)?;
// Manually encode indexes because they are differentially encoded VarInts.
len += VarInt(self.indexes.len() as u64).consensus_encode(w)?;
len += VarInt(self.indexes.len().to_u64()).consensus_encode(w)?;
let mut last_idx = 0;
for idx in &self.indexes {
len += VarInt(*idx - last_idx).consensus_encode(w)?;
@ -383,7 +383,7 @@ impl BlockTransactions {
transactions: {
let mut txs = Vec::with_capacity(request.indexes.len());
for idx in &request.indexes {
if *idx >= block.txdata.len() as u64 {
if *idx >= block.txdata.len().to_u64() {
return Err(TxIndexOutOfRangeError(*idx));
}
txs.push(block.txdata[*idx as usize].clone());

View File

@ -41,7 +41,7 @@ use core::cmp::{self, Ordering};
use core::fmt;
use hashes::{sha256d, siphash24, HashEngine as _};
use internals::write_err;
use internals::{write_err, ToU64 as _};
use io::{BufRead, Write};
use crate::block::{Block, BlockHash};
@ -387,7 +387,7 @@ impl<'a, W: Write> GcsFilterWriter<'a, W> {
/// Writes the filter to the wrapped writer.
pub fn finish(&mut self) -> Result<usize, io::Error> {
let nm = self.elements.len() as u64 * self.m;
let nm = self.elements.len().to_u64() * self.m;
// map hashes to [0, n_elements * M)
let mut mapped: Vec<_> = self

View File

@ -480,6 +480,7 @@ impl std::error::Error for ValidationError {
#[cfg(test)]
mod tests {
use hex::{test_hex_unwrap as hex, FromHex};
use internals::ToU64 as _;
use super::*;
use crate::consensus::encode::{deserialize, serialize};
@ -540,7 +541,7 @@ mod tests {
assert_eq!(real_decode.base_size(), some_block.len());
assert_eq!(
real_decode.weight(),
Weight::from_non_witness_data_size(some_block.len() as u64)
Weight::from_non_witness_data_size(some_block.len().to_u64())
);
// should be also ok for a non-witness block as commitment is optional in that case

View File

@ -25,6 +25,8 @@ pub mod fee_rate {
#[cfg(test)]
mod tests {
use internals::ToU64 as _;
use super::*;
#[test]
@ -41,7 +43,7 @@ pub mod fee_rate {
let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/byte is valid");
assert_eq!(rate.fee_vb(tx.vsize() as u64), rate.fee_wu(tx.weight()));
assert_eq!(rate.fee_vb(tx.vsize().to_u64()), rate.fee_wu(tx.weight()));
}
}
}

View File

@ -5,6 +5,8 @@ use core::ops::{
Bound, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
};
use internals::ToU64 as _;
use super::witness_version::WitnessVersion;
use super::{
bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, PushBytes,
@ -399,11 +401,11 @@ impl Script {
} else if self.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
} else {
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field
self.consensus_encode(&mut sink()).expect("sinks don't error") as u64 // The serialized size of this script_pubkey
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
})
.expect("dust_relay_fee or script length should not be absurdly large")
/ 1000; // divide by 1000 like in Core to get value as it cancels out DEFAULT_MIN_RELAY_TX_FEE

View File

@ -4,6 +4,7 @@
use core::ops::Deref;
use hex::FromHex;
use internals::ToU64 as _;
use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script};
use crate::opcodes::all::*;
@ -100,7 +101,7 @@ impl ScriptBuf {
/// Pushes the slice without reserving
fn push_slice_no_opt(&mut self, data: &PushBytes) {
// Start with a PUSH opcode
match data.len() as u64 {
match data.len().to_u64() {
n if n < opcodes::Ordinary::OP_PUSHDATA1 as u64 => {
self.0.push(n as u8);
}

View File

@ -13,7 +13,7 @@
use core::{cmp, fmt, str};
use hashes::sha256d;
use internals::write_err;
use internals::{write_err, ToU64 as _};
use io::{BufRead, Write};
use primitives::Sequence;
use units::parse;
@ -277,7 +277,7 @@ impl TxIn {
/// might increase more than `TxIn::legacy_weight`. This happens when the new input added causes
/// the input length `VarInt` to increase its encoding length.
pub fn legacy_weight(&self) -> Weight {
Weight::from_non_witness_data_size(self.base_size() as u64)
Weight::from_non_witness_data_size(self.base_size().to_u64())
}
/// The weight of the TxIn when it's included in a segwit transaction (i.e., a transaction
@ -292,8 +292,8 @@ impl TxIn {
/// - the new input is the first segwit input added - this will add an additional 2WU to the
/// transaction weight to take into account the segwit marker
pub fn segwit_weight(&self) -> Weight {
Weight::from_non_witness_data_size(self.base_size() as u64)
+ Weight::from_witness_data_size(self.witness.size() as u64)
Weight::from_non_witness_data_size(self.base_size().to_u64())
+ Weight::from_witness_data_size(self.witness.size().to_u64())
}
/// Returns the base size of this input.
@ -359,10 +359,10 @@ impl TxOut {
/// # Panics
///
/// If output size * 4 overflows, this should never happen under normal conditions. Use
/// `Weght::from_vb_checked(self.size() as u64)` if you are concerned.
/// `Weght::from_vb_checked(self.size().to_u64())` if you are concerned.
pub fn weight(&self) -> Weight {
// Size is equivalent to virtual size since all bytes of a TxOut are non-witness bytes.
Weight::from_vb(self.size() as u64).expect("should never happen under normal conditions")
Weight::from_vb(self.size().to_u64()).expect("should never happen under normal conditions")
}
/// Returns the total number of bytes that this output contributes to a transaction.
@ -1184,7 +1184,7 @@ where
let (output_count, output_scripts_size) = output_script_lens.into_iter().fold(
(0, 0),
|(output_count, total_scripts_size), script_len| {
let script_size = script_len + VarInt(script_len as u64).size();
let script_size = script_len + VarInt(script_len.to_u64()).size();
(output_count + 1, total_scripts_size + script_size)
},
);
@ -1376,11 +1376,11 @@ impl InputWeightPrediction {
let (count, total_size) =
witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| {
let elem_len = *elem_len.borrow();
let elem_size = elem_len + VarInt(elem_len as u64).size();
let elem_size = elem_len + VarInt(elem_len.to_u64()).size();
(count + 1, total_size + elem_size)
});
let witness_size = if count > 0 { total_size + VarInt(count as u64).size() } else { 0 };
let script_size = input_script_len + VarInt(input_script_len as u64).size();
let script_size = input_script_len + VarInt(input_script_len.to_u64()).size();
InputWeightPrediction { script_size, witness_size }
}

View File

@ -18,7 +18,7 @@ use core::{fmt, mem};
use hashes::{sha256, sha256d, GeneralHash, Hash};
use hex::error::{InvalidCharError, OddLengthStringError};
use internals::write_err;
use internals::{write_err, ToU64 as _};
use io::{BufRead, Cursor, Read, Write};
use crate::bip152::{PrefilledTransaction, ShortId};
@ -369,7 +369,7 @@ pub trait Decodable: Sized {
/// instead.
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(reader: &mut R) -> Result<Self, Error> {
Self::consensus_decode_from_finite_reader(&mut reader.take(MAX_VEC_SIZE as u64))
Self::consensus_decode_from_finite_reader(&mut reader.take(MAX_VEC_SIZE.to_u64()))
}
}
@ -458,7 +458,7 @@ macro_rules! impl_var_int_from {
$(
/// Creates a `VarInt` from a `usize` by casting the to a `u64`.
impl From<$ty> for VarInt {
fn from(x: $ty) -> Self { VarInt(x as u64) }
fn from(x: $ty) -> Self { VarInt(x.to_u64()) }
}
)*
}
@ -545,7 +545,7 @@ impl Encodable for String {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let b = self.as_bytes();
let vi_len = VarInt(b.len() as u64).consensus_encode(w)?;
let vi_len = VarInt(b.len().to_u64()).consensus_encode(w)?;
w.emit_slice(b)?;
Ok(vi_len + b.len())
}
@ -563,7 +563,7 @@ impl Encodable for Cow<'static, str> {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let b = self.as_bytes();
let vi_len = VarInt(b.len() as u64).consensus_encode(w)?;
let vi_len = VarInt(b.len().to_u64()).consensus_encode(w)?;
w.emit_slice(b)?;
Ok(vi_len + b.len())
}
@ -644,7 +644,7 @@ macro_rules! impl_vec {
w: &mut W,
) -> core::result::Result<usize, io::Error> {
let mut len = 0;
len += VarInt(self.len() as u64).consensus_encode(w)?;
len += VarInt(self.len().to_u64()).consensus_encode(w)?;
for c in self.iter() {
len += c.consensus_encode(w)?;
}
@ -700,7 +700,7 @@ pub(crate) fn consensus_encode_with_size<W: Write + ?Sized>(
data: &[u8],
w: &mut W,
) -> Result<usize, io::Error> {
let vi_len = VarInt(data.len() as u64).consensus_encode(w)?;
let vi_len = VarInt(data.len().to_u64()).consensus_encode(w)?;
w.emit_slice(data)?;
Ok(vi_len + data.len())
}
@ -1202,7 +1202,7 @@ mod tests {
T: fmt::Debug,
{
let rand_io_err = Error::Io(io::Error::new(io::ErrorKind::Other, ""));
let varint = VarInt((super::MAX_VEC_SIZE / mem::size_of::<T>()) as u64);
let varint = VarInt((super::MAX_VEC_SIZE / mem::size_of::<T>()).to_u64());
let err = deserialize::<Vec<T>>(&serialize(&varint)).unwrap_err();
assert_eq!(discriminant(&err), discriminant(&rand_io_err));
}

View File

@ -33,7 +33,9 @@ macro_rules! impl_consensus_encoding {
fn consensus_decode<R: $crate::io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$thing, $crate::consensus::encode::Error> {
let mut r = r.take($crate::consensus::encode::MAX_VEC_SIZE as u64);
use internals::ToU64 as _;
let mut r = r.take($crate::consensus::encode::MAX_VEC_SIZE.to_u64());
Ok($thing {
$($field: $crate::consensus::Decodable::consensus_decode(&mut r)?),+
})

View File

@ -11,6 +11,7 @@
use core::fmt;
use internals::ToU64 as _;
use io::{BufRead, Write};
use self::MerkleBlockError::*;
@ -245,7 +246,7 @@ impl PartialMerkleTree {
return Err(NoTransactions);
};
// check for excessively high numbers of transactions
if self.num_transactions as u64 > Weight::MAX_BLOCK / Weight::MIN_TRANSACTION {
if self.num_transactions.to_u64() > Weight::MAX_BLOCK / Weight::MIN_TRANSACTION {
return Err(TooManyTransactions);
}
// there can never be more hashes provided than one for every txid

View File

@ -8,6 +8,7 @@
use core::{fmt, iter};
use hashes::sha256d;
use internals::ToU64 as _;
use io::{BufRead, Write};
use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt};
@ -426,7 +427,7 @@ impl Decodable for HeaderDeserializationWrapper {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE as u64))
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE.to_u64()))
}
}
@ -531,7 +532,7 @@ impl Decodable for RawNetworkMessage {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE as u64))
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE.to_u64()))
}
}

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: CC0-1.0
use internals::ToU64 as _;
use io::{BufRead, Cursor, Read};
use crate::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub};
@ -71,7 +72,7 @@ impl Map for Psbt {
impl Psbt {
pub(crate) fn decode_global<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
let mut r = r.take(MAX_VEC_SIZE as u64);
let mut r = r.take(MAX_VEC_SIZE.to_u64());
let mut tx: Option<Transaction> = None;
let mut version: Option<u32> = None;
let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default();
@ -100,7 +101,7 @@ impl Psbt {
lock_time: Decodable::consensus_decode(&mut decoder)?,
});
if decoder.position() != vlen as u64 {
if decoder.position() != vlen.to_u64() {
return Err(Error::PartialDataConsumption);
}
} else {

View File

@ -7,6 +7,7 @@
use core::fmt;
use internals::ToU64 as _;
use io::{BufRead, Write};
use super::serialize::{Deserialize, Serialize};
@ -80,7 +81,7 @@ impl Key {
let key_byte_size: u64 = byte_size - 1;
if key_byte_size > MAX_VEC_SIZE as u64 {
if key_byte_size > MAX_VEC_SIZE.to_u64() {
return Err(encode::Error::OversizedVectorAllocation {
requested: key_byte_size as usize,
max: MAX_VEC_SIZE,

View File

@ -35,3 +35,28 @@ mod parse;
#[cfg(feature = "serde")]
#[macro_use]
pub mod serde;
/// A conversion trait for unsigned integer types smaller than or equal to 64-bits.
///
/// This trait exists because [`usize`] doesn't implement `Into<u64>`. We only support 32 and 64 bit
/// architectures because of consensus code so we can infallibly do the conversion.
pub trait ToU64 {
/// Converts unsigned integer type to a [`u64`].
fn to_u64(self) -> u64;
}
macro_rules! impl_to_u64 {
($($ty:ident),*) => {
$(
impl ToU64 for $ty { fn to_u64(self) -> u64 { self.into() } }
)*
}
}
impl_to_u64!(u8, u16, u32, u64);
impl ToU64 for usize {
fn to_u64(self) -> u64 {
crate::const_assert!(core::mem::size_of::<usize>() <= 8);
self as u64
}
}