Skip to content

Commit c4ca6d3

Browse files
committed
Fix: Insert all LDK-registered transaction outputs into wallet
Previously, we'd selectively insert the funding outputs into the onchain wallet to later allow calculating `fees_paid` when creating payment store entries (only for splicing mostly). However, this didn't always work, and we might for example end up with a missing funding output (and hence would fall back to `fees_paid: Some(0)`) if it was a counterparty-initiated channel and we synced via `bitcoind` RPC. Here, we fix this by tracking all LDK-registered `txids` in `ChainSource` and then in the `Wallet`'s `Listen` implementation insert all outputs of all registered transactions into the `Wallet`, ensuring we'd always have sufficient data for `calculate_fee` available. Thereby we also fix the `onchain_send_receive` test which previously failed when using `TestChainSource::Bitcoind`. Signed-off-by: Elias Rohrer <dev@tnull.de>
1 parent 579f65b commit c4ca6d3

File tree

4 files changed

+41
-37
lines changed

4 files changed

+41
-37
lines changed

src/builder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,7 @@ fn build_with_store_internal(
12941294
wallet_persister,
12951295
Arc::clone(&tx_broadcaster),
12961296
Arc::clone(&fee_estimator),
1297+
Arc::clone(&chain_source),
12971298
Arc::clone(&payment_store),
12981299
Arc::clone(&config),
12991300
Arc::clone(&logger),

src/chain/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod electrum;
1010
mod esplora;
1111

1212
use std::collections::HashMap;
13-
use std::sync::{Arc, RwLock};
13+
use std::sync::{Arc, Mutex, RwLock};
1414
use std::time::Duration;
1515

1616
use bitcoin::{Script, Txid};
@@ -84,6 +84,7 @@ impl WalletSyncStatus {
8484

8585
pub(crate) struct ChainSource {
8686
kind: ChainSourceKind,
87+
registered_txids: Mutex<Vec<Txid>>,
8788
tx_broadcaster: Arc<Broadcaster>,
8889
logger: Arc<Logger>,
8990
}
@@ -112,7 +113,8 @@ impl ChainSource {
112113
node_metrics,
113114
);
114115
let kind = ChainSourceKind::Esplora(esplora_chain_source);
115-
(Self { kind, tx_broadcaster, logger }, None)
116+
let registered_txids = Mutex::new(Vec::new());
117+
(Self { kind, registered_txids, tx_broadcaster, logger }, None)
116118
}
117119

118120
pub(crate) fn new_electrum(
@@ -131,7 +133,8 @@ impl ChainSource {
131133
node_metrics,
132134
);
133135
let kind = ChainSourceKind::Electrum(electrum_chain_source);
134-
(Self { kind, tx_broadcaster, logger }, None)
136+
let registered_txids = Mutex::new(Vec::new());
137+
(Self { kind, registered_txids, tx_broadcaster, logger }, None)
135138
}
136139

137140
pub(crate) async fn new_bitcoind_rpc(
@@ -153,7 +156,8 @@ impl ChainSource {
153156
);
154157
let best_block = bitcoind_chain_source.poll_best_block().await.ok();
155158
let kind = ChainSourceKind::Bitcoind(bitcoind_chain_source);
156-
(Self { kind, tx_broadcaster, logger }, best_block)
159+
let registered_txids = Mutex::new(Vec::new());
160+
(Self { kind, registered_txids, tx_broadcaster, logger }, best_block)
157161
}
158162

159163
pub(crate) async fn new_bitcoind_rest(
@@ -176,7 +180,8 @@ impl ChainSource {
176180
);
177181
let best_block = bitcoind_chain_source.poll_best_block().await.ok();
178182
let kind = ChainSourceKind::Bitcoind(bitcoind_chain_source);
179-
(Self { kind, tx_broadcaster, logger }, best_block)
183+
let registered_txids = Mutex::new(Vec::new());
184+
(Self { kind, registered_txids, tx_broadcaster, logger }, best_block)
180185
}
181186

182187
pub(crate) fn start(&self, runtime: Arc<Runtime>) -> Result<(), Error> {
@@ -209,6 +214,10 @@ impl ChainSource {
209214
}
210215
}
211216

217+
pub(crate) fn registered_txids(&self) -> Vec<Txid> {
218+
self.registered_txids.lock().unwrap().clone()
219+
}
220+
212221
pub(crate) fn is_transaction_based(&self) -> bool {
213222
match &self.kind {
214223
ChainSourceKind::Esplora(_) => true,
@@ -463,6 +472,7 @@ impl ChainSource {
463472

464473
impl Filter for ChainSource {
465474
fn register_tx(&self, txid: &Txid, script_pubkey: &Script) {
475+
self.registered_txids.lock().unwrap().push(*txid);
466476
match &self.kind {
467477
ChainSourceKind::Esplora(esplora_chain_source) => {
468478
esplora_chain_source.register_tx(txid, script_pubkey)

src/lib.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,10 +1329,6 @@ impl Node {
13291329
Error::ChannelSplicingFailed
13301330
})?;
13311331

1332-
// insert channel's funding utxo into the wallet so we can later calculate fees
1333-
// correctly when viewing this splice-in.
1334-
self.wallet.insert_txo(funding_txo.into_bitcoin_outpoint(), funding_output)?;
1335-
13361332
let change_address = self.wallet.get_new_internal_address()?;
13371333

13381334
let contribution = SpliceContribution::splice_in(
@@ -1426,18 +1422,6 @@ impl Node {
14261422
},
14271423
};
14281424

1429-
let funding_txo = channel_details.funding_txo.ok_or_else(|| {
1430-
log_error!(self.logger, "Failed to splice channel: channel not yet ready",);
1431-
Error::ChannelSplicingFailed
1432-
})?;
1433-
1434-
let funding_output = channel_details.get_funding_output().ok_or_else(|| {
1435-
log_error!(self.logger, "Failed to splice channel: channel not yet ready");
1436-
Error::ChannelSplicingFailed
1437-
})?;
1438-
1439-
self.wallet.insert_txo(funding_txo.into_bitcoin_outpoint(), funding_output)?;
1440-
14411425
self.channel_manager
14421426
.splice_channel(
14431427
&channel_details.channel_id,

src/wallet/mod.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use crate::payment::{
5454
PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PendingPaymentDetails,
5555
};
5656
use crate::types::{Broadcaster, PaymentStore, PendingPaymentStore};
57-
use crate::Error;
57+
use crate::{ChainSource, Error};
5858

5959
pub(crate) enum OnchainSendAmount {
6060
ExactRetainingReserve { amount_sats: u64, cur_anchor_reserve_sats: u64 },
@@ -71,6 +71,7 @@ pub(crate) struct Wallet {
7171
persister: Mutex<KVStoreWalletPersister>,
7272
broadcaster: Arc<Broadcaster>,
7373
fee_estimator: Arc<OnchainFeeEstimator>,
74+
chain_source: Arc<ChainSource>,
7475
payment_store: Arc<PaymentStore>,
7576
config: Arc<Config>,
7677
logger: Arc<Logger>,
@@ -81,8 +82,9 @@ impl Wallet {
8182
pub(crate) fn new(
8283
wallet: bdk_wallet::PersistedWallet<KVStoreWalletPersister>,
8384
wallet_persister: KVStoreWalletPersister, broadcaster: Arc<Broadcaster>,
84-
fee_estimator: Arc<OnchainFeeEstimator>, payment_store: Arc<PaymentStore>,
85-
config: Arc<Config>, logger: Arc<Logger>, pending_payment_store: Arc<PendingPaymentStore>,
85+
fee_estimator: Arc<OnchainFeeEstimator>, chain_source: Arc<ChainSource>,
86+
payment_store: Arc<PaymentStore>, config: Arc<Config>, logger: Arc<Logger>,
87+
pending_payment_store: Arc<PendingPaymentStore>,
8688
) -> Self {
8789
let inner = Mutex::new(wallet);
8890
let persister = Mutex::new(wallet_persister);
@@ -91,6 +93,7 @@ impl Wallet {
9193
persister,
9294
broadcaster,
9395
fee_estimator,
96+
chain_source,
9497
payment_store,
9598
config,
9699
logger,
@@ -196,19 +199,6 @@ impl Wallet {
196199
Ok(())
197200
}
198201

199-
pub(crate) fn insert_txo(&self, outpoint: OutPoint, txout: TxOut) -> Result<(), Error> {
200-
let mut locked_wallet = self.inner.lock().unwrap();
201-
locked_wallet.insert_txout(outpoint, txout);
202-
203-
let mut locked_persister = self.persister.lock().unwrap();
204-
locked_wallet.persist(&mut locked_persister).map_err(|e| {
205-
log_error!(self.logger, "Failed to persist wallet: {}", e);
206-
Error::PersistenceFailed
207-
})?;
208-
209-
Ok(())
210-
}
211-
212202
fn update_payment_store<'a>(
213203
&self, locked_wallet: &'a mut PersistedWallet<KVStoreWalletPersister>,
214204
mut events: Vec<WalletEvent>,
@@ -1040,6 +1030,25 @@ impl Listen for Wallet {
10401030
);
10411031
}
10421032

1033+
// In order to be able to reliably calculate fees the `Wallet` needs access to the previous
1034+
// ouput data. To this end, we here insert any ouputs of transactions that LDK is intersted
1035+
// in (e.g., funding transaction ouputs) into the wallet's transaction graph when we see
1036+
// them, so it is reliably able to calculate fees for subsequent spends.
1037+
//
1038+
// FIXME: technically, we should also do this for mempool transactions. However, at the
1039+
// current time fixing the edge case doesn't seem worth the additional conplexity /
1040+
// additional overhead..
1041+
let registered_txids = self.chain_source.registered_txids();
1042+
for tx in &block.txdata {
1043+
let txid = tx.compute_txid();
1044+
if registered_txids.contains(&txid) {
1045+
for (vout, txout) in tx.output.iter().enumerate() {
1046+
let outpoint = OutPoint { txid, vout: vout as u32 };
1047+
locked_wallet.insert_txout(outpoint, txout.clone());
1048+
}
1049+
}
1050+
}
1051+
10431052
match locked_wallet.apply_block_events(block, height) {
10441053
Ok(events) => {
10451054
if let Err(e) = self.update_payment_store(&mut *locked_wallet, events) {

0 commit comments

Comments
 (0)