Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 81 additions & 84 deletions src/chain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ impl ElectrumChainSource {
self.server_url.clone(),
self.sync_config.clone(),
Arc::clone(&runtime),
Arc::clone(&self.config),
Arc::clone(&self.logger),
)
}
Expand Down Expand Up @@ -301,7 +300,14 @@ impl ElectrumChainSource {

let now = Instant::now();

let new_fee_rate_cache = electrum_client.get_fee_rate_cache_update().await?;
let new_fee_rate_cache = get_electrum_fee_rate_cache_update(
Arc::clone(&electrum_client.runtime),
Arc::clone(&electrum_client.electrum_client),
self.config.network,
self.sync_config.timeouts_config.fee_rate_cache_update_timeout_secs,
Arc::clone(&self.logger),
)
.await?;
self.fee_estimator.set_fee_rate_cache(new_fee_rate_cache);

log_debug!(
Expand Down Expand Up @@ -423,15 +429,15 @@ impl ElectrumRuntimeStatus {

pub(super) fn start(
&mut self, server_url: String, sync_config: ElectrumSyncConfig, runtime: Arc<Runtime>,
config: Arc<Config>, logger: Arc<Logger>,
logger: Arc<Logger>,
) -> Result<(), Error> {
if self.client.is_some() {
debug_assert!(false, "We shouldn't call start if we're already started");
return Ok(());
}

let client =
Arc::new(ElectrumRuntimeClient::new(server_url, sync_config, runtime, config, logger)?);
Arc::new(ElectrumRuntimeClient::new(server_url, sync_config, runtime, logger)?);

// (Re-)apply all known `Filter` entries to the fresh client.
for (txid, script_pubkey) in self.registered_txs.iter() {
Expand Down Expand Up @@ -480,15 +486,14 @@ struct ElectrumRuntimeClient {
bdk_electrum_client: Arc<BdkElectrumClient<Arc<ElectrumClient>>>,
tx_sync: Arc<ElectrumSyncClient<Arc<Logger>>>,
runtime: Arc<Runtime>,
config: Arc<Config>,
logger: Arc<Logger>,
confirm_gate: Arc<ConfirmGate>,
}

impl ElectrumRuntimeClient {
fn new(
server_url: String, sync_config: ElectrumSyncConfig, runtime: Arc<Runtime>,
config: Arc<Config>, logger: Arc<Logger>,
logger: Arc<Logger>,
) -> Result<Self, Error> {
let electrum_config = ElectrumConfigBuilder::new()
.retry(ELECTRUM_CLIENT_NUM_RETRIES)
Expand Down Expand Up @@ -517,7 +522,6 @@ impl ElectrumRuntimeClient {
bdk_electrum_client,
tx_sync,
runtime,
config,
logger,
confirm_gate: Arc::new(ConfirmGate::new()),
})
Expand Down Expand Up @@ -738,91 +742,84 @@ impl ElectrumRuntimeClient {
Err(e) => self.log_broadcast_error(e, &txids, &package),
}
}
}

async fn get_fee_rate_cache_update(
&self,
) -> Result<HashMap<ConfirmationTarget, FeeRate>, Error> {
let electrum_client = Arc::clone(&self.electrum_client);

let mut batch = Batch::default();
let confirmation_targets = get_all_conf_targets();
for target in confirmation_targets {
let num_blocks = get_num_block_defaults_for_target(target);
batch.estimate_fee(num_blocks, None);
}

let spawn_fut = self.runtime.spawn_blocking(move || electrum_client.batch_call(&batch));
pub(crate) async fn get_electrum_fee_rate_cache_update(
runtime: Arc<Runtime>, electrum_client: Arc<ElectrumClient>, network: Network,
fee_rate_cache_update_timeout_secs: u64, logger: Arc<Logger>,
) -> Result<HashMap<ConfirmationTarget, FeeRate>, Error> {
let mut batch = Batch::default();
let confirmation_targets = get_all_conf_targets();
for target in confirmation_targets {
let num_blocks = get_num_block_defaults_for_target(target);
batch.estimate_fee(num_blocks, None);
}

let timeout_fut = tokio::time::timeout(
Duration::from_secs(
self.sync_config.timeouts_config.fee_rate_cache_update_timeout_secs,
),
spawn_fut,
let spawn_fut = runtime.spawn_blocking(move || electrum_client.batch_call(&batch));

let timeout_fut =
tokio::time::timeout(Duration::from_secs(fee_rate_cache_update_timeout_secs), spawn_fut);

let raw_estimates_btc_kvb = timeout_fut
.await
.map_err(|e| {
log_error!(logger, "Updating fee rate estimates timed out: {}", e);
Error::FeerateEstimationUpdateTimeout
})?
.map_err(|e| {
log_error!(logger, "Failed to retrieve fee rate estimates: {}", e);
Error::FeerateEstimationUpdateFailed
})?
.map_err(|e| {
log_error!(logger, "Failed to retrieve fee rate estimates: {}", e);
Error::FeerateEstimationUpdateFailed
})?;

if raw_estimates_btc_kvb.len() != confirmation_targets.len() && network == Network::Bitcoin {
// Ensure we fail if we didn't receive all estimates.
debug_assert!(
false,
"Electrum server didn't return all expected results. This is disallowed on Mainnet."
);

let raw_estimates_btc_kvb = timeout_fut
.await
.map_err(|e| {
log_error!(self.logger, "Updating fee rate estimates timed out: {}", e);
Error::FeerateEstimationUpdateTimeout
})?
.map_err(|e| {
log_error!(self.logger, "Failed to retrieve fee rate estimates: {}", e);
Error::FeerateEstimationUpdateFailed
})?
.map_err(|e| {
log_error!(self.logger, "Failed to retrieve fee rate estimates: {}", e);
Error::FeerateEstimationUpdateFailed
})?;

if raw_estimates_btc_kvb.len() != confirmation_targets.len()
&& self.config.network == Network::Bitcoin
{
// Ensure we fail if we didn't receive all estimates.
debug_assert!(false,
"Electrum server didn't return all expected results. This is disallowed on Mainnet."
);
log_error!(self.logger,
log_error!(logger,
"Failed to retrieve fee rate estimates: Electrum server didn't return all expected results. This is disallowed on Mainnet."
);
return Err(Error::FeerateEstimationUpdateFailed);
}
return Err(Error::FeerateEstimationUpdateFailed);
}

let mut new_fee_rate_cache = HashMap::with_capacity(10);
for (target, raw_fee_rate_btc_per_kvb) in
confirmation_targets.into_iter().zip(raw_estimates_btc_kvb.into_iter())
{
// Parse the retrieved serde_json::Value and fall back to 1 sat/vb (10^3 / 10^8 = 10^-5
// = 0.00001 btc/kvb) if we fail or it yields less than that. This is mostly necessary
// to continue on `signet`/`regtest` where we might not get estimates (or bogus
// values).
let fee_rate_btc_per_kvb = raw_fee_rate_btc_per_kvb
.as_f64()
.map_or(0.00001, |converted| converted.max(0.00001));

// Electrum, just like Bitcoin Core, gives us a feerate in BTC/KvB.
// Thus, we multiply by 25_000_000 (10^8 / 4) to get satoshis/kwu.
let fee_rate = {
let fee_rate_sat_per_kwu = (fee_rate_btc_per_kvb * 25_000_000.0).round() as u64;
FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu)
};

// LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
// require some post-estimation adjustments to the fee rates, which we do here.
let adjusted_fee_rate = apply_post_estimation_adjustments(target, fee_rate);

new_fee_rate_cache.insert(target, adjusted_fee_rate);

log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.to_sat_per_kwu(),
);
}
let mut new_fee_rate_cache = HashMap::with_capacity(10);
for (target, raw_fee_rate_btc_per_kvb) in
confirmation_targets.into_iter().zip(raw_estimates_btc_kvb.into_iter())
{
// Parse the retrieved serde_json::Value and fall back to 1 sat/vb (10^3 / 10^8 = 10^-5
// = 0.00001 btc/kvb) if we fail or it yields less than that. This is mostly necessary
// to continue on `signet`/`regtest` where we might not get estimates (or bogus
// values).
let fee_rate_btc_per_kvb =
raw_fee_rate_btc_per_kvb.as_f64().map_or(0.00001, |converted| converted.max(0.00001));

// Electrum, just like Bitcoin Core, gives us a feerate in BTC/KvB.
// Thus, we multiply by 25_000_000 (10^8 / 4) to get satoshis/kwu.
let fee_rate = {
let fee_rate_sat_per_kwu = (fee_rate_btc_per_kvb * 25_000_000.0).round() as u64;
FeeRate::from_sat_per_kwu(fee_rate_sat_per_kwu)
};

// LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
// require some post-estimation adjustments to the fee rates, which we do here.
let adjusted_fee_rate = apply_post_estimation_adjustments(target, fee_rate);

Ok(new_fee_rate_cache)
new_fee_rate_cache.insert(target, adjusted_fee_rate);

log_trace!(
logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.to_sat_per_kwu(),
);
}

Ok(new_fee_rate_cache)
}

struct ConfirmGate {
Expand Down
Loading