• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 15280364074

27 May 2025 04:13PM UTC coverage: 41.223% (-43.3%) from 84.553%
15280364074

Pull #705

github

web-flow
Merge 34cbcca7d into 87042266d
Pull Request #705: Adjust _danger-local-https to be the default to prevent dangerous cert vulnerability when building with --all-features

1 of 1 new or added line in 1 file covered. (100.0%)

1600 existing lines in 30 files now uncovered.

1705 of 4136 relevant lines covered (41.22%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.61
/payjoin/src/receive/optional_parameters.rs
1
use std::borrow::Borrow;
2
use std::fmt;
3

4
use bitcoin::FeeRate;
5
use log::warn;
6

7
use crate::output_substitution::OutputSubstitution;
8
use crate::Version;
9

10
#[derive(Debug, Clone)]
11
pub(crate) struct Params {
12
    // version
13
    pub v: Version,
14
    // disableoutputsubstitution
15
    pub output_substitution: OutputSubstitution,
16
    // maxadditionalfeecontribution, additionalfeeoutputindex
17
    pub additional_fee_contribution: Option<(bitcoin::Amount, usize)>,
18
    // minfeerate
19
    pub min_fee_rate: FeeRate,
20
    #[cfg(feature = "_multiparty")]
21
    /// Opt in to optimistic psbt merge
22
    pub optimistic_merge: bool,
23
}
24

25
impl Default for Params {
26
    fn default() -> Self {
1✔
27
        Params {
1✔
28
            v: Version::One,
1✔
29
            output_substitution: OutputSubstitution::Enabled,
1✔
30
            additional_fee_contribution: None,
1✔
31
            min_fee_rate: FeeRate::BROADCAST_MIN,
1✔
32
            #[cfg(feature = "_multiparty")]
1✔
33
            optimistic_merge: false,
1✔
34
        }
1✔
35
    }
1✔
36
}
37

38
impl Params {
39
    pub fn from_query_pairs<K, V, I>(
1✔
40
        pairs: I,
1✔
41
        supported_versions: &'static [Version],
1✔
42
    ) -> Result<Self, Error>
1✔
43
    where
1✔
44
        I: Iterator<Item = (K, V)>,
1✔
45
        K: Borrow<str> + Into<String>,
1✔
46
        V: Borrow<str> + Into<String>,
1✔
47
    {
1✔
48
        let mut params = Params::default();
1✔
49

1✔
50
        let mut additional_fee_output_index = None;
1✔
51
        let mut max_additional_fee_contribution = None;
1✔
52

53
        for (key, v) in pairs {
5✔
54
            match (key.borrow(), v.borrow()) {
4✔
55
                ("v", version) =>
4✔
56
                    params.v = match version {
1✔
57
                        "1" => Version::One,
1✔
UNCOV
58
                        "2" => Version::Two,
×
UNCOV
59
                        _ => return Err(Error::UnknownVersion { supported_versions }),
×
60
                    },
61
                ("additionalfeeoutputindex", index) =>
3✔
62
                    additional_fee_output_index = match index.parse::<usize>() {
1✔
63
                        Ok(index) => Some(index),
1✔
64
                        Err(_error) => {
×
65
                            warn!("bad `additionalfeeoutputindex` query value '{index}': {_error}");
×
66
                            None
×
67
                        }
68
                    },
69
                ("maxadditionalfeecontribution", fee) =>
2✔
70
                    max_additional_fee_contribution =
1✔
71
                        match bitcoin::Amount::from_str_in(fee, bitcoin::Denomination::Satoshi) {
1✔
72
                            Ok(contribution) => Some(contribution),
1✔
73
                            Err(_error) => {
×
74
                                warn!(
×
75
                                "bad `maxadditionalfeecontribution` query value '{fee}': {_error}"
×
76
                            );
77
                                None
×
78
                            }
79
                        },
80
                ("minfeerate", fee_rate) =>
1✔
81
                    params.min_fee_rate = match fee_rate.parse::<f32>() {
1✔
82
                        Ok(fee_rate_sat_per_vb) => {
1✔
83
                            // TODO Parse with serde when rust-bitcoin supports it
1✔
84
                            let fee_rate_sat_per_kwu = fee_rate_sat_per_vb * 250.0_f32;
1✔
85
                            // since it's a minimum, we want to round up
1✔
86
                            FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu.ceil() as u64)
1✔
87
                        }
88
                        Err(_) => return Err(Error::FeeRate),
×
89
                    },
UNCOV
90
                ("disableoutputsubstitution", v) =>
×
UNCOV
91
                    params.output_substitution = if v == "true" {
×
UNCOV
92
                        OutputSubstitution::Disabled
×
93
                    } else {
94
                        OutputSubstitution::Enabled
×
95
                    },
96
                #[cfg(feature = "_multiparty")]
97
                ("optimisticmerge", v) => params.optimistic_merge = v == "true",
98
                _ => (),
×
99
            }
100
        }
101

102
        match (max_additional_fee_contribution, additional_fee_output_index) {
1✔
103
            (Some(amount), Some(index)) =>
1✔
104
                params.additional_fee_contribution = Some((amount, index)),
1✔
105
            (Some(_), None) | (None, Some(_)) => {
106
                warn!("only one additional-fee parameter specified: {params:?}");
×
107
            }
UNCOV
108
            (None, None) => (),
×
109
        }
110

111
        log::debug!("parsed optional parameters: {params:?}");
1✔
112
        Ok(params)
1✔
113
    }
1✔
114
}
115

116
#[derive(Debug, PartialEq, Eq)]
117
pub(crate) enum Error {
118
    UnknownVersion { supported_versions: &'static [Version] },
119
    FeeRate,
120
}
121

122
impl fmt::Display for Error {
123
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
124
        match self {
×
125
            Error::UnknownVersion { .. } => write!(f, "unknown version"),
×
126
            Error::FeeRate => write!(f, "could not parse feerate"),
×
127
        }
128
    }
×
129
}
130

131
impl std::error::Error for Error {
132
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
×
133
}
134

135
#[cfg(test)]
136
pub(crate) mod test {
137
    use bitcoin::{Amount, FeeRate};
138

139
    use super::*;
140
    use crate::receive::optional_parameters::Params;
141
    use crate::Version;
142

143
    #[test]
144
    fn test_parse_params() {
145
        let pairs = url::form_urlencoded::parse(b"&maxadditionalfeecontribution=182&additionalfeeoutputindex=0&minfeerate=2&disableoutputsubstitution=true&optimisticmerge=true");
146
        let params = Params::from_query_pairs(pairs, &[Version::One])
147
            .expect("Could not parse params from query pairs");
148
        assert_eq!(params.v, Version::One);
149
        assert_eq!(params.output_substitution, OutputSubstitution::Disabled);
150
        assert_eq!(params.additional_fee_contribution, Some((Amount::from_sat(182), 0)));
151
        assert_eq!(
152
            params.min_fee_rate,
153
            FeeRate::from_sat_per_vb(2).expect("Could not calculate feerate")
154
        );
155
        #[cfg(feature = "_multiparty")]
156
        assert!(params.optimistic_merge)
157
    }
158

159
    #[test]
160
    fn from_query_pairs_unsupported_versions() {
161
        let invalid_pair: Vec<(&str, &str)> = vec![("v", "888")];
162
        let supported_versions = &[Version::One, Version::Two];
163
        let params = Params::from_query_pairs(invalid_pair.into_iter(), supported_versions);
164
        assert!(params.is_err());
165
        assert_eq!(params.err().unwrap(), Error::UnknownVersion { supported_versions });
166
    }
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc