From 0dbcd09bbcd817cafd2108e2b470cad88aed4eb8 Mon Sep 17 00:00:00 2001 From: yancy Date: Mon, 26 May 2025 18:04:33 -0500 Subject: [PATCH 1/2] Move CheckedSum trait to crate root In order to add other types to CheckedSum, remove from the Amount module. In so doing, other types added to CheeckSum do not need to be imported into Amount. --- bitcoin/src/lib.rs | 7 ++++--- units/src/amount/mod.rs | 18 +----------------- units/src/lib.rs | 17 +++++++++++++++++ units/tests/api.rs | 4 ++-- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index cb68c3543..60e151355 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -204,14 +204,15 @@ pub mod amount { #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] + pub use units::CheckedSum; + #[cfg(feature = "serde")] + pub use units::amount::serde; pub use units::amount::{ - Amount, CheckedSum, Denomination, Display, InvalidCharacterError, MissingDenominationError, + Amount, Denomination, Display, InvalidCharacterError, MissingDenominationError, MissingDigitsError, OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError, PossiblyConfusingDenominationError, SignedAmount, TooPreciseError, UnknownDenominationError, }; - #[cfg(feature = "serde")] - pub use units::amount::serde; impl Decodable for Amount { #[inline] diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs index b08e6ebb4..19b4a9832 100644 --- a/units/src/amount/mod.rs +++ b/units/src/amount/mod.rs @@ -26,6 +26,7 @@ use core::str::FromStr; use arbitrary::{Arbitrary, Unstructured}; use self::error::{MissingDigitsKind, ParseAmountErrorInner, ParseErrorInner}; +use crate::CheckedSum; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] @@ -593,13 +594,6 @@ enum DisplayStyle { DynamicDenomination, } -/// Calculates the sum over the iterator using checked arithmetic. -pub trait CheckedSum: sealed::Sealed { - /// Calculates the sum over the iterator using checked arithmetic. If an - /// overflow happens it returns [`None`]. - fn checked_sum(self) -> Option; -} - impl CheckedSum for T where T: Iterator, @@ -616,16 +610,6 @@ where } } -mod sealed { - use super::{Amount, SignedAmount}; - - /// Used to seal the `CheckedSum` trait - pub trait Sealed {} - - impl Sealed for T where T: Iterator {} - impl Sealed for T where T: Iterator {} -} - #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for Denomination { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { diff --git a/units/src/lib.rs b/units/src/lib.rs index 06d24d718..e6b36a7dd 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -69,3 +69,20 @@ pub(crate) use self::result::OptionExt; #[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")] #[doc(hidden)] pub type BlockInterval = BlockHeightInterval; + +/// Calculates the sum over the iterator using checked arithmetic. +pub trait CheckedSum: sealed::Sealed { + /// Calculates the sum over the iterator using checked arithmetic. If an + /// overflow happens it returns [`None`]. + fn checked_sum(self) -> Option; +} + +mod sealed { + use super::{Amount, SignedAmount}; + + /// Used to seal the `CheckedSum` trait + pub trait Sealed {} + + impl Sealed for T where T: Iterator {} + impl Sealed for T where T: Iterator {} +} diff --git a/units/tests/api.rs b/units/tests/api.rs index 33b60bc7b..cf5f5def4 100644 --- a/units/tests/api.rs +++ b/units/tests/api.rs @@ -15,7 +15,7 @@ use arbitrary::{Arbitrary, Unstructured}; use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`. use bitcoin_units::{ amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, BlockMtp, - BlockMtpInterval, BlockTime, FeeRate, SignedAmount, Weight, + BlockMtpInterval, BlockTime, CheckedSum, FeeRate, SignedAmount, Weight, }; /// A struct that includes all public non-error enums. @@ -272,7 +272,7 @@ fn regression_default() { fn dyn_compatible() { // If this builds then traits are dyn compatible. struct Traits { - a: Box>, + a: Box>, // These traits are explicitly not dyn compatible. // b: Box, // c: Box, From 9dac4d69e0f142e9a2dc4b61ea49365a8cae3f4b Mon Sep 17 00:00:00 2001 From: yancy Date: Fri, 23 May 2025 14:58:03 -0500 Subject: [PATCH 2/2] Implement CheckedSum for Weight iterator Expose `checked_sum` method to sum an iterator of Weights checked. --- units/src/lib.rs | 3 ++- units/src/weight.rs | 21 +++++++++++++++++++++ units/tests/api.rs | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/units/src/lib.rs b/units/src/lib.rs index e6b36a7dd..643120223 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -78,11 +78,12 @@ pub trait CheckedSum: sealed::Sealed { } mod sealed { - use super::{Amount, SignedAmount}; + use super::{Amount, SignedAmount, Weight}; /// Used to seal the `CheckedSum` trait pub trait Sealed {} impl Sealed for T where T: Iterator {} impl Sealed for T where T: Iterator {} + impl Sealed for T where T: Iterator {} } diff --git a/units/src/weight.rs b/units/src/weight.rs index d801dd254..6649b4d64 100644 --- a/units/src/weight.rs +++ b/units/src/weight.rs @@ -10,6 +10,8 @@ use arbitrary::{Arbitrary, Unstructured}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::CheckedSum; + /// The factor that non-witness serialization data is multiplied by during weight calculation. pub const WITNESS_SCALE_FACTOR: usize = 4; @@ -241,6 +243,13 @@ impl ops::RemAssign for Weight { fn rem_assign(&mut self, rhs: u64) { *self = Weight::from_wu(self.to_wu() % rhs); } } +impl CheckedSum for T +where + T: Iterator, +{ + fn checked_sum(mut self) -> Option { self.try_fold(Weight::ZERO, Weight::checked_add) } +} + impl core::iter::Sum for Weight { fn sum(iter: I) -> Self where @@ -524,4 +533,16 @@ mod tests { weight %= 3; assert_eq!(weight, Weight::from_wu(1)); } + + #[test] + #[cfg(feature = "alloc")] + fn checked_sum_weights() { + assert_eq!([].into_iter().checked_sum(), Some(Weight::ZERO)); + + let sum = alloc::vec![0, 1, 2].iter().map(|&w| Weight::from_wu(w)).checked_sum().unwrap(); + assert_eq!(sum, Weight::from_wu(3)); + + let sum = alloc::vec![1, u64::MAX].iter().map(|&w| Weight::from_wu(w)).checked_sum(); + assert!(sum.is_none()); + } } diff --git a/units/tests/api.rs b/units/tests/api.rs index cf5f5def4..c44025295 100644 --- a/units/tests/api.rs +++ b/units/tests/api.rs @@ -273,6 +273,7 @@ fn dyn_compatible() { // If this builds then traits are dyn compatible. struct Traits { a: Box>, + b: Box>, // These traits are explicitly not dyn compatible. // b: Box, // c: Box,