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 } ;
@@ -15,10 +19,11 @@ use lightning_transaction_sync::ElectrumSyncClient;
1519
1620use bdk_electrum:: BdkElectrumClient ;
1721
18- use electrum_client:: { Client as ElectrumClient , ElectrumApi } ;
22+ use electrum_client:: { Batch , Client as ElectrumClient , ElectrumApi } ;
1923
20- use bitcoin:: { Script , Transaction , Txid } ;
24+ use bitcoin:: { FeeRate , Network , Script , Transaction , Txid } ;
2125
26+ use std:: collections:: HashMap ;
2227use std:: sync:: Arc ;
2328use std:: time:: Duration ;
2429
@@ -27,12 +32,14 @@ pub(crate) struct ElectrumRuntimeClient {
2732 bdk_electrum_client : Arc < BdkElectrumClient < ElectrumClient > > ,
2833 tx_sync : Arc < ElectrumSyncClient < Arc < Logger > > > ,
2934 runtime : Arc < tokio:: runtime:: Runtime > ,
35+ config : Arc < Config > ,
3036 logger : Arc < Logger > ,
3137}
3238
3339impl ElectrumRuntimeClient {
3440 pub ( crate ) fn new (
35- server_url : String , runtime : Arc < tokio:: runtime:: Runtime > , logger : Arc < Logger > ,
41+ server_url : String , runtime : Arc < tokio:: runtime:: Runtime > , config : Arc < Config > ,
42+ logger : Arc < Logger > ,
3643 ) -> Result < Self , Error > {
3744 let electrum_client = Arc :: new ( ElectrumClient :: new ( & server_url) . map_err ( |e| {
3845 log_error ! ( logger, "Failed to connect to electrum server: {}" , e) ;
@@ -49,7 +56,7 @@ impl ElectrumRuntimeClient {
4956 Error :: ConnectionFailed
5057 } ) ?,
5158 ) ;
52- Ok ( Self { electrum_client, bdk_electrum_client, tx_sync, runtime, logger } )
59+ Ok ( Self { electrum_client, bdk_electrum_client, tx_sync, runtime, config , logger } )
5360 }
5461
5562 pub ( crate ) async fn broadcast ( & self , tx : Transaction ) {
@@ -93,6 +100,89 @@ impl ElectrumRuntimeClient {
93100 } ,
94101 }
95102 }
103+
104+ pub ( crate ) async fn get_fee_rate_cache_update (
105+ & self ,
106+ ) -> Result < HashMap < ConfirmationTarget , FeeRate > , Error > {
107+ let electrum_client = Arc :: clone ( & self . electrum_client ) ;
108+
109+ let mut batch = Batch :: default ( ) ;
110+ let confirmation_targets = get_all_conf_targets ( ) ;
111+ for target in confirmation_targets {
112+ let num_blocks = get_num_block_defaults_for_target ( target) ;
113+ batch. estimate_fee ( num_blocks) ;
114+ }
115+
116+ let spawn_fut = self . runtime . spawn_blocking ( move || electrum_client. batch_call ( & batch) ) ;
117+
118+ let timeout_fut = tokio:: time:: timeout (
119+ Duration :: from_secs ( FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS ) ,
120+ spawn_fut,
121+ ) ;
122+
123+ let raw_estimates_btc_kvb = timeout_fut
124+ . await
125+ . map_err ( |e| {
126+ log_error ! ( self . logger, "Updating fee rate estimates timed out: {}" , e) ;
127+ Error :: FeerateEstimationUpdateTimeout
128+ } ) ?
129+ . map_err ( |e| {
130+ log_error ! ( self . logger, "Failed to retrieve fee rate estimates: {}" , e) ;
131+ Error :: FeerateEstimationUpdateFailed
132+ } ) ?
133+ . map_err ( |e| {
134+ log_error ! ( self . logger, "Failed to retrieve fee rate estimates: {}" , e) ;
135+ Error :: FeerateEstimationUpdateFailed
136+ } ) ?;
137+
138+ if raw_estimates_btc_kvb. len ( ) != confirmation_targets. len ( )
139+ && self . config . network == Network :: Bitcoin
140+ {
141+ // Ensure we fail if we didn't receive all estimates.
142+ debug_assert ! ( false ,
143+ "Electrum server didn't return all expected results. This is disallowed on Mainnet."
144+ ) ;
145+ log_error ! ( self . logger,
146+ "Failed to retrieve fee rate estimates: Electrum server didn't return all expected results. This is disallowed on Mainnet."
147+ ) ;
148+ return Err ( Error :: FeerateEstimationUpdateFailed ) ;
149+ }
150+
151+ let mut new_fee_rate_cache = HashMap :: with_capacity ( 10 ) ;
152+ for ( target, raw_fee_rate_btc_per_kvb) in
153+ confirmation_targets. into_iter ( ) . zip ( raw_estimates_btc_kvb. into_iter ( ) )
154+ {
155+ // Parse the retrieved serde_json::Value and fall back to 1 sat/vb (10^3 / 10^8 = 10^-5
156+ // = 0.00001 btc/kvb) if we fail or it yields less than that. This is mostly necessary
157+ // to continue on `signet`/`regtest` where we might not get estimates (or bogus
158+ // values).
159+ let fee_rate_btc_per_kvb = raw_fee_rate_btc_per_kvb
160+ . as_f64 ( )
161+ . map_or ( 0.00001 , |converted| converted. max ( 0.00001 ) ) ;
162+
163+ // Electrum, just like Bitcoin Core, gives us a feerate in BTC/KvB.
164+ // Thus, we multiply by 25_000_000 (10^8 / 4) to get satoshis/kwu.
165+ let fee_rate = {
166+ let fee_rate_sat_per_kwu = ( fee_rate_btc_per_kvb * 25_000_000.0 ) . round ( ) as u64 ;
167+ FeeRate :: from_sat_per_kwu ( fee_rate_sat_per_kwu)
168+ } ;
169+
170+ // LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
171+ // require some post-estimation adjustments to the fee rates, which we do here.
172+ let adjusted_fee_rate = apply_post_estimation_adjustments ( target, fee_rate) ;
173+
174+ new_fee_rate_cache. insert ( target, adjusted_fee_rate) ;
175+
176+ log_trace ! (
177+ self . logger,
178+ "Fee rate estimation updated for {:?}: {} sats/kwu" ,
179+ target,
180+ adjusted_fee_rate. to_sat_per_kwu( ) ,
181+ ) ;
182+ }
183+
184+ Ok ( new_fee_rate_cache)
185+ }
96186}
97187
98188impl Filter for ElectrumRuntimeClient {
0 commit comments