Skip to content

Commit 3b93638

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 3b93638

File tree

5 files changed

+43
-24
lines changed

5 files changed

+43
-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: 19 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};
@@ -38,6 +38,7 @@ use rustls::{
3838

3939
#[cfg(any(feature = "default", feature = "proxy"))]
4040
use crate::socks::{Socks5Stream, TargetAddr, ToTargetAddr};
41+
use crate::utils::convert_fee_rate;
4142

4243
use crate::stream::ClonableStream;
4344

@@ -857,7 +858,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
857858
Ok(deserialized)
858859
}
859860

860-
fn estimate_fee(&self, number: usize) -> Result<f64, Error> {
861+
fn estimate_fee(&self, number: usize) -> Result<FeeRate, Error> {
861862
let req = Request::new_id(
862863
self.last_id.fetch_add(1, Ordering::SeqCst),
863864
"blockchain.estimatefee",
@@ -867,10 +868,11 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
867868

868869
result
869870
.as_f64()
870-
.ok_or_else(|| Error::InvalidResponse(result.clone()))
871+
.map(convert_fee_rate)
872+
.expect("Invalid response")
871873
}
872874

873-
fn relay_fee(&self) -> Result<f64, Error> {
875+
fn relay_fee(&self) -> Result<FeeRate, Error> {
874876
let req = Request::new_id(
875877
self.last_id.fetch_add(1, Ordering::SeqCst),
876878
"blockchain.relayfee",
@@ -880,7 +882,8 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
880882

881883
result
882884
.as_f64()
883-
.ok_or_else(|| Error::InvalidResponse(result.clone()))
885+
.map(convert_fee_rate)
886+
.expect("Invalid response")
884887
}
885888

886889
fn script_subscribe(&self, script: &Script) -> Result<Option<ScriptStatus>, Error> {
@@ -1061,12 +1064,14 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
10611064
.collect()
10621065
}
10631066

1064-
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
1067+
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
10651068
where
10661069
I: IntoIterator + Clone,
10671070
I::Item: Borrow<usize>,
10681071
{
1069-
impl_batch_call!(self, numbers, estimate_fee, apply_deref)
1072+
let fee_rate_num: Result<Vec<f64>, Error> =
1073+
impl_batch_call!(self, numbers, estimate_fee, apply_deref);
1074+
fee_rate_num?.into_iter().map(convert_fee_rate).collect()
10701075
}
10711076

10721077
fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
@@ -1149,20 +1154,21 @@ mod test {
11491154
assert_eq!(resp.hash_function, Some("sha256".into()));
11501155
assert_eq!(resp.pruning, None);
11511156
}
1157+
11521158
#[test]
11531159
fn test_relay_fee() {
11541160
let client = RawClient::new(get_test_server(), None).unwrap();
11551161

1156-
let resp = client.relay_fee().unwrap();
1157-
assert_eq!(resp, 0.00001);
1162+
let resp = client.relay_fee().unwrap().to_sat_per_vb_ceil();
1163+
assert!(resp > 0);
11581164
}
11591165

11601166
#[test]
11611167
fn test_estimate_fee() {
11621168
let client = RawClient::new(get_test_server(), None).unwrap();
11631169

1164-
let resp = client.estimate_fee(10).unwrap();
1165-
assert!(resp > 0.0);
1170+
let resp = client.estimate_fee(10).unwrap().to_sat_per_vb_ceil();
1171+
assert!(resp > 0);
11661172
}
11671173

11681174
#[test]
@@ -1288,8 +1294,8 @@ mod test {
12881294

12891295
let resp = client.batch_estimate_fee(vec![10, 20]).unwrap();
12901296
assert_eq!(resp.len(), 2);
1291-
assert!(resp[0] > 0.0);
1292-
assert!(resp[1] > 0.0);
1297+
assert!(resp[0].to_sat_per_vb_ceil() > 0);
1298+
assert!(resp[1].to_sat_per_vb_ceil() > 0);
12931299
}
12941300

12951301
#[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: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
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};
89

910
/// Verifies a Merkle inclusion proof as retrieved via [`transaction_get_merkle`] for a transaction with the
1011
/// given `txid` and `merkle_root` as included in the [`BlockHeader`].
@@ -41,3 +42,13 @@ pub fn validate_merkle_proof(
4142

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

0 commit comments

Comments
 (0)