From 579b76b7cbb08b7a367a6e5cfda5ab4c00fd5c05 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 27 Jun 2024 14:43:51 +1000 Subject: [PATCH] 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)`. --- bitcoin/src/bip152.rs | 6 +++--- bitcoin/src/bip158.rs | 4 ++-- bitcoin/src/blockdata/block.rs | 3 ++- bitcoin/src/blockdata/mod.rs | 4 +++- bitcoin/src/blockdata/script/borrowed.rs | 6 ++++-- bitcoin/src/blockdata/script/owned.rs | 3 ++- bitcoin/src/blockdata/transaction.rs | 18 ++++++++--------- bitcoin/src/consensus/encode.rs | 16 +++++++-------- bitcoin/src/internal_macros.rs | 4 +++- bitcoin/src/merkle_tree/block.rs | 3 ++- bitcoin/src/p2p/message.rs | 5 +++-- bitcoin/src/psbt/map/global.rs | 5 +++-- bitcoin/src/psbt/raw.rs | 3 ++- internals/src/lib.rs | 25 ++++++++++++++++++++++++ 14 files changed, 71 insertions(+), 34 deletions(-) diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index e74fd60ed..e59d89a9b 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -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(&self, w: &mut W) -> Result { 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()); diff --git a/bitcoin/src/bip158.rs b/bitcoin/src/bip158.rs index d5b61a549..9326c082e 100644 --- a/bitcoin/src/bip158.rs +++ b/bitcoin/src/bip158.rs @@ -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 { - 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 diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 454f6681f..48314aff9 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -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 diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index bd8595807..230337d11 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -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())); } } } diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 19cc614f5..82b65be53 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -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 diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs index d8a80dea0..ba27f2984 100644 --- a/bitcoin/src/blockdata/script/owned.rs +++ b/bitcoin/src/blockdata/script/owned.rs @@ -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); } diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 2ea5950d2..332a5fdc9 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -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 } } diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs index 5462032e3..c196ba088 100644 --- a/bitcoin/src/consensus/encode.rs +++ b/bitcoin/src/consensus/encode.rs @@ -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(reader: &mut R) -> Result { - 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(&self, w: &mut W) -> Result { 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(&self, w: &mut W) -> Result { 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 { 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( data: &[u8], w: &mut W, ) -> Result { - 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::()) as u64); + let varint = VarInt((super::MAX_VEC_SIZE / mem::size_of::()).to_u64()); let err = deserialize::>(&serialize(&varint)).unwrap_err(); assert_eq!(discriminant(&err), discriminant(&rand_io_err)); } diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs index 5d90b9592..5037b8b75 100644 --- a/bitcoin/src/internal_macros.rs +++ b/bitcoin/src/internal_macros.rs @@ -33,7 +33,9 @@ macro_rules! impl_consensus_encoding { fn consensus_decode( 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)?),+ }) diff --git a/bitcoin/src/merkle_tree/block.rs b/bitcoin/src/merkle_tree/block.rs index d486061fb..a29020475 100644 --- a/bitcoin/src/merkle_tree/block.rs +++ b/bitcoin/src/merkle_tree/block.rs @@ -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 diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs index 8682bcfdc..84794a4ee 100644 --- a/bitcoin/src/p2p/message.rs +++ b/bitcoin/src/p2p/message.rs @@ -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: &mut R) -> Result { - 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: &mut R) -> Result { - 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())) } } diff --git a/bitcoin/src/psbt/map/global.rs b/bitcoin/src/psbt/map/global.rs index 9fd261c5d..ddeeaca77 100644 --- a/bitcoin/src/psbt/map/global.rs +++ b/bitcoin/src/psbt/map/global.rs @@ -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: &mut R) -> Result { - let mut r = r.take(MAX_VEC_SIZE as u64); + let mut r = r.take(MAX_VEC_SIZE.to_u64()); let mut tx: Option = None; let mut version: Option = None; let mut unknowns: BTreeMap> = 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 { diff --git a/bitcoin/src/psbt/raw.rs b/bitcoin/src/psbt/raw.rs index 0e4739bb0..d6c7a6019 100644 --- a/bitcoin/src/psbt/raw.rs +++ b/bitcoin/src/psbt/raw.rs @@ -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, diff --git a/internals/src/lib.rs b/internals/src/lib.rs index 9de80dfc7..fb93fd50e 100644 --- a/internals/src/lib.rs +++ b/internals/src/lib.rs @@ -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`. 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::() <= 8); + self as u64 + } +}