Skip to content

Commit d9bd4aa

Browse files
committed
feat!: convert fees from BTC/kB to sats/vB
Also changes all fee rates from f64 to proper FeeRate
1 parent 0b97659 commit d9bd4aa

File tree

5 files changed

+66
-24
lines changed

5 files changed

+66
-24
lines changed

src/api.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::borrow::Borrow;
44
use std::convert::TryInto;
55

66
use bitcoin::consensus::encode::{deserialize, serialize};
7-
use bitcoin::{block, Script, Transaction, Txid};
7+
use bitcoin::{block, FeeRate, Script, Transaction, Txid};
88

99
use crate::batch::Batch;
1010
use crate::types::*;
@@ -94,11 +94,11 @@ pub trait ElectrumApi {
9494
/// Tries to fetch `count` block headers starting from `start_height`.
9595
fn block_headers(&self, start_height: usize, count: usize) -> Result<GetHeadersRes, Error>;
9696

97-
/// Estimates the fee required in **Bitcoin per kilobyte** to confirm a transaction in `number` blocks.
98-
fn estimate_fee(&self, number: usize) -> Result<f64, Error>;
97+
/// Estimates the fee required in [`FeeRate`] to confirm a transaction in `number` blocks.
98+
fn estimate_fee(&self, number: usize) -> Result<FeeRate, Error>;
9999

100100
/// Returns the minimum accepted fee by the server's node in **Bitcoin, not Satoshi**.
101-
fn relay_fee(&self) -> Result<f64, Error>;
101+
fn relay_fee(&self) -> Result<FeeRate, Error>;
102102

103103
/// Subscribes to notifications for activity on a specific *scriptPubKey*.
104104
///
@@ -189,7 +189,7 @@ pub trait ElectrumApi {
189189
///
190190
/// Takes a list of `numbers` of blocks and returns a list of fee required in
191191
/// **Satoshis per kilobyte** to confirm a transaction in the given number of blocks.
192-
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
192+
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
193193
where
194194
I: IntoIterator + Clone,
195195
I::Item: Borrow<usize>;

src/client.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{borrow::Borrow, sync::RwLock};
44

55
use log::{info, warn};
66

7-
use bitcoin::{Script, Txid};
7+
use bitcoin::{FeeRate, Script, Txid};
88

99
use crate::api::ElectrumApi;
1010
use crate::batch::Batch;
@@ -207,12 +207,12 @@ impl ElectrumApi for Client {
207207
}
208208

209209
#[inline]
210-
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
210+
fn estimate_fee(&self, number: usize) -> Result<FeeRate, Error> {
211211
impl_inner_call!(self, estimate_fee, number)
212212
}
213213

214214
#[inline]
215-
fn relay_fee(&self) -> Result<f64, Error> {
215+
fn relay_fee(&self) -> Result<FeeRate, Error> {
216216
impl_inner_call!(self, relay_fee)
217217
}
218218

@@ -309,7 +309,7 @@ impl ElectrumApi for Client {
309309
}
310310

311311
#[inline]
312-
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
312+
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
313313
where
314314
I: IntoIterator + Clone,
315315
I::Item: Borrow<usize>,

src/raw_client.rs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use log::{debug, error, info, trace, warn};
1717

1818
use bitcoin::consensus::encode::deserialize;
1919
use bitcoin::hex::{DisplayHex, FromHex};
20-
use bitcoin::{Script, Txid};
20+
use bitcoin::{FeeRate, Script, Txid};
2121

2222
#[cfg(feature = "use-openssl")]
2323
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
@@ -35,9 +35,11 @@ use rustls::{
3535
pki_types::{Der, TrustAnchor},
3636
ClientConfig, ClientConnection, RootCertStore, StreamOwned,
3737
};
38+
use serde_json::Value;
3839

3940
#[cfg(any(feature = "default", feature = "proxy"))]
4041
use crate::socks::{Socks5Stream, TargetAddr, ToTargetAddr};
42+
use crate::utils::convert_fee_rate;
4143

4244
use crate::stream::ClonableStream;
4345

@@ -69,6 +71,23 @@ macro_rules! impl_batch_call {
6971

7072
Ok(answer)
7173
}};
74+
75+
( $self:expr, $data:expr, $aux_call:expr, $call:ident, $($apply_deref:tt)? ) => {{
76+
let mut batch = Batch::default();
77+
for i in $data {
78+
batch.$call($($apply_deref)* i.borrow());
79+
}
80+
81+
let resp = $self.batch_call(&batch)?;
82+
let mut answer = Vec::new();
83+
84+
for x in resp {
85+
$aux_call(x);
86+
answer.push(serde_json::from_value(x)?);
87+
}
88+
89+
Ok(answer)
90+
}};
7291
}
7392

7493
/// A trait for [`ToSocketAddrs`](https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html) that
@@ -857,7 +876,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
857876
Ok(deserialized)
858877
}
859878

860-
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
879+
fn estimate_fee(&self, number: usize) -> Result<FeeRate, Error> {
861880
let req = Request::new_id(
862881
self.last_id.fetch_add(1, Ordering::SeqCst),
863882
"blockchain.estimatefee",
@@ -867,10 +886,11 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
867886

868887
result
869888
.as_f64()
870-
.ok_or_else(|| Error::InvalidResponse(result.clone()))
889+
.map(|val| convert_fee_rate(Value::from(val)))
890+
.expect("Invalid response")
871891
}
872892

873-
fn relay_fee(&self) -> Result<f64, Error> {
893+
fn relay_fee(&self) -> Result<FeeRate, Error> {
874894
let req = Request::new_id(
875895
self.last_id.fetch_add(1, Ordering::SeqCst),
876896
"blockchain.relayfee",
@@ -880,7 +900,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
880900

881901
result
882902
.as_f64()
883-
.ok_or_else(|| Error::InvalidResponse(result.clone()))
903+
.map(|val| convert_fee_rate(Value::from(val)))
904+
.expect("Invalid response")
884905
}
885906

886907
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
@@ -1061,12 +1082,12 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
10611082
.collect()
10621083
}
10631084

1064-
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
1085+
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
10651086
where
10661087
I: IntoIterator + Clone,
10671088
I::Item: Borrow<usize>,
10681089
{
1069-
impl_batch_call!(self, numbers, estimate_fee, apply_deref)
1090+
impl_batch_call!(self, numbers, convert_fee_rate, estimate_fee, apply_deref)
10701091
}
10711092

10721093
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
@@ -1149,20 +1170,21 @@ mod test {
11491170
assert_eq!(resp.hash_function, Some("sha256".into()));
11501171
assert_eq!(resp.pruning, None);
11511172
}
1173+
11521174
#[test]
11531175
fn test_relay_fee() {
11541176
let client = RawClient::new(get_test_server(), None).unwrap();
11551177

1156-
let resp = client.relay_fee().unwrap();
1157-
assert_eq!(resp, 0.00001);
1178+
let resp = client.relay_fee().unwrap().to_sat_per_vb_ceil();
1179+
assert!(resp > 0);
11581180
}
11591181

11601182
#[test]
11611183
fn test_estimate_fee() {
11621184
let client = RawClient::new(get_test_server(), None).unwrap();
11631185

1164-
let resp = client.estimate_fee(10).unwrap();
1165-
assert!(resp > 0.0);
1186+
let resp = client.estimate_fee(10).unwrap().to_sat_per_vb_ceil();
1187+
assert!(resp > 0);
11661188
}
11671189

11681190
#[test]
@@ -1288,8 +1310,8 @@ mod test {
12881310

12891311
let resp = client.batch_estimate_fee(vec![10, 20]).unwrap();
12901312
assert_eq!(resp.len(), 2);
1291-
assert!(resp[0] > 0.0);
1292-
assert!(resp[1] > 0.0);
1313+
assert!(resp[0].to_sat_per_vb_ceil() > 0);
1314+
assert!(resp[1].to_sat_per_vb_ceil() > 0);
12931315
}
12941316

12951317
#[test]

src/types.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ pub enum Error {
309309
AllAttemptsErrored(Vec<Error>),
310310
/// There was an io error reading the socket, to be shared between threads
311311
SharedIOError(Arc<std::io::Error>),
312-
312+
/// There was an error parsing a fee rate
313+
FeeRate(String),
313314
/// Couldn't take a lock on the reader mutex. This means that there's already another reader
314315
/// thread running
315316
CouldntLockReader,
@@ -365,6 +366,7 @@ impl Display for Error {
365366
Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
366367
Error::CouldntLockReader => f.write_str("Couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"),
367368
Error::Mpsc => f.write_str("Broken IPC communication channel: the other thread probably has exited"),
369+
Error::FeeRate(e) => f.write_str(e),
368370
}
369371
}
370372
}

src/utils.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Utilities helping to handle Electrum-related data.
22
33
use crate::types::GetMerkleRes;
4+
use crate::Error;
45
use bitcoin::hash_types::TxMerkleNode;
56
use bitcoin::hashes::sha256d::Hash as Sha256d;
67
use bitcoin::hashes::{Hash, HashEngine};
7-
use bitcoin::Txid;
8+
use bitcoin::{Amount, FeeRate, Txid};
9+
use serde_json::Value;
810

911
/// Verifies a Merkle inclusion proof as retrieved via [`transaction_get_merkle`] for a transaction with the
1012
/// given `txid` and `merkle_root` as included in the [`BlockHeader`].
@@ -41,3 +43,19 @@ pub fn validate_merkle_proof(
4143

4244
cur == merkle_root.to_raw_hash()
4345
}
46+
47+
/// Converts a fee rate in BTC/kB to sats/vbyte.
48+
pub(crate) fn convert_fee_rate(fee_rate_kvb: Value) -> Result<FeeRate, Error> {
49+
let fee_rate_kvb = match fee_rate_kvb.as_f64() {
50+
Some(fee_rate_kvb) => fee_rate_kvb,
51+
None => {
52+
return Err(Error::FeeRate("Fee rate conversion failed".to_string()));
53+
}
54+
};
55+
let fee_rate_sat_vb = (Amount::ONE_BTC.to_sat() as f64) * fee_rate_kvb;
56+
let fee_rate = FeeRate::from_sat_per_vb(fee_rate_sat_vb as u64);
57+
match fee_rate {
58+
Some(fee_rate) => Ok(fee_rate),
59+
None => Err(Error::FeeRate("Fee rate conversion failed".to_string())),
60+
}
61+
}

0 commit comments

Comments
 (0)