55// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66// accordance with one or both of these licenses.
77
8- use crate :: config:: TX_BROADCAST_TIMEOUT_SECS ;
8+ use crate :: config:: { Config , FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS , TX_BROADCAST_TIMEOUT_SECS } ;
99use crate :: error:: Error ;
10+ use crate :: fee_estimator:: {
11+ apply_post_estimation_adjustments, get_all_conf_targets, get_num_block_defaults_for_target,
12+ ConfirmationTarget ,
13+ } ;
1014use crate :: logger:: { log_bytes, log_error, log_trace, LdkLogger , Logger } ;
1115
1216use lightning:: chain:: { Filter , WatchedOutput } ;
@@ -17,10 +21,11 @@ use bdk_electrum::BdkElectrumClient;
1721
1822use electrum_client:: Client as ElectrumClient ;
1923use electrum_client:: ConfigBuilder as ElectrumConfigBuilder ;
20- use electrum_client:: ElectrumApi ;
24+ use electrum_client:: { Batch , ElectrumApi } ;
2125
22- use bitcoin:: { Script , Transaction , Txid } ;
26+ use bitcoin:: { FeeRate , Network , Script , Transaction , Txid } ;
2327
28+ use std:: collections:: HashMap ;
2429use std:: sync:: Arc ;
2530use std:: time:: Duration ;
2631
@@ -32,12 +37,14 @@ pub(crate) struct ElectrumRuntimeClient {
3237 bdk_electrum_client : Arc < BdkElectrumClient < ElectrumClient > > ,
3338 tx_sync : Arc < ElectrumSyncClient < Arc < Logger > > > ,
3439 runtime : Arc < tokio:: runtime:: Runtime > ,
40+ config : Arc < Config > ,
3541 logger : Arc < Logger > ,
3642}
3743
3844impl ElectrumRuntimeClient {
3945 pub ( crate ) fn new (
40- server_url : String , runtime : Arc < tokio:: runtime:: Runtime > , logger : Arc < Logger > ,
46+ server_url : String , runtime : Arc < tokio:: runtime:: Runtime > , config : Arc < Config > ,
47+ logger : Arc < Logger > ,
4148 ) -> Result < Self , Error > {
4249 let electrum_config = ElectrumConfigBuilder :: new ( )
4350 . retry ( ELECTRUM_CLIENT_NUM_RETRIES )
@@ -62,7 +69,7 @@ impl ElectrumRuntimeClient {
6269 Error :: ConnectionFailed
6370 } ) ?,
6471 ) ;
65- Ok ( Self { electrum_client, bdk_electrum_client, tx_sync, runtime, logger } )
72+ Ok ( Self { electrum_client, bdk_electrum_client, tx_sync, runtime, config , logger } )
6673 }
6774
6875 pub ( crate ) async fn broadcast ( & self , tx : Transaction ) {
@@ -106,6 +113,89 @@ impl ElectrumRuntimeClient {
106113 } ,
107114 }
108115 }
116+
117+ pub ( crate ) async fn get_fee_rate_cache_update (
118+ & self ,
119+ ) -> Result < HashMap < ConfirmationTarget , FeeRate > , Error > {
120+ let electrum_client = Arc :: clone ( & self . electrum_client ) ;
121+
122+ let mut batch = Batch :: default ( ) ;
123+ let confirmation_targets = get_all_conf_targets ( ) ;
124+ for target in confirmation_targets {
125+ let num_blocks = get_num_block_defaults_for_target ( target) ;
126+ batch. estimate_fee ( num_blocks) ;
127+ }
128+
129+ let spawn_fut = self . runtime . spawn_blocking ( move || electrum_client. batch_call ( & batch) ) ;
130+
131+ let timeout_fut = tokio:: time:: timeout (
132+ Duration :: from_secs ( FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS ) ,
133+ spawn_fut,
134+ ) ;
135+
136+ let raw_estimates_btc_kvb = timeout_fut
137+ . await
138+ . map_err ( |e| {
139+ log_error ! ( self . logger, "Updating fee rate estimates timed out: {}" , e) ;
140+ Error :: FeerateEstimationUpdateTimeout
141+ } ) ?
142+ . map_err ( |e| {
143+ log_error ! ( self . logger, "Failed to retrieve fee rate estimates: {}" , e) ;
144+ Error :: FeerateEstimationUpdateFailed
145+ } ) ?
146+ . map_err ( |e| {
147+ log_error ! ( self . logger, "Failed to retrieve fee rate estimates: {}" , e) ;
148+ Error :: FeerateEstimationUpdateFailed
149+ } ) ?;
150+
151+ if raw_estimates_btc_kvb. len ( ) != confirmation_targets. len ( )
152+ && self . config . network == Network :: Bitcoin
153+ {
154+ // Ensure we fail if we didn't receive all estimates.
155+ debug_assert ! ( false ,
156+ "Electrum server didn't return all expected results. This is disallowed on Mainnet."
157+ ) ;
158+ log_error ! ( self . logger,
159+ "Failed to retrieve fee rate estimates: Electrum server didn't return all expected results. This is disallowed on Mainnet."
160+ ) ;
161+ return Err ( Error :: FeerateEstimationUpdateFailed ) ;
162+ }
163+
164+ let mut new_fee_rate_cache = HashMap :: with_capacity ( 10 ) ;
165+ for ( target, raw_fee_rate_btc_per_kvb) in
166+ confirmation_targets. into_iter ( ) . zip ( raw_estimates_btc_kvb. into_iter ( ) )
167+ {
168+ // Parse the retrieved serde_json::Value and fall back to 1 sat/vb (10^3 / 10^8 = 10^-5
169+ // = 0.00001 btc/kvb) if we fail or it yields less than that. This is mostly necessary
170+ // to continue on `signet`/`regtest` where we might not get estimates (or bogus
171+ // values).
172+ let fee_rate_btc_per_kvb = raw_fee_rate_btc_per_kvb
173+ . as_f64 ( )
174+ . map_or ( 0.00001 , |converted| converted. max ( 0.00001 ) ) ;
175+
176+ // Electrum, just like Bitcoin Core, gives us a feerate in BTC/KvB.
177+ // Thus, we multiply by 25_000_000 (10^8 / 4) to get satoshis/kwu.
178+ let fee_rate = {
179+ let fee_rate_sat_per_kwu = ( fee_rate_btc_per_kvb * 25_000_000.0 ) . round ( ) as u64 ;
180+ FeeRate :: from_sat_per_kwu ( fee_rate_sat_per_kwu)
181+ } ;
182+
183+ // LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
184+ // require some post-estimation adjustments to the fee rates, which we do here.
185+ let adjusted_fee_rate = apply_post_estimation_adjustments ( target, fee_rate) ;
186+
187+ new_fee_rate_cache. insert ( target, adjusted_fee_rate) ;
188+
189+ log_trace ! (
190+ self . logger,
191+ "Fee rate estimation updated for {:?}: {} sats/kwu" ,
192+ target,
193+ adjusted_fee_rate. to_sat_per_kwu( ) ,
194+ ) ;
195+ }
196+
197+ Ok ( new_fee_rate_cache)
198+ }
109199}
110200
111201impl Filter for ElectrumRuntimeClient {
0 commit comments