Skip to content

Commit

Permalink
feat!: convert fees from BTC/kB to sats/vB
Browse files Browse the repository at this point in the history
Also changes all fee rates from f64 to proper FeeRate
  • Loading branch information
storopoli committed Aug 8, 2024
1 parent 0b97659 commit 3b93638
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 24 deletions.
10 changes: 5 additions & 5 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::borrow::Borrow;
use std::convert::TryInto;

use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::{block, Script, Transaction, Txid};
use bitcoin::{block, FeeRate, Script, Transaction, Txid};

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

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

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

/// Subscribes to notifications for activity on a specific *scriptPubKey*.
///
Expand Down Expand Up @@ -189,7 +189,7 @@ pub trait ElectrumApi {
///
/// Takes a list of `numbers` of blocks and returns a list of fee required in
/// **Satoshis per kilobyte** to confirm a transaction in the given number of blocks.
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<f64>, Error>
fn batch_estimate_fee<I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<usize>;
Expand Down
8 changes: 4 additions & 4 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{borrow::Borrow, sync::RwLock};

use log::{info, warn};

use bitcoin::{Script, Txid};
use bitcoin::{FeeRate, Script, Txid};

use crate::api::ElectrumApi;
use crate::batch::Batch;
Expand Down Expand Up @@ -207,12 +207,12 @@ impl ElectrumApi for Client {
}

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

#[inline]
fn relay_fee(&self) -> Result<f64, Error> {
fn relay_fee(&self) -> Result<FeeRate, Error> {
impl_inner_call!(self, relay_fee)
}

Expand Down Expand Up @@ -309,7 +309,7 @@ impl ElectrumApi for Client {
}

#[inline]
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<usize>,
Expand Down
32 changes: 19 additions & 13 deletions src/raw_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use log::{debug, error, info, trace, warn};

use bitcoin::consensus::encode::deserialize;
use bitcoin::hex::{DisplayHex, FromHex};
use bitcoin::{Script, Txid};
use bitcoin::{FeeRate, Script, Txid};

#[cfg(feature = "use-openssl")]
use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode};
Expand All @@ -38,6 +38,7 @@ use rustls::{

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

use crate::stream::ClonableStream;

Expand Down Expand Up @@ -857,7 +858,7 @@ impl<T: Read + Write> ElectrumApi for RawClient<T> {
Ok(deserialized)
}

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

result
.as_f64()
.ok_or_else(|| Error::InvalidResponse(result.clone()))
.map(convert_fee_rate)
.expect("Invalid response")
}

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

result
.as_f64()
.ok_or_else(|| Error::InvalidResponse(result.clone()))
.map(convert_fee_rate)
.expect("Invalid response")
}

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

fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<f64>, Error>
fn batch_estimate_fee<'s, I>(&self, numbers: I) -> Result<Vec<FeeRate>, Error>
where
I: IntoIterator + Clone,
I::Item: Borrow<usize>,
{
impl_batch_call!(self, numbers, estimate_fee, apply_deref)
let fee_rate_num: Result<Vec<f64>, Error> =
impl_batch_call!(self, numbers, estimate_fee, apply_deref);
fee_rate_num?.into_iter().map(convert_fee_rate).collect()
}

fn transaction_broadcast_raw(&self, raw_tx: &[u8]) -> Result<Txid, Error> {
Expand Down Expand Up @@ -1149,20 +1154,21 @@ mod test {
assert_eq!(resp.hash_function, Some("sha256".into()));
assert_eq!(resp.pruning, None);
}

#[test]
fn test_relay_fee() {
let client = RawClient::new(get_test_server(), None).unwrap();

let resp = client.relay_fee().unwrap();
assert_eq!(resp, 0.00001);
let resp = client.relay_fee().unwrap().to_sat_per_vb_ceil();
assert!(resp > 0);
}

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

let resp = client.estimate_fee(10).unwrap();
assert!(resp > 0.0);
let resp = client.estimate_fee(10).unwrap().to_sat_per_vb_ceil();
assert!(resp > 0);
}

#[test]
Expand Down Expand Up @@ -1288,8 +1294,8 @@ mod test {

let resp = client.batch_estimate_fee(vec![10, 20]).unwrap();
assert_eq!(resp.len(), 2);
assert!(resp[0] > 0.0);
assert!(resp[1] > 0.0);
assert!(resp[0].to_sat_per_vb_ceil() > 0);
assert!(resp[1].to_sat_per_vb_ceil() > 0);
}

#[test]
Expand Down
4 changes: 3 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ pub enum Error {
AllAttemptsErrored(Vec<Error>),
/// There was an io error reading the socket, to be shared between threads
SharedIOError(Arc<std::io::Error>),

/// There was an error parsing a fee rate
FeeRate(String),
/// Couldn't take a lock on the reader mutex. This means that there's already another reader
/// thread running
CouldntLockReader,
Expand Down Expand Up @@ -365,6 +366,7 @@ impl Display for Error {
Error::MissingDomain => f.write_str("Missing domain while it was explicitly asked to validate it"),
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"),
Error::Mpsc => f.write_str("Broken IPC communication channel: the other thread probably has exited"),
Error::FeeRate(e) => f.write_str(e),
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Utilities helping to handle Electrum-related data.
use crate::types::GetMerkleRes;
use crate::Error;
use bitcoin::hash_types::TxMerkleNode;
use bitcoin::hashes::sha256d::Hash as Sha256d;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::Txid;
use bitcoin::{Amount, FeeRate, Txid};

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

cur == merkle_root.to_raw_hash()
}

/// Converts a fee rate in BTC/kB to sats/vbyte.
pub(crate) fn convert_fee_rate(fee_rate_kvb: f64) -> Result<FeeRate, Error> {
let fee_rate_sat_vb = (Amount::ONE_BTC.to_sat() as f64) * fee_rate_kvb;
let fee_rate = FeeRate::from_sat_per_vb(fee_rate_sat_vb as u64);
match fee_rate {
Some(fee_rate) => Ok(fee_rate),
None => Err(Error::FeeRate("Fee rate conversion failed".to_string())),
}
}

0 comments on commit 3b93638

Please sign in to comment.