|
| 1 | +use std::borrow::Borrow; |
| 2 | +use std::fmt; |
| 3 | + |
| 4 | +use bitcoin::{Amount, FeeRate}; |
| 5 | +use log::warn; |
| 6 | +use serde::de::{Deserializer, MapAccess, Visitor}; |
| 7 | +use serde::ser::SerializeMap; |
| 8 | +use serde::{Deserialize, Serialize, Serializer}; |
| 9 | + |
| 10 | +#[derive(Debug)] |
| 11 | +#[cfg_attr(feature = "v2", derive(Deserialize, Serialize))] |
| 12 | +pub(crate) struct Params { |
| 13 | + // version |
| 14 | + #[cfg_attr( |
| 15 | + feature = "v2", |
| 16 | + serde(skip_serializing_if = "skip_if_default_v", default = "default_v") |
| 17 | + )] |
| 18 | + pub v: usize, |
| 19 | + |
| 20 | + // disableoutputsubstitution |
| 21 | + #[cfg_attr( |
| 22 | + feature = "v2", |
| 23 | + serde(skip_serializing_if = "skip_if_false", default = "default_output_substitution") |
| 24 | + )] |
| 25 | + pub disable_output_substitution: bool, |
| 26 | + |
| 27 | + // maxadditionalfeecontribution, additionalfeeoutputindex |
| 28 | + #[cfg_attr( |
| 29 | + feature = "v2", |
| 30 | + serde( |
| 31 | + deserialize_with = "deserialize_additional_fee_contribution", |
| 32 | + skip_serializing_if = "Option::is_none", |
| 33 | + serialize_with = "serialize_additional_fee_contribution" |
| 34 | + ) |
| 35 | + )] |
| 36 | + pub additional_fee_contribution: Option<(Amount, usize)>, |
| 37 | + |
| 38 | + // minfeerate |
| 39 | + #[cfg_attr( |
| 40 | + feature = "v2", |
| 41 | + serde( |
| 42 | + deserialize_with = "from_sat_per_vb", |
| 43 | + skip_serializing_if = "skip_if_zero_rate", |
| 44 | + default = "default_min_feerate" |
| 45 | + ) |
| 46 | + )] |
| 47 | + pub min_feerate: FeeRate, |
| 48 | +} |
| 49 | + |
| 50 | +impl Default for Params { |
| 51 | + fn default() -> Self { |
| 52 | + Params { |
| 53 | + v: 1, |
| 54 | + disable_output_substitution: false, |
| 55 | + additional_fee_contribution: None, |
| 56 | + min_feerate: FeeRate::ZERO, |
| 57 | + } |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +impl Params { |
| 62 | + #[cfg(feature = "receive")] |
| 63 | + pub fn from_query_pairs<K, V, I>(pairs: I) -> Result<Self, Error> |
| 64 | + where |
| 65 | + I: Iterator<Item = (K, V)>, |
| 66 | + K: Borrow<str> + Into<String>, |
| 67 | + V: Borrow<str> + Into<String>, |
| 68 | + { |
| 69 | + let mut params = Params::default(); |
| 70 | + |
| 71 | + let mut additional_fee_output_index = None; |
| 72 | + let mut max_additional_fee_contribution = None; |
| 73 | + |
| 74 | + for (k, v) in pairs { |
| 75 | + match (k.borrow(), v.borrow()) { |
| 76 | + ("v", v) => |
| 77 | + if v != "1" { |
| 78 | + return Err(Error::UnknownVersion); |
| 79 | + }, |
| 80 | + ("additionalfeeoutputindex", index) => |
| 81 | + additional_fee_output_index = match index.parse::<usize>() { |
| 82 | + Ok(index) => Some(index), |
| 83 | + Err(_error) => { |
| 84 | + warn!( |
| 85 | + "bad `additionalfeeoutputindex` query value '{}': {}", |
| 86 | + index, _error |
| 87 | + ); |
| 88 | + None |
| 89 | + } |
| 90 | + }, |
| 91 | + ("maxadditionalfeecontribution", fee) => |
| 92 | + max_additional_fee_contribution = |
| 93 | + match bitcoin::Amount::from_str_in(fee, bitcoin::Denomination::Satoshi) { |
| 94 | + Ok(contribution) => Some(contribution), |
| 95 | + Err(_error) => { |
| 96 | + warn!( |
| 97 | + "bad `maxadditionalfeecontribution` query value '{}': {}", |
| 98 | + fee, _error |
| 99 | + ); |
| 100 | + None |
| 101 | + } |
| 102 | + }, |
| 103 | + ("minfeerate", feerate) => |
| 104 | + params.min_feerate = match feerate.parse::<f32>() { |
| 105 | + Ok(fee_rate_sat_per_vb) => { |
| 106 | + // TODO Parse with serde when rust-bitcoin supports it |
| 107 | + let fee_rate_sat_per_kwu = fee_rate_sat_per_vb * 250.0_f32; |
| 108 | + // since it's a minnimum, we want to round up |
| 109 | + FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu.ceil() as u64) |
| 110 | + } |
| 111 | + Err(e) => return Err(Error::FeeRate(e.to_string())), |
| 112 | + }, |
| 113 | + ("disableoutputsubstitution", v) => |
| 114 | + params.disable_output_substitution = v == "true", |
| 115 | + _ => (), |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + match (max_additional_fee_contribution, additional_fee_output_index) { |
| 120 | + (Some(amount), Some(index)) => |
| 121 | + params.additional_fee_contribution = Some((amount, index)), |
| 122 | + (Some(_), None) | (None, Some(_)) => { |
| 123 | + warn!("only one additional-fee parameter specified: {:?}", params); |
| 124 | + } |
| 125 | + _ => (), |
| 126 | + } |
| 127 | + |
| 128 | + log::debug!("parsed optional parameters: {:?}", params); |
| 129 | + Ok(params) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +fn deserialize_additional_fee_contribution<'de, D>( |
| 134 | + deserializer: D, |
| 135 | +) -> Result<Option<(bitcoin::Amount, usize)>, D::Error> |
| 136 | +where |
| 137 | + D: Deserializer<'de>, |
| 138 | +{ |
| 139 | + struct AdditionalFeeContributionVisitor; |
| 140 | + |
| 141 | + impl<'de> Visitor<'de> for AdditionalFeeContributionVisitor { |
| 142 | + type Value = Option<(bitcoin::Amount, usize)>; |
| 143 | + |
| 144 | + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
| 145 | + formatter.write_str("struct params") |
| 146 | + } |
| 147 | + |
| 148 | + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> |
| 149 | + where |
| 150 | + A: MapAccess<'de>, |
| 151 | + { |
| 152 | + let mut additional_fee_output_index: Option<usize> = None; |
| 153 | + let mut max_additional_fee_contribution: Option<bitcoin::Amount> = None; |
| 154 | + |
| 155 | + while let Some(key) = map.next_key()? { |
| 156 | + match key { |
| 157 | + "additional_fee_output_index" => { |
| 158 | + additional_fee_output_index = Some(map.next_value()?); |
| 159 | + } |
| 160 | + "max_additional_fee_contribution" => { |
| 161 | + max_additional_fee_contribution = |
| 162 | + Some(bitcoin::Amount::from_sat(map.next_value()?)); |
| 163 | + } |
| 164 | + _ => { |
| 165 | + // ignore other fields |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + let additional_fee_contribution = |
| 171 | + match (max_additional_fee_contribution, additional_fee_output_index) { |
| 172 | + (Some(amount), Some(index)) => Some((amount, index)), |
| 173 | + (Some(_), None) | (None, Some(_)) => { |
| 174 | + warn!( |
| 175 | + "only one additional-fee parameter specified: {:?}, {:?}", |
| 176 | + max_additional_fee_contribution, additional_fee_output_index |
| 177 | + ); |
| 178 | + None |
| 179 | + } |
| 180 | + _ => None, |
| 181 | + }; |
| 182 | + Ok(additional_fee_contribution) |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + deserializer.deserialize_map(AdditionalFeeContributionVisitor) |
| 187 | +} |
| 188 | + |
| 189 | +fn default_v() -> usize { 2 } |
| 190 | + |
| 191 | +fn default_output_substitution() -> bool { false } |
| 192 | + |
| 193 | +fn default_min_feerate() -> FeeRate { FeeRate::ZERO } |
| 194 | + |
| 195 | +// Function to determine whether to skip serializing a usize if it is 2 (the default) |
| 196 | +fn skip_if_default_v(v: &usize) -> bool { *v == 2 } |
| 197 | + |
| 198 | +// Function to determine whether to skip serializing a bool if it is false (the default) |
| 199 | +fn skip_if_false(b: &bool) -> bool { !(*b) } |
| 200 | + |
| 201 | +// Function to determine whether to skip serializing a FeeRate if it is ZERO (the default) |
| 202 | +fn skip_if_zero_rate(rate: &FeeRate) -> bool { |
| 203 | + *rate == FeeRate::ZERO // replace with your actual comparison logic |
| 204 | +} |
| 205 | + |
| 206 | +fn from_sat_per_vb<'de, D>(deserializer: D) -> Result<FeeRate, D::Error> |
| 207 | +where |
| 208 | + D: Deserializer<'de>, |
| 209 | +{ |
| 210 | + let fee_rate_sat_per_vb = f32::deserialize(deserializer)?; |
| 211 | + Ok(FeeRate::from_sat_per_kwu((fee_rate_sat_per_vb * 250.0_f32) as u64)) |
| 212 | +} |
| 213 | + |
| 214 | +fn serialize_amount<S>(amount: &Amount, serializer: S) -> Result<S::Ok, S::Error> |
| 215 | +where |
| 216 | + S: Serializer, |
| 217 | +{ |
| 218 | + serializer.serialize_u64(amount.to_sat()) |
| 219 | +} |
| 220 | + |
| 221 | +fn serialize_additional_fee_contribution<S>( |
| 222 | + additional_fee_contribution: &Option<(Amount, usize)>, |
| 223 | + serializer: S, |
| 224 | +) -> Result<S::Ok, S::Error> |
| 225 | +where |
| 226 | + S: Serializer, |
| 227 | +{ |
| 228 | + let mut map = serializer.serialize_map(None)?; |
| 229 | + if let Some((amount, index)) = additional_fee_contribution { |
| 230 | + map.serialize_entry("additional_fee_output_index", index)?; |
| 231 | + map.serialize_entry("max_additional_fee_contribution", &amount.to_sat())?; |
| 232 | + } |
| 233 | + map.end() |
| 234 | +} |
| 235 | + |
| 236 | +#[derive(Debug)] |
| 237 | +pub(crate) enum Error { |
| 238 | + UnknownVersion, |
| 239 | + FeeRate(String), |
| 240 | + #[cfg(feature = "v2")] |
| 241 | + Json(serde_json::Error), |
| 242 | +} |
| 243 | + |
| 244 | +impl fmt::Display for Error { |
| 245 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 246 | + match self { |
| 247 | + Error::UnknownVersion => write!(f, "unknown version"), |
| 248 | + Error::FeeRate(_) => write!(f, "could not parse feerate"), |
| 249 | + #[cfg(feature = "v2")] |
| 250 | + Error::Json(e) => write!(f, "could not parse json: {}", e), |
| 251 | + } |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +impl std::error::Error for Error { |
| 256 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } |
| 257 | +} |
0 commit comments