Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Move transaction broadcasting and fee estimation to dedicated modules by tnull · Pull Request #205 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,7 @@ enum NodeError {
"ChannelClosingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
"WalletOperationFailed",
"OnchainTxSigningFailed",
"MessageSigningFailed",
Expand Down
43 changes: 32 additions & 11 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
use crate::event::EventQueue;
use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
OnionMessenger, PeerManager,
Expand DownExpand Up@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
BuildError::WalletSetupFailed
})?;

let (blockchain, tx_sync) = match chain_data_source_config {
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora(server_url)) => {
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
None => {
// Default to Esplora client.
Expand All@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
let blockchain =
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
.with_concurrency(BDK_CLIENT_CONCURRENCY);
(blockchain, tx_sync)
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
tx_sync.client().clone(),
Arc::clone(&logger),
));
let fee_estimator =
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
}
};

let runtime = Arc::new(RwLock::new(None));
let wallet =
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
let wallet = Arc::new(Wallet::new(
blockchain,
bdk_wallet,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&logger),
));

// Initialize the ChainMonitor
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&logger),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&kv_store),
));

Expand DownExpand Up@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&keys_manager),
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
user_config,
Expand All@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
best_block: BestBlock::new(genesis_block_hash, 0),
};
channelmanager::ChannelManager::new(
Arc::clone(&wallet),
Arc::clone(&fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&wallet),
Arc::clone(&tx_broadcaster),
Arc::clone(&router),
Arc::clone(&logger),
Arc::clone(&keys_manager),
Expand DownExpand Up@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config,
wallet,
tx_sync,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,8 @@ pub enum Error {
ChannelConfigUpdateFailed,
/// Persistence failed.
PersistenceFailed,
/// A fee rate estimation update failed.
FeerateEstimationUpdateFailed,
/// A wallet operation failed.
WalletOperationFailed,
/// A signing operation for transaction failed.
Expand DownExpand Up@@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
write!(f, "Failed to update fee rate estimates.")
}
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),
Expand Down
34 changes: 22 additions & 12 deletions src/event.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
use crate::peer_store::{PeerInfo, PeerStore};
use crate::types::{Broadcaster, FeeEstimator, Wallet};
use crate::{
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
UserChannelId,
};

use crate::payment_store::{
Expand All@@ -13,7 +14,9 @@ use crate::io::{
};
use crate::logger::{log_debug, log_error, log_info, Logger};

use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chaininterface::{
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
};
use lightning::events::Event as LdkEvent;
use lightning::events::PaymentPurpose;
use lightning::impl_writeable_tlv_based_enum;
Expand DownExpand Up@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
where
L::Target: Logger,
{
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
event_queue: Arc<EventQueue<K, L>>,
wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>,
tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>,
network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L,
config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
}

impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
where
L::Target: Logger,
{
pub fn new(
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
peer_store: Arc<PeerStore<K, L>>,
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
logger: L, config: Arc<Config>,
) -> Self {
Self {
event_queue,
wallet,
channel_manager,
tx_broadcaster,
fee_estimator,
network_graph,
keys_manager,
payment_store,
peer_store,
logger,
runtime,
config,
peer_store,
}
}

Expand DownExpand Up@@ -586,7 +594,7 @@ where

let output_descriptors = &outputs.iter().collect::<Vec<_>>();
let tx_feerate = self
.wallet
.fee_estimator
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);

// We set nLockTime to the current height to discourage fee sniping.
Expand All@@ -603,7 +611,9 @@ where
);

match res {
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
Ok(Some(spending_tx)) => {
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
}
Ok(None) => {
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
}
Expand Down
131 changes: 131 additions & 0 deletions src/fee_estimator.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
use crate::logger::{log_error, log_trace, Logger};
use crate::Error;

use lightning::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
};

use bdk::FeeRate;
use esplora_client::AsyncClient as EsploraClient;

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::RwLock;

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

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

pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
let confirmation_targets = vec![
ConfirmationTarget::OnChainSweep,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
ConfirmationTarget::AnchorChannelFee,
ConfirmationTarget::NonAnchorChannelFee,
ConfirmationTarget::ChannelCloseMinimum,
];
for target in confirmation_targets {
let num_blocks = match target {
ConfirmationTarget::OnChainSweep => 6,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
ConfirmationTarget::AnchorChannelFee => 1008,
ConfirmationTarget::NonAnchorChannelFee => 12,
ConfirmationTarget::ChannelCloseMinimum => 144,
};

let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
log_error!(
self.logger,
"Failed to retrieve fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
.map_err(|e| {
log_error!(
self.logger,
"Failed to convert fee rate estimates for {:?}: {}",
target,
e
);
Error::FeerateEstimationUpdateFailed
})?;

let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);

// 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 = match target {
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
FeeRate::from_sat_per_vb(really_high_prio)
}
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
}
_ => fee_rate,
};

let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
log_trace!(
self.logger,
"Fee rate estimation updated for {:?}: {} sats/kwu",
target,
adjusted_fee_rate.fee_wu(1000)
);
}
Ok(())
}

pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();

let fallback_sats_kwu = match confirmation_target {
ConfirmationTarget::OnChainSweep => 5000,
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
ConfirmationTarget::AnchorChannelFee => 500,
ConfirmationTarget::NonAnchorChannelFee => 1000,
ConfirmationTarget::ChannelCloseMinimum => 500,
};

// We'll fall back on this, if we really don't have any other information.
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);

*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
}
}

impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
where
L::Target: Logger,
{
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
.max(FEERATE_FLOOR_SATS_PER_KW)
}
}
Loading