Repository: rust-bitcoin/rust-bitcoin
Pull request #3677 (2024-11-28 05:02:42)

units: Implement `iter::Sum` for all types that implement `ops::Add`
Enables summing an iterator of values. Note that this does not include either `LockTime`s. `absolute::LockTime` should not be added and for `relative::LockTime` we have https://github.com/rust-bitcoin/rust-bitcoin/issues/3676

Close: #1638


Patch:
diff --git a/bitcoin/src/network/params.rs b/bitcoin/src/network/params.rs
index e322009b..eecc42cb 100644
--- a/bitcoin/src/network/params.rs
+++ b/bitcoin/src/network/params.rs
@@ -113,7 +113,7 @@ pub struct Params {
     /// Expected amount of time to mine one block.
     pub pow_target_spacing: u64,
     /// Difficulty recalculation interval.
-    pub pow_target_timespan: u32,
+    pub pow_target_timespan: u64,
     /// Determines whether minimal difficulty may be used for blocks or not.
     pub allow_min_difficulty_blocks: bool,
     /// Determines whether retargeting is disabled for this network or not.
@@ -261,7 +261,7 @@ pub const fn new(network: Network) -> Self {
 
     /// Calculates the number of blocks between difficulty adjustments.
     pub fn difficulty_adjustment_interval(&self) -> u64 {
-        u64::from(self.pow_target_timespan) / self.pow_target_spacing
+        self.pow_target_timespan / self.pow_target_spacing
     }
 }
 
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index b6d531cb..7566e73b 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -357,7 +357,7 @@ fn from_unprefixed_hex(s: &str) -> Result<CompactTarget, UnprefixedHexError> {
         /// ref: <https://github.com/bitcoin/bitcoin/blob/0503cbea9aab47ec0a87d34611e5453158727169/src/pow.cpp>
         ///
         /// Given the previous Target, represented as a [`CompactTarget`], the difficulty is adjusted
-        /// by taking the timespan between them, and multiplying the current [`CompactTarget`] by a factor
+        /// by taking the timespan between them, and multipling the current [`CompactTarget`] by a factor
         /// of the net timespan and expected timespan. The [`CompactTarget`] may not adjust by more than
         /// a factor of 4, or adjust beyond the maximum threshold for the network.
         ///
@@ -371,14 +371,12 @@ fn from_unprefixed_hex(s: &str) -> Result<CompactTarget, UnprefixedHexError> {
         /// Take the example of the first difficulty adjustment. Block 2016 introduces a new [`CompactTarget`],
         /// which takes the net timespan between Block 2015 and Block 0, and recomputes the difficulty.
         ///
-        /// To calculate the timespan, users should first convert their u32 timestamps to i64s before subtracting them
-        ///
         /// # Returns
         ///
         /// The expected [`CompactTarget`] recalculation.
         fn from_next_work_required(
             last: CompactTarget,
-            timespan: i64,
+            timespan: u64,
             params: impl AsRef<Params>,
         ) -> CompactTarget {
             let params = params.as_ref();
@@ -389,11 +387,11 @@ fn from_next_work_required(
             // ref: <https://github.com/bitcoin/bitcoin/blob/0503cbea9aab47ec0a87d34611e5453158727169/src/pow.cpp>
             let min_timespan = params.pow_target_timespan >> 2; // Lines 56/57
             let max_timespan = params.pow_target_timespan << 2; // Lines 58/59
-            let actual_timespan = timespan.clamp(min_timespan.into(), max_timespan.into());
+            let actual_timespan = timespan.clamp(min_timespan, max_timespan);
             let prev_target: Target = last.into();
             let maximum_retarget = prev_target.max_transition_threshold(params); // bnPowLimit
             let retarget = prev_target.0; // bnNew
-            let retarget = retarget.mul(u128::try_from(actual_timespan).expect("clamped value won't be negative").into());
+            let retarget = retarget.mul(actual_timespan.into());
             let retarget = retarget.div(params.pow_target_timespan.into());
             let retarget = Target(retarget);
             if retarget.ge(&maximum_retarget) {
@@ -1752,8 +1750,8 @@ fn compact_target_lower_hex_and_upper_hex() {
     fn compact_target_from_upwards_difficulty_adjustment() {
         let params = Params::new(crate::Network::Signet);
         let starting_bits = CompactTarget::from_consensus(503543726); // Genesis compact target on Signet
-        let start_time: i64 = 1598918400; // Genesis block unix time
-        let end_time: i64 = 1599332177; // Block 2015 unix time
+        let start_time: u64 = 1598918400; // Genesis block unix time
+        let end_time: u64 = 1599332177; // Block 2015 unix time
         let timespan = end_time - start_time; // Faster than expected
         let adjustment = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
         let adjustment_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
@@ -1764,8 +1762,8 @@ fn compact_target_from_upwards_difficulty_adjustment() {
     fn compact_target_from_downwards_difficulty_adjustment() {
         let params = Params::new(crate::Network::Signet);
         let starting_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
-        let start_time: i64 = 1599332844; // Block 2016 unix time
-        let end_time: i64 = 1600591200; // Block 4031 unix time
+        let start_time: u64 = 1599332844; // Block 2016 unix time
+        let end_time: u64 = 1600591200; // Block 4031 unix time
         let timespan = end_time - start_time; // Slower than expected
         let adjustment = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
         let adjustment_bits = CompactTarget::from_consensus(503397348); // Block 4032 compact target
@@ -1831,18 +1829,7 @@ fn compact_target_from_downwards_difficulty_adjustment_using_headers() {
     fn compact_target_from_maximum_upward_difficulty_adjustment() {
         let params = Params::new(crate::Network::Signet);
         let starting_bits = CompactTarget::from_consensus(503403001);
-        let timespan = params.pow_target_timespan / 5;
-        let got = CompactTarget::from_next_work_required(starting_bits, timespan.into(), params);
-        let want =
-            Target::from_compact(starting_bits).min_transition_threshold().to_compact_lossy();
-        assert_eq!(got, want);
-    }
-
-    #[test]
-    fn compact_target_from_maximum_upward_difficulty_adjustment_with_negative_timespan() {
-        let params = Params::new(crate::Network::Signet);
-        let starting_bits = CompactTarget::from_consensus(503403001);
-        let timespan: i64 = -i64::from(params.pow_target_timespan);
+        let timespan = (0.2 * params.pow_target_timespan as f64) as u64;
         let got = CompactTarget::from_next_work_required(starting_bits, timespan, params);
         let want =
             Target::from_compact(starting_bits).min_transition_threshold().to_compact_lossy();
@@ -1854,7 +1841,7 @@ fn compact_target_from_minimum_downward_difficulty_adjustment() {
         let params = Params::new(crate::Network::Signet);
         let starting_bits = CompactTarget::from_consensus(403403001); // High difficulty for Signet
         let timespan = 5 * params.pow_target_timespan; // Really slow.
-        let got = CompactTarget::from_next_work_required(starting_bits, timespan.into(), &params);
+        let got = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
         let want =
             Target::from_compact(starting_bits).max_transition_threshold(params).to_compact_lossy();
         assert_eq!(got, want);
@@ -1865,7 +1852,7 @@ fn compact_target_from_adjustment_is_max_target() {
         let params = Params::new(crate::Network::Signet);
         let starting_bits = CompactTarget::from_consensus(503543726); // Genesis compact target on Signet
         let timespan = 5 * params.pow_target_timespan; // Really slow.
-        let got = CompactTarget::from_next_work_required(starting_bits, timespan.into(), &params);
+        let got = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
         let want = params.max_attainable_target.to_compact_lossy();
         assert_eq!(got, want);
     }
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index b90fdd5e..841b2707 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -442,6 +442,16 @@ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
     }
 }
 
+impl<'a> core::iter::Sum<&'a SignedAmount> for SignedAmount {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a SignedAmount>,
+    {
+        let sats: i64 = iter.map(|amt| amt.0).sum();
+        SignedAmount::from_sat(sats)
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for SignedAmount {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index a6826238..143c94e5 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -432,6 +432,16 @@ fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
     }
 }
 
+impl<'a> core::iter::Sum<&'a Amount> for Amount {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a Amount>,
+    {
+        let sats: u64 = iter.map(|amt| amt.0).sum();
+        Amount::from_sat(sats)
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for Amount {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/units/src/block.rs b/units/src/block.rs
index 7b407e33..aabb4c2c 100644
--- a/units/src/block.rs
+++ b/units/src/block.rs
@@ -231,6 +231,23 @@ impl ops::SubAssign<BlockInterval> for BlockInterval {
     fn sub_assign(&mut self, rhs: BlockInterval) { self.0 = self.to_u32() - rhs.to_u32(); }
 }
 
+impl core::iter::Sum for BlockInterval {
+    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+        let sum = iter.map(|interval| interval.0).sum();
+        BlockInterval::from_u32(sum)
+    }
+}
+
+impl<'a> core::iter::Sum<&'a BlockInterval> for BlockInterval {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a BlockInterval>,
+    {
+        let sum = iter.map(|interval| interval.0).sum();
+        BlockInterval::from_u32(sum)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/units/src/fee_rate.rs b/units/src/fee_rate.rs
index 4ed19f68..c6f3f76f 100644
--- a/units/src/fee_rate.rs
+++ b/units/src/fee_rate.rs
@@ -267,6 +267,24 @@ impl SubAssign<&FeeRate> for FeeRate {
     fn sub_assign(&mut self, rhs: &FeeRate) { self.0 -= rhs.0 }
 }
 
+impl core::iter::Sum for FeeRate {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = Self>,
+    {
+        FeeRate::from_sat_per_kwu(iter.map(FeeRate::to_sat_per_kwu).sum())
+    }
+}
+
+impl<'a> core::iter::Sum<&'a FeeRate> for FeeRate {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a FeeRate>,
+    {
+        FeeRate::from_sat_per_kwu(iter.map(|f| FeeRate::to_sat_per_kwu(*f)).sum())
+    }
+}
+
 crate::impl_parse_str_from_int_infallible!(FeeRate, u64, from_sat_per_kwu);
 
 #[cfg(test)]


Test patch:
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index e695a443..00d54d72 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -825,8 +825,8 @@ struct T {
 
 #[test]
 fn sum_amounts() {
-    assert_eq!(Amount::from_sat(0), [].into_iter().sum::<Amount>());
-    assert_eq!(SignedAmount::from_sat(0), [].into_iter().sum::<SignedAmount>());
+    assert_eq!(Amount::from_sat(0), [].iter().sum::<Amount>());
+    assert_eq!(SignedAmount::from_sat(0), [].iter().sum::<SignedAmount>());
 
     let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
     let sum = amounts.into_iter().sum::<Amount>();