5
5
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6
6
// accordance with one or both of these licenses.
7
7
8
- use crate :: config:: TX_BROADCAST_TIMEOUT_SECS ;
8
+ use crate :: config:: { Config , FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS , TX_BROADCAST_TIMEOUT_SECS } ;
9
9
use 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
+ } ;
10
14
use crate :: logger:: { log_bytes, log_error, log_trace, LdkLogger , Logger } ;
11
15
12
16
use lightning:: chain:: { Filter , WatchedOutput } ;
@@ -17,10 +21,11 @@ use bdk_electrum::BdkElectrumClient;
17
21
18
22
use electrum_client:: Client as ElectrumClient ;
19
23
use electrum_client:: ConfigBuilder as ElectrumConfigBuilder ;
20
- use electrum_client:: ElectrumApi ;
24
+ use electrum_client:: { Batch , ElectrumApi } ;
21
25
22
- use bitcoin:: { Script , Transaction , Txid } ;
26
+ use bitcoin:: { FeeRate , Network , Script , Transaction , Txid } ;
23
27
28
+ use std:: collections:: HashMap ;
24
29
use std:: sync:: Arc ;
25
30
use std:: time:: Duration ;
26
31
@@ -32,12 +37,14 @@ pub(crate) struct ElectrumRuntimeClient {
32
37
bdk_electrum_client : Arc < BdkElectrumClient < ElectrumClient > > ,
33
38
tx_sync : Arc < ElectrumSyncClient < Arc < Logger > > > ,
34
39
runtime : Arc < tokio:: runtime:: Runtime > ,
40
+ config : Arc < Config > ,
35
41
logger : Arc < Logger > ,
36
42
}
37
43
38
44
impl ElectrumRuntimeClient {
39
45
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 > ,
41
48
) -> Result < Self , Error > {
42
49
let electrum_config = ElectrumConfigBuilder :: new ( )
43
50
. retry ( ELECTRUM_CLIENT_NUM_RETRIES )
@@ -62,7 +69,7 @@ impl ElectrumRuntimeClient {
62
69
Error :: ConnectionFailed
63
70
} ) ?,
64
71
) ;
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 } )
66
73
}
67
74
68
75
pub ( crate ) async fn broadcast ( & self , tx : Transaction ) {
@@ -106,6 +113,89 @@ impl ElectrumRuntimeClient {
106
113
} ,
107
114
}
108
115
}
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
+ }
109
199
}
110
200
111
201
impl Filter for ElectrumRuntimeClient {
0 commit comments