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

View File

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

View File

@ -480,6 +480,7 @@ impl std::error::Error for ValidationError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use hex::{test_hex_unwrap as hex, FromHex}; use hex::{test_hex_unwrap as hex, FromHex};
use internals::ToU64 as _;
use super::*; use super::*;
use crate::consensus::encode::{deserialize, serialize}; 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.base_size(), some_block.len());
assert_eq!( assert_eq!(
real_decode.weight(), 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 // 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)] #[cfg(test)]
mod tests { mod tests {
use internals::ToU64 as _;
use super::*; use super::*;
#[test] #[test]
@ -41,7 +43,7 @@ pub mod fee_rate {
let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/byte is valid"); 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, Bound, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
}; };
use internals::ToU64 as _;
use super::witness_version::WitnessVersion; use super::witness_version::WitnessVersion;
use super::{ use super::{
bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, PushBytes, bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, PushBytes,
@ -399,11 +401,11 @@ impl Script {
} else if self.is_witness_program() { } else if self.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core 32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field 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 { } else {
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core 32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
8 + // The serialized size of the TxOut's amount field 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") .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 / 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 core::ops::Deref;
use hex::FromHex; use hex::FromHex;
use internals::ToU64 as _;
use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script}; use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script};
use crate::opcodes::all::*; use crate::opcodes::all::*;
@ -100,7 +101,7 @@ impl ScriptBuf {
/// Pushes the slice without reserving /// Pushes the slice without reserving
fn push_slice_no_opt(&mut self, data: &PushBytes) { fn push_slice_no_opt(&mut self, data: &PushBytes) {
// Start with a PUSH opcode // Start with a PUSH opcode
match data.len() as u64 { match data.len().to_u64() {
n if n < opcodes::Ordinary::OP_PUSHDATA1 as u64 => { n if n < opcodes::Ordinary::OP_PUSHDATA1 as u64 => {
self.0.push(n as u8); self.0.push(n as u8);
} }

View File

@ -13,7 +13,7 @@
use core::{cmp, fmt, str}; use core::{cmp, fmt, str};
use hashes::sha256d; use hashes::sha256d;
use internals::write_err; use internals::{write_err, ToU64 as _};
use io::{BufRead, Write}; use io::{BufRead, Write};
use primitives::Sequence; use primitives::Sequence;
use units::parse; use units::parse;
@ -277,7 +277,7 @@ impl TxIn {
/// might increase more than `TxIn::legacy_weight`. This happens when the new input added causes /// might increase more than `TxIn::legacy_weight`. This happens when the new input added causes
/// the input length `VarInt` to increase its encoding length. /// the input length `VarInt` to increase its encoding length.
pub fn legacy_weight(&self) -> Weight { 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 /// 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 /// - 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 /// transaction weight to take into account the segwit marker
pub fn segwit_weight(&self) -> Weight { pub fn segwit_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())
+ Weight::from_witness_data_size(self.witness.size() as u64) + Weight::from_witness_data_size(self.witness.size().to_u64())
} }
/// Returns the base size of this input. /// Returns the base size of this input.
@ -359,10 +359,10 @@ impl TxOut {
/// # Panics /// # Panics
/// ///
/// If output size * 4 overflows, this should never happen under normal conditions. Use /// 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 { pub fn weight(&self) -> Weight {
// Size is equivalent to virtual size since all bytes of a TxOut are non-witness bytes. // 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. /// 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( let (output_count, output_scripts_size) = output_script_lens.into_iter().fold(
(0, 0), (0, 0),
|(output_count, total_scripts_size), script_len| { |(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) (output_count + 1, total_scripts_size + script_size)
}, },
); );
@ -1376,11 +1376,11 @@ impl InputWeightPrediction {
let (count, total_size) = let (count, total_size) =
witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| { witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| {
let elem_len = *elem_len.borrow(); 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) (count + 1, total_size + elem_size)
}); });
let witness_size = if count > 0 { total_size + VarInt(count as u64).size() } else { 0 }; 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 } InputWeightPrediction { script_size, witness_size }
} }

View File

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

View File

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

View File

@ -8,6 +8,7 @@
use core::{fmt, iter}; use core::{fmt, iter};
use hashes::sha256d; use hashes::sha256d;
use internals::ToU64 as _;
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt}; use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt};
@ -426,7 +427,7 @@ impl Decodable for HeaderDeserializationWrapper {
#[inline] #[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> { 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] #[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> { 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 // SPDX-License-Identifier: CC0-1.0
use internals::ToU64 as _;
use io::{BufRead, Cursor, Read}; use io::{BufRead, Cursor, Read};
use crate::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub}; use crate::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub};
@ -71,7 +72,7 @@ impl Map for Psbt {
impl Psbt { impl Psbt {
pub(crate) fn decode_global<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, Error> { 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 tx: Option<Transaction> = None;
let mut version: Option<u32> = None; let mut version: Option<u32> = None;
let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default(); let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default();
@ -100,7 +101,7 @@ impl Psbt {
lock_time: Decodable::consensus_decode(&mut decoder)?, lock_time: Decodable::consensus_decode(&mut decoder)?,
}); });
if decoder.position() != vlen as u64 { if decoder.position() != vlen.to_u64() {
return Err(Error::PartialDataConsumption); return Err(Error::PartialDataConsumption);
} }
} else { } else {

View File

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

View File

@ -35,3 +35,28 @@ mod parse;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[macro_use] #[macro_use]
pub mod serde; 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
}
}