]> Untitled Git - bdk/commitdiff
[wallet] Add a type convert fee units, add `Wallet::estimate_fee()`
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 7 Aug 2020 09:23:01 +0000 (11:23 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 7 Aug 2020 09:23:46 +0000 (11:23 +0200)
src/blockchain/electrum.rs
src/blockchain/esplora.rs
src/blockchain/mod.rs
src/cli.rs
src/lib.rs
src/wallet/mod.rs
src/wallet/tx_builder.rs
src/wallet/utils.rs

index d0bc9710248f6233a244572f401cf37b86fde85c..5ac861519a0743508bef0f5148f79251b36e0861 100644 (file)
@@ -11,6 +11,7 @@ use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
 use super::*;
 use crate::database::{BatchDatabase, DatabaseUtils};
 use crate::error::Error;
+use crate::FeeRate;
 
 pub struct ElectrumBlockchain(Option<Client>);
 
@@ -77,6 +78,15 @@ impl OnlineBlockchain for ElectrumBlockchain {
             .block_headers_subscribe()
             .map(|data| data.height)?)
     }
+
+    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+        Ok(FeeRate::from_btc_per_kvb(
+            self.0
+                .as_ref()
+                .ok_or(Error::OfflineClient)?
+                .estimate_fee(target)? as f32,
+        ))
+    }
 }
 
 impl ElectrumLikeSync for Client {
index f49fdcfd4160f74b29d682406c47a0f6c2d53f64..53dcb5b3862f249fc7f564b91da266622b44f6c4 100644 (file)
@@ -1,4 +1,4 @@
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 
 use futures::stream::{self, StreamExt, TryStreamExt};
 
@@ -18,6 +18,7 @@ use self::utils::{ELSGetHistoryRes, ELSListUnspentRes, ElectrumLikeSync};
 use super::*;
 use crate::database::{BatchDatabase, DatabaseUtils};
 use crate::error::Error;
+use crate::FeeRate;
 
 #[derive(Debug)]
 pub struct UrlClient {
@@ -99,6 +100,27 @@ impl OnlineBlockchain for EsploraBlockchain {
             .ok_or(Error::OfflineClient)?
             ._get_height())?)
     }
+
+    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+        let estimates = await_or_block!(self
+            .0
+            .as_ref()
+            .ok_or(Error::OfflineClient)?
+            ._get_fee_estimates())?;
+
+        let fee_val = estimates
+            .into_iter()
+            .map(|(k, v)| Ok::<_, std::num::ParseIntError>((k.parse::<usize>()?, v)))
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|e| Error::Generic(e.to_string()))?
+            .into_iter()
+            .take_while(|(k, _)| k <= &target)
+            .map(|(_, v)| v)
+            .last()
+            .unwrap_or(1.0);
+
+        Ok(FeeRate::from_sat_per_vb(fee_val as f32))
+    }
 }
 
 impl UrlClient {
@@ -232,6 +254,17 @@ impl UrlClient {
             })
             .collect())
     }
+
+    async fn _get_fee_estimates(&self) -> Result<HashMap<String, f64>, EsploraError> {
+        Ok(self
+            .client
+            .get(&format!("{}/api/fee-estimates", self.url,))
+            .send()
+            .await?
+            .error_for_status()?
+            .json::<HashMap<String, f64>>()
+            .await?)
+    }
 }
 
 #[maybe_async]
index 2c7081ac9586c5a691ee4152a17076099148944d..08e466948edcaf645f840c334626337126ac8010 100644 (file)
@@ -5,6 +5,7 @@ use bitcoin::{Transaction, Txid};
 
 use crate::database::{BatchDatabase, DatabaseUtils};
 use crate::error::Error;
+use crate::FeeRate;
 
 pub mod utils;
 
@@ -64,6 +65,7 @@ pub trait OnlineBlockchain: Blockchain {
     fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
 
     fn get_height(&self) -> Result<usize, Error>;
+    fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
 }
 
 pub type ProgressData = (f32, Option<String>);
index d4e4578905700cbdc8da1e5e6140ee9ed7d3a3a9..48f5e42b565ea3c3eb45fc528be2f3406b9098fd 100644 (file)
@@ -13,7 +13,7 @@ use bitcoin::{Address, OutPoint};
 
 use crate::error::Error;
 use crate::types::ScriptType;
-use crate::{TxBuilder, Wallet};
+use crate::{FeeRate, TxBuilder, Wallet};
 
 fn parse_addressee(s: &str) -> Result<(Address, u64), String> {
     let parts: Vec<_> = s.split(":").collect();
@@ -331,7 +331,7 @@ where
 
         if let Some(fee_rate) = sub_matches.value_of("fee_rate") {
             let fee_rate = f32::from_str(fee_rate).map_err(|s| Error::Generic(s.to_string()))?;
-            tx_builder = tx_builder.fee_rate(fee_rate);
+            tx_builder = tx_builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate));
         }
         if let Some(utxos) = sub_matches.values_of("utxos") {
             let utxos = utxos
index 0d5ce881cf7a996b3188ebf4e269a269b51d2c13..96dcc6bc06703a6a964d3dc565dfdb23091bba3d 100644 (file)
@@ -42,4 +42,6 @@ pub mod types;
 pub mod wallet;
 
 pub use descriptor::ExtendedDescriptor;
-pub use wallet::{OfflineWallet, TxBuilder, Wallet};
+pub use wallet::tx_builder::TxBuilder;
+pub use wallet::utils::FeeRate;
+pub use wallet::{OfflineWallet, Wallet};
index 6dd5ad5983c9e092f0d114aa9f54cfac36cb753a..609f2f457ca8693fc6988e6bc29478efaa5a3626 100644 (file)
@@ -22,8 +22,8 @@ pub mod time;
 pub mod tx_builder;
 pub mod utils;
 
-pub use tx_builder::TxBuilder;
-use utils::IsDust;
+use tx_builder::TxBuilder;
+use utils::{FeeRate, IsDust};
 
 use crate::blockchain::{noop_progress, Blockchain, OfflineBlockchain, OnlineBlockchain};
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
@@ -142,7 +142,7 @@ where
             output: vec![],
         };
 
-        let fee_rate = builder.fee_perkb.unwrap_or(1e3) * 100_000.0;
+        let fee_rate = builder.fee_rate.unwrap_or_default().as_sat_vb();
         if builder.send_all && builder.addressees.len() != 1 {
             return Err(Error::SendAllMultipleOutputs);
         }
@@ -759,6 +759,11 @@ where
 
         Ok(tx.txid())
     }
+
+    #[maybe_async]
+    pub fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+        Ok(maybe_await!(self.client.estimate_fee(target))?)
+    }
 }
 
 #[cfg(test)]
index dab488febd44022889b8b5901b70564efa711845..b644660a5817df5e28d8e5fcae0b5ad59c4c8c8a 100644 (file)
@@ -3,13 +3,14 @@ use std::collections::BTreeMap;
 use bitcoin::{Address, OutPoint, SigHashType};
 
 use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
+use super::utils::FeeRate;
 
 // TODO: add a flag to ignore change outputs (make them unspendable)
 #[derive(Debug, Default)]
 pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
     pub(crate) addressees: Vec<(Address, u64)>,
     pub(crate) send_all: bool,
-    pub(crate) fee_perkb: Option<f32>,
+    pub(crate) fee_rate: Option<FeeRate>,
     pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
     pub(crate) utxos: Option<Vec<OutPoint>>,
     pub(crate) unspendable: Option<Vec<OutPoint>>,
@@ -44,13 +45,8 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
         self
     }
 
-    pub fn fee_rate(mut self, satoshi_per_vbyte: f32) -> Self {
-        self.fee_perkb = Some(satoshi_per_vbyte * 1e3);
-        self
-    }
-
-    pub fn fee_rate_perkb(mut self, satoshi_per_kb: f32) -> Self {
-        self.fee_perkb = Some(satoshi_per_kb);
+    pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
+        self.fee_rate = Some(fee_rate);
         self
     }
 
@@ -93,7 +89,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
         TxBuilder {
             addressees: self.addressees,
             send_all: self.send_all,
-            fee_perkb: self.fee_perkb,
+            fee_rate: self.fee_rate,
             policy_path: self.policy_path,
             utxos: self.utxos,
             unspendable: self.unspendable,
index 0b969b4493b00941484d606a255ddd3478a90304..465036f0e24e9a680e9839806f63bd29d0c01bd4 100644 (file)
@@ -14,6 +14,34 @@ impl IsDust for u64 {
     }
 }
 
+#[derive(Debug, Copy, Clone)]
+// Internally stored as satoshi/vbyte
+pub struct FeeRate(f32);
+
+impl FeeRate {
+    pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self {
+        FeeRate(btc_per_kvb * 1e5)
+    }
+
+    pub fn from_sat_per_vb(sat_per_vb: f32) -> Self {
+        FeeRate(sat_per_vb)
+    }
+
+    pub fn default_min_relay_fee() -> Self {
+        FeeRate(1.0)
+    }
+
+    pub fn as_sat_vb(&self) -> f32 {
+        self.0
+    }
+}
+
+impl std::default::Default for FeeRate {
+    fn default() -> Self {
+        FeeRate::default_min_relay_fee()
+    }
+}
+
 pub struct ChunksIterator<I: Iterator> {
     iter: I,
     size: usize,
@@ -46,3 +74,26 @@ impl<I: Iterator> Iterator for ChunksIterator<I> {
         Some(v)
     }
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_fee_from_btc_per_kb() {
+        let fee = FeeRate::from_btc_per_kvb(1e-5);
+        assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+    }
+
+    #[test]
+    fn test_fee_from_sats_vbyte() {
+        let fee = FeeRate::from_sat_per_vb(1.0);
+        assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+    }
+
+    #[test]
+    fn test_fee_default_min_relay_fee() {
+        let fee = FeeRate::default_min_relay_fee();
+        assert!((fee.as_sat_vb() - 1.0).abs() < 0.0001);
+    }
+}