Skip to content

Commit a6b6d8d

Browse files
authored
Merge pull request #205 from tnull/2023-11-rip-wallet-apart
Move transaction broadcasting and fee estimation to dedicated modules
2 parents d46efb6 + ea31d2d commit a6b6d8d

File tree

10 files changed

+438
-223
lines changed

10 files changed

+438
-223
lines changed

bindings/ldk_node.udl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ enum NodeError {
108108
"ChannelClosingFailed",
109109
"ChannelConfigUpdateFailed",
110110
"PersistenceFailed",
111+
"FeerateEstimationUpdateFailed",
111112
"WalletOperationFailed",
112113
"OnchainTxSigningFailed",
113114
"MessageSigningFailed",

src/builder.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::event::EventQueue;
2+
use crate::fee_estimator::OnchainFeeEstimator;
23
use crate::gossip::GossipSource;
34
use crate::io;
45
use crate::io::sqlite_store::SqliteStore;
56
use crate::logger::{log_error, FilesystemLogger, Logger};
67
use crate::payment_store::PaymentStore;
78
use crate::peer_store::PeerStore;
9+
use crate::tx_broadcaster::TransactionBroadcaster;
810
use crate::types::{
911
ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetworkGraph,
1012
OnionMessenger, PeerManager,
@@ -464,13 +466,19 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
464466
BuildError::WalletSetupFailed
465467
})?;
466468

467-
let (blockchain, tx_sync) = match chain_data_source_config {
469+
let (blockchain, tx_sync, tx_broadcaster, fee_estimator) = match chain_data_source_config {
468470
Some(ChainDataSourceConfig::Esplora(server_url)) => {
469471
let tx_sync = Arc::new(EsploraSyncClient::new(server_url.clone(), Arc::clone(&logger)));
470472
let blockchain =
471473
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
472474
.with_concurrency(BDK_CLIENT_CONCURRENCY);
473-
(blockchain, tx_sync)
475+
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
476+
tx_sync.client().clone(),
477+
Arc::clone(&logger),
478+
));
479+
let fee_estimator =
480+
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
481+
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
474482
}
475483
None => {
476484
// Default to Esplora client.
@@ -479,20 +487,31 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
479487
let blockchain =
480488
EsploraBlockchain::from_client(tx_sync.client().clone(), BDK_CLIENT_STOP_GAP)
481489
.with_concurrency(BDK_CLIENT_CONCURRENCY);
482-
(blockchain, tx_sync)
490+
let tx_broadcaster = Arc::new(TransactionBroadcaster::new(
491+
tx_sync.client().clone(),
492+
Arc::clone(&logger),
493+
));
494+
let fee_estimator =
495+
Arc::new(OnchainFeeEstimator::new(tx_sync.client().clone(), Arc::clone(&logger)));
496+
(blockchain, tx_sync, tx_broadcaster, fee_estimator)
483497
}
484498
};
485499

486500
let runtime = Arc::new(RwLock::new(None));
487-
let wallet =
488-
Arc::new(Wallet::new(blockchain, bdk_wallet, Arc::clone(&runtime), Arc::clone(&logger)));
501+
let wallet = Arc::new(Wallet::new(
502+
blockchain,
503+
bdk_wallet,
504+
Arc::clone(&tx_broadcaster),
505+
Arc::clone(&fee_estimator),
506+
Arc::clone(&logger),
507+
));
489508

490509
// Initialize the ChainMonitor
491510
let chain_monitor: Arc<ChainMonitor<K>> = Arc::new(chainmonitor::ChainMonitor::new(
492511
Some(Arc::clone(&tx_sync)),
493-
Arc::clone(&wallet),
512+
Arc::clone(&tx_broadcaster),
494513
Arc::clone(&logger),
495-
Arc::clone(&wallet),
514+
Arc::clone(&fee_estimator),
496515
Arc::clone(&kv_store),
497516
));
498517

@@ -592,9 +611,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
592611
Arc::clone(&keys_manager),
593612
Arc::clone(&keys_manager),
594613
Arc::clone(&keys_manager),
595-
Arc::clone(&wallet),
614+
Arc::clone(&fee_estimator),
596615
Arc::clone(&chain_monitor),
597-
Arc::clone(&wallet),
616+
Arc::clone(&tx_broadcaster),
598617
Arc::clone(&router),
599618
Arc::clone(&logger),
600619
user_config,
@@ -616,9 +635,9 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
616635
best_block: BestBlock::new(genesis_block_hash, 0),
617636
};
618637
channelmanager::ChannelManager::new(
619-
Arc::clone(&wallet),
638+
Arc::clone(&fee_estimator),
620639
Arc::clone(&chain_monitor),
621-
Arc::clone(&wallet),
640+
Arc::clone(&tx_broadcaster),
622641
Arc::clone(&router),
623642
Arc::clone(&logger),
624643
Arc::clone(&keys_manager),
@@ -767,6 +786,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
767786
config,
768787
wallet,
769788
tx_sync,
789+
tx_broadcaster,
790+
fee_estimator,
770791
event_queue,
771792
channel_manager,
772793
chain_monitor,

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub enum Error {
2525
ChannelConfigUpdateFailed,
2626
/// Persistence failed.
2727
PersistenceFailed,
28+
/// A fee rate estimation update failed.
29+
FeerateEstimationUpdateFailed,
2830
/// A wallet operation failed.
2931
WalletOperationFailed,
3032
/// A signing operation for transaction failed.
@@ -79,6 +81,9 @@ impl fmt::Display for Error {
7981
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
8082
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
8183
Self::PersistenceFailed => write!(f, "Failed to persist data."),
84+
Self::FeerateEstimationUpdateFailed => {
85+
write!(f, "Failed to update fee rate estimates.")
86+
}
8287
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
8388
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
8489
Self::MessageSigningFailed => write!(f, "Failed to sign given message."),

src/event.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::peer_store::{PeerInfo, PeerStore};
1+
use crate::types::{Broadcaster, FeeEstimator, Wallet};
22
use crate::{
3-
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, UserChannelId, Wallet,
3+
hex_utils, ChannelManager, Config, Error, KeysManager, NetworkGraph, PeerInfo, PeerStore,
4+
UserChannelId,
45
};
56

67
use crate::payment_store::{
@@ -13,7 +14,9 @@ use crate::io::{
1314
};
1415
use crate::logger::{log_debug, log_error, log_info, Logger};
1516

16-
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
17+
use lightning::chain::chaininterface::{
18+
BroadcasterInterface, ConfirmationTarget, FeeEstimator as LDKFeeEstimator,
19+
};
1720
use lightning::events::Event as LdkEvent;
1821
use lightning::events::PaymentPurpose;
1922
use lightning::impl_writeable_tlv_based_enum;
@@ -243,40 +246,45 @@ pub(crate) struct EventHandler<K: KVStore + Sync + Send, L: Deref>
243246
where
244247
L::Target: Logger,
245248
{
246-
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>,
247249
event_queue: Arc<EventQueue<K, L>>,
250+
wallet: Arc<Wallet>,
248251
channel_manager: Arc<ChannelManager<K>>,
252+
tx_broadcaster: Arc<Broadcaster>,
253+
fee_estimator: Arc<FeeEstimator>,
249254
network_graph: Arc<NetworkGraph>,
250255
keys_manager: Arc<KeysManager>,
251256
payment_store: Arc<PaymentStore<K, L>>,
257+
peer_store: Arc<PeerStore<K, L>>,
252258
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
253259
logger: L,
254260
config: Arc<Config>,
255-
peer_store: Arc<PeerStore<K, L>>,
256261
}
257262

258263
impl<K: KVStore + Sync + Send + 'static, L: Deref> EventHandler<K, L>
259264
where
260265
L::Target: Logger,
261266
{
262267
pub fn new(
263-
wallet: Arc<Wallet<bdk::database::SqliteDatabase, L>>, event_queue: Arc<EventQueue<K, L>>,
264-
channel_manager: Arc<ChannelManager<K>>, network_graph: Arc<NetworkGraph>,
268+
event_queue: Arc<EventQueue<K, L>>, wallet: Arc<Wallet>,
269+
channel_manager: Arc<ChannelManager<K>>, tx_broadcaster: Arc<Broadcaster>,
270+
fee_estimator: Arc<FeeEstimator>, network_graph: Arc<NetworkGraph>,
265271
keys_manager: Arc<KeysManager>, payment_store: Arc<PaymentStore<K, L>>,
266-
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
267-
peer_store: Arc<PeerStore<K, L>>,
272+
peer_store: Arc<PeerStore<K, L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
273+
logger: L, config: Arc<Config>,
268274
) -> Self {
269275
Self {
270276
event_queue,
271277
wallet,
272278
channel_manager,
279+
tx_broadcaster,
280+
fee_estimator,
273281
network_graph,
274282
keys_manager,
275283
payment_store,
284+
peer_store,
276285
logger,
277286
runtime,
278287
config,
279-
peer_store,
280288
}
281289
}
282290

@@ -586,7 +594,7 @@ where
586594

587595
let output_descriptors = &outputs.iter().collect::<Vec<_>>();
588596
let tx_feerate = self
589-
.wallet
597+
.fee_estimator
590598
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
591599

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

605613
match res {
606-
Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]),
614+
Ok(Some(spending_tx)) => {
615+
self.tx_broadcaster.broadcast_transactions(&[&spending_tx])
616+
}
607617
Ok(None) => {
608618
log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs);
609619
}

src/fee_estimator.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use crate::logger::{log_error, log_trace, Logger};
2+
use crate::Error;
3+
4+
use lightning::chain::chaininterface::{
5+
ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW,
6+
};
7+
8+
use bdk::FeeRate;
9+
use esplora_client::AsyncClient as EsploraClient;
10+
11+
use std::collections::HashMap;
12+
use std::ops::Deref;
13+
use std::sync::RwLock;
14+
15+
pub(crate) struct OnchainFeeEstimator<L: Deref>
16+
where
17+
L::Target: Logger,
18+
{
19+
fee_rate_cache: RwLock<HashMap<ConfirmationTarget, FeeRate>>,
20+
esplora_client: EsploraClient,
21+
logger: L,
22+
}
23+
24+
impl<L: Deref> OnchainFeeEstimator<L>
25+
where
26+
L::Target: Logger,
27+
{
28+
pub(crate) fn new(esplora_client: EsploraClient, logger: L) -> Self {
29+
let fee_rate_cache = RwLock::new(HashMap::new());
30+
Self { fee_rate_cache, esplora_client, logger }
31+
}
32+
33+
pub(crate) async fn update_fee_estimates(&self) -> Result<(), Error> {
34+
let confirmation_targets = vec![
35+
ConfirmationTarget::OnChainSweep,
36+
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee,
37+
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee,
38+
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee,
39+
ConfirmationTarget::AnchorChannelFee,
40+
ConfirmationTarget::NonAnchorChannelFee,
41+
ConfirmationTarget::ChannelCloseMinimum,
42+
];
43+
for target in confirmation_targets {
44+
let num_blocks = match target {
45+
ConfirmationTarget::OnChainSweep => 6,
46+
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 1,
47+
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => 1008,
48+
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 144,
49+
ConfirmationTarget::AnchorChannelFee => 1008,
50+
ConfirmationTarget::NonAnchorChannelFee => 12,
51+
ConfirmationTarget::ChannelCloseMinimum => 144,
52+
};
53+
54+
let estimates = self.esplora_client.get_fee_estimates().await.map_err(|e| {
55+
log_error!(
56+
self.logger,
57+
"Failed to retrieve fee rate estimates for {:?}: {}",
58+
target,
59+
e
60+
);
61+
Error::FeerateEstimationUpdateFailed
62+
})?;
63+
64+
let converted_estimates = esplora_client::convert_fee_rate(num_blocks, estimates)
65+
.map_err(|e| {
66+
log_error!(
67+
self.logger,
68+
"Failed to convert fee rate estimates for {:?}: {}",
69+
target,
70+
e
71+
);
72+
Error::FeerateEstimationUpdateFailed
73+
})?;
74+
75+
let fee_rate = FeeRate::from_sat_per_vb(converted_estimates);
76+
77+
// LDK 0.0.118 introduced changes to the `ConfirmationTarget` semantics that
78+
// require some post-estimation adjustments to the fee rates, which we do here.
79+
let adjusted_fee_rate = match target {
80+
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => {
81+
let really_high_prio = fee_rate.as_sat_per_vb() * 10.0;
82+
FeeRate::from_sat_per_vb(really_high_prio)
83+
}
84+
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => {
85+
let slightly_less_than_background = fee_rate.fee_wu(1000) - 250;
86+
FeeRate::from_sat_per_kwu(slightly_less_than_background as f32)
87+
}
88+
_ => fee_rate,
89+
};
90+
91+
let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap();
92+
locked_fee_rate_cache.insert(target, adjusted_fee_rate);
93+
log_trace!(
94+
self.logger,
95+
"Fee rate estimation updated for {:?}: {} sats/kwu",
96+
target,
97+
adjusted_fee_rate.fee_wu(1000)
98+
);
99+
}
100+
Ok(())
101+
}
102+
103+
pub(crate) fn estimate_fee_rate(&self, confirmation_target: ConfirmationTarget) -> FeeRate {
104+
let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap();
105+
106+
let fallback_sats_kwu = match confirmation_target {
107+
ConfirmationTarget::OnChainSweep => 5000,
108+
ConfirmationTarget::MaxAllowedNonAnchorChannelRemoteFee => 25 * 250,
109+
ConfirmationTarget::MinAllowedAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
110+
ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => FEERATE_FLOOR_SATS_PER_KW,
111+
ConfirmationTarget::AnchorChannelFee => 500,
112+
ConfirmationTarget::NonAnchorChannelFee => 1000,
113+
ConfirmationTarget::ChannelCloseMinimum => 500,
114+
};
115+
116+
// We'll fall back on this, if we really don't have any other information.
117+
let fallback_rate = FeeRate::from_sat_per_kwu(fallback_sats_kwu as f32);
118+
119+
*locked_fee_rate_cache.get(&confirmation_target).unwrap_or(&fallback_rate)
120+
}
121+
}
122+
123+
impl<L: Deref> FeeEstimator for OnchainFeeEstimator<L>
124+
where
125+
L::Target: Logger,
126+
{
127+
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
128+
(self.estimate_fee_rate(confirmation_target).fee_wu(1000) as u32)
129+
.max(FEERATE_FLOOR_SATS_PER_KW)
130+
}
131+
}

0 commit comments

Comments
 (0)