What changed, and why it matters
This commit adds new serialization/deserialization helpers so that a list of Bitcoin fee-rate values can be converted to and from JSON (or other serde formats). It is a straightforward feature addition with matching unit tests and contains no security fix or vulnerability.
No security action required; review as a normal feature addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends the existing FeeRate serde modules in units/src/fee_rate/serde.rs by adding a nested vec submodule under each of as_sat_per_kwu_floor, as_sat_per_vb_floor, and as_sat_per_vb_ceil. Each new submodule provides serialize/deserialize functions for &[FeeRate]/Vec
Changed components
units/src/fee_rate/serde.rsunits/tests/serde.rsInspect captured patch +239 / −0
diff --git a/units/src/fee_rate/serde.rs b/units/src/fee_rate/serde.rs
index f778a732..975fff72 100644
--- a/units/src/fee_rate/serde.rs
+++ b/units/src/fee_rate/serde.rs
@@ -94,6 +94,60 @@ pub mod as_sat_per_kwu_floor {
d.deserialize_option(VisitOpt)
}
}
+
+ #[cfg(feature = "alloc")]
+ pub mod vec {
+ //! Serialize and deserialize [`Vec<FeeRate>`] denominated in satoshis per 1000 weight units.
+ //!
+ //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu_floor::vec")]`.
+
+ use alloc::vec::Vec;
+ use core::fmt;
+
+ use serde::de::{self, SeqAccess};
+ use serde::ser::SerializeSeq;
+ use serde::{Deserialize, Deserializer, Serializer};
+
+ use crate::FeeRate;
+
+ pub fn serialize<S: Serializer>(f: &[FeeRate], s: S) -> Result<S::Ok, S::Error> {
+ let mut seq = s.serialize_seq(Some(f.len()))?;
+ for rate in f {
+ seq.serialize_element(&rate.to_sat_per_kwu_floor())?;
+ }
+ seq.end()
+ }
+
+ // Errors on overflow.
+ pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<Vec<FeeRate>, D::Error> {
+ struct VisitVec;
+
+ impl<'de> de::Visitor<'de> for VisitVec {
+ type Value = Vec<FeeRate>;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "a sequence of u64")
+ }
+
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(transparent)]
+ struct Wrapper(#[serde(with = "super")] FeeRate);
+
+ let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
+ while let Some(wrapped) = seq.next_element::<Wrapper>()? {
+ out.push(wrapped.0);
+ }
+ Ok(out)
+ }
+ }
+
+ d.deserialize_seq(VisitVec)
+ }
+ }
}
pub mod as_sat_per_vb_floor {
@@ -167,6 +221,61 @@ pub mod as_sat_per_vb_floor {
d.deserialize_option(VisitOpt)
}
}
+
+ #[cfg(feature = "alloc")]
+ pub mod vec {
+ //! Serialize and deserialize [`Vec<FeeRate>`] denominated in satoshis per virtual byte.
+ //!
+ //! When serializing use floor division to convert per kwu to per virtual byte.
+ //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb_floor::vec")]`.
+
+ use alloc::vec::Vec;
+ use core::fmt;
+
+ use serde::de::{self, SeqAccess};
+ use serde::ser::SerializeSeq;
+ use serde::{Deserialize, Deserializer, Serializer};
+
+ use crate::FeeRate;
+
+ pub fn serialize<S: Serializer>(f: &[FeeRate], s: S) -> Result<S::Ok, S::Error> {
+ let mut seq = s.serialize_seq(Some(f.len()))?;
+ for rate in f {
+ seq.serialize_element(&rate.to_sat_per_vb_floor())?;
+ }
+ seq.end()
+ }
+
+ // Errors on overflow.
+ pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<Vec<FeeRate>, D::Error> {
+ struct VisitVec;
+
+ impl<'de> de::Visitor<'de> for VisitVec {
+ type Value = Vec<FeeRate>;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "a sequence of u64")
+ }
+
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(transparent)]
+ struct Wrapper(#[serde(with = "super")] FeeRate);
+
+ let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
+ while let Some(wrapped) = seq.next_element::<Wrapper>()? {
+ out.push(wrapped.0);
+ }
+ Ok(out)
+ }
+ }
+
+ d.deserialize_seq(VisitVec)
+ }
+ }
}
pub mod as_sat_per_vb_ceil {
@@ -240,6 +349,61 @@ pub mod as_sat_per_vb_ceil {
d.deserialize_option(VisitOpt)
}
}
+
+ #[cfg(feature = "alloc")]
+ pub mod vec {
+ //! Serialize and deserialize [`Vec<FeeRate>`] denominated in satoshis per virtual byte.
+ //!
+ //! When serializing use ceil division to convert per kwu to per virtual byte.
+ //! Use with `#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil::vec")]`.
+
+ use alloc::vec::Vec;
+ use core::fmt;
+
+ use serde::de::{self, SeqAccess};
+ use serde::ser::SerializeSeq;
+ use serde::{Deserialize, Deserializer, Serializer};
+
+ use crate::FeeRate;
+
+ pub fn serialize<S: Serializer>(f: &[FeeRate], s: S) -> Result<S::Ok, S::Error> {
+ let mut seq = s.serialize_seq(Some(f.len()))?;
+ for rate in f {
+ seq.serialize_element(&rate.to_sat_per_vb_ceil())?;
+ }
+ seq.end()
+ }
+
+ // Errors on overflow.
+ pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<Vec<FeeRate>, D::Error> {
+ struct VisitVec;
+
+ impl<'de> de::Visitor<'de> for VisitVec {
+ type Value = Vec<FeeRate>;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "a sequence of u64")
+ }
+
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(transparent)]
+ struct Wrapper(#[serde(with = "super")] FeeRate);
+
+ let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
+ while let Some(wrapped) = seq.next_element::<Wrapper>()? {
+ out.push(wrapped.0);
+ }
+ Ok(out)
+ }
+ }
+
+ d.deserialize_seq(VisitVec)
+ }
+ }
}
/// Overflow occurred while deserializing fee rate per virtual byte.
diff --git a/units/tests/serde.rs b/units/tests/serde.rs
index b9e5132e..13162370 100644
--- a/units/tests/serde.rs
+++ b/units/tests/serde.rs
@@ -329,6 +329,31 @@ fn serde_fee_rate_as_sat_per_vb_floor() {
);
}
+#[test]
+#[cfg(feature = "serde")]
+#[cfg(feature = "alloc")]
+fn serde_fee_rate_as_sat_per_vb_floor_vec() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug)]
+ struct T {
+ #[serde(with = "crate::fee_rate::serde::as_sat_per_vb_floor::vec")]
+ pub fee_rates: Vec<FeeRate>,
+ }
+
+ serde_test::assert_tokens(
+ &T { fee_rates: vec![fee_rate_vb(123), fee_rate_vb(456), fee_rate_vb(789)] },
+ &[
+ serde_test::Token::Struct { name: "T", len: 1 },
+ serde_test::Token::Str("fee_rates"),
+ serde_test::Token::Seq { len: Some(3) },
+ serde_test::Token::U64(123),
+ serde_test::Token::U64(456),
+ serde_test::Token::U64(789),
+ serde_test::Token::SeqEnd,
+ serde_test::Token::StructEnd,
+ ],
+ );
+}
+
#[test]
#[cfg(feature = "serde")]
#[cfg(feature = "alloc")]
@@ -350,6 +375,31 @@ fn serde_fee_rate_as_sat_per_kwu_floor() {
);
}
+#[test]
+#[cfg(feature = "serde")]
+#[cfg(feature = "alloc")]
+fn serde_fee_rate_as_sat_per_kwu_floor_vec() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug)]
+ struct T {
+ #[serde(with = "crate::fee_rate::serde::as_sat_per_kwu_floor::vec")]
+ pub fee_rates: Vec<FeeRate>,
+ }
+
+ serde_test::assert_tokens(
+ &T { fee_rates: vec![fee_rate_kwu(123), fee_rate_kwu(456), fee_rate_kwu(789)] },
+ &[
+ serde_test::Token::Struct { name: "T", len: 1 },
+ serde_test::Token::Str("fee_rates"),
+ serde_test::Token::Seq { len: Some(3) },
+ serde_test::Token::U64(123),
+ serde_test::Token::U64(456),
+ serde_test::Token::U64(789),
+ serde_test::Token::SeqEnd,
+ serde_test::Token::StructEnd,
+ ],
+ );
+}
+
#[test]
#[cfg(feature = "serde")]
#[cfg(feature = "alloc")]
@@ -371,6 +421,31 @@ fn serde_fee_rate_as_sat_per_vb_ceil() {
);
}
+#[test]
+#[cfg(feature = "serde")]
+#[cfg(feature = "alloc")]
+fn serde_fee_rate_as_sat_per_vb_ceil_vec() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug)]
+ struct T {
+ #[serde(with = "crate::fee_rate::serde::as_sat_per_vb_ceil::vec")]
+ pub fee_rates: Vec<FeeRate>,
+ }
+
+ serde_test::assert_tokens(
+ &T { fee_rates: vec![fee_rate_vb(123), fee_rate_vb(456), fee_rate_vb(789)] },
+ &[
+ serde_test::Token::Struct { name: "T", len: 1 },
+ serde_test::Token::Str("fee_rates"),
+ serde_test::Token::Seq { len: Some(3) },
+ serde_test::Token::U64(123),
+ serde_test::Token::U64(456),
+ serde_test::Token::U64(789),
+ serde_test::Token::SeqEnd,
+ serde_test::Token::StructEnd,
+ ],
+ );
+}
+
#[test]
#[cfg(feature = "serde")]
#[cfg(feature = "alloc")]
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.