Skip to content

Commit

Permalink
Disallow empty fee estimates on Mainnet
Browse files Browse the repository at this point in the history
We generally want to properly be able to detect whenever a fee estimation would
fail, as we need to fail startup if we can't retrieve the latest fee
rates.

However, currently the `convert_fee_estimates` function would fallback
to a default of 1sat/vbyte if the retrieved estimate map is empty. This
is fine/potentially needed for, e.g., Signet where Esplora's
`fee-estimates` endpoint would return an empty dictionary.

However, we need to detect the empty map and fail our fee estimation if
we encounter such a case, rather than using the 1 sat/vbyte fallback on
mainnet, where differences in fee estimation could lead to costly
force-closures. Here we do this and just return a
`FeerateEstimationFailed` if we hit such a case.
  • Loading branch information
tnull committed Feb 10, 2024
1 parent 6fef493 commit 20742f4
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 8 deletions.
14 changes: 10 additions & 4 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,11 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
let fee_estimator = Arc::new(OnchainFeeEstimator::new(
tx_sync.client().clone(),
Arc::clone(&config),
Arc::clone(&logger),
));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
Expand All @@ -523,8 +526,11 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
let fee_estimator = Arc::new(OnchainFeeEstimator::new(
tx_sync.client().clone(),
Arc::clone(&config),
Arc::clone(&logger),
));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};
Expand Down
20 changes: 16 additions & 4 deletions src/fee_estimator.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;
use crate::{Config, Error};

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
Expand All @@ -8,28 +8,30 @@ use lightning::chain::chaininterface::{
use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use bitcoin::Network;
use bitcoin::blockdata::weight::Weight;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;
use std::sync::{Arc, RwLock};

pub(crate) struct OnchainFeeEstimator<L: Deref>
where
L::Target: Logger,
{
fee_rate_cache: RwLock<HashMap<ConfirmationTarget, FeeRate>>,
esplora_client: EsploraClient,
config: Arc<Config>,
logger: L,
}

impl<L: Deref> OnchainFeeEstimator<L>
where
L::Target: Logger,
{
pub(crate) fn new(esplora_client: EsploraClient, logger: L) -> Self {
pub(crate) fn new(esplora_client: EsploraClient, config: Arc<Config>, logger: L) -> Self {
let fee_rate_cache = RwLock::new(HashMap::new());
Self { fee_rate_cache, esplora_client, logger }
Self { fee_rate_cache, esplora_client, config, logger }
}

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
Expand Down Expand Up @@ -61,6 +63,16 @@ where
Error::FeerateEstimationUpdateFailed
})?;

if estimates.is_empty() && self.config.network == Network::Bitcoin {
// Ensure we fail if we didn't receive any estimates.
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: empty fee estimates are dissallowed on Mainnet.",
target,
);
return Err(Error::FeerateEstimationUpdateFailed);
}

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
Expand Down

0 comments on commit 20742f4

Please sign in to comment.