Skip to content

Commit ae37e46

Browse files
committed
Merge remote-tracking branch 'upstream/release/0.6'
# Conflicts: # Cargo.toml # Package.swift # bindings/kotlin/ldk-node-android/gradle.properties
2 parents 9a468b3 + 0d0e09f commit ae37e46

File tree

8 files changed

+121
-22
lines changed

8 files changed

+121
-22
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
# 0.6.0 - Jun. 9, 2025
2+
This sixth minor release mainly fixes an issue that could have left the
3+
on-chain wallet unable to spend funds if transactions that had previously been
4+
accepted to the mempool ended up being evicted.
5+
6+
## Feature and API updates
7+
- Onchain addresses are now validated against the expected network before use (#519).
8+
- The API methods on the `Bolt11Invoice` type are now exposed in bindings (#522).
9+
- The `UnifiedQrPayment::receive` flow no longer aborts if we're unable to generate a BOLT12 offer (#548).
10+
11+
## Bug Fixes and Improvements
12+
- Previously, the node could potentially enter a state that would have left the
13+
onchain wallet unable spend any funds if previously-generated transactions
14+
had been first accepted, and then evicted from the mempool. This has been
15+
fixed in BDK 2.0.0, to which we upgrade as part of this release. (#551)
16+
- A bug that had us fail `OnchainPayment::send_all` in the `retrain_reserves`
17+
mode when requiring sub-dust-limit anchor reserves has been fixed (#540).
18+
- The output of the `log` facade logger has been corrected (#547).
19+
20+
## Compatibility Notes
21+
- The BDK dependency has been bumped to `bdk_wallet` v2.0 (#551).
22+
23+
In total, this release features 20 files changed, 1188 insertions, 447 deletions, in 18 commits from 3 authors in alphabetical order:
24+
25+
- alexanderwiederin
26+
- Camillarhi
27+
- Elias Rohrer
28+
129
# 0.5.0 - Apr. 29, 2025
230
Besides numerous API improvements and bugfixes this fifth minor release notably adds support for sourcing chain and fee rate data from an Electrum backend, requesting channels via the [bLIP-51 / LSPS1](https://github.com/lightning/blips/blob/master/blip-0051.md) protocol, as well as experimental support for operating as a [bLIP-52 / LSPS2](https://github.com/lightning/blips/blob/master/blip-0052.md) service.
331

Cargo.toml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,28 @@ lightning-liquidity = { version = "0.1.0", features = ["std"] }
6161
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum", "time"] }
6262
#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity", features = ["std"] }
6363

64-
bdk_chain = { version = "0.21.1", default-features = false, features = ["std"] }
65-
bdk_esplora = { version = "0.20.1", default-features = false, features = ["async-https-rustls", "tokio"]}
66-
bdk_electrum = { version = "0.20.1", default-features = false, features = ["use-rustls"]}
67-
bdk_wallet = { version = "1.0.0", default-features = false, features = ["std", "keys-bip39"]}
64+
bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] }
65+
bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]}
66+
bdk_electrum = { version = "0.23.0", default-features = false, features = ["use-rustls"]}
67+
bdk_wallet = { version = "2.0.0", default-features = false, features = ["std", "keys-bip39"]}
6868

69-
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] }
69+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
7070
rusqlite = { version = "0.31.0", features = ["bundled"] }
71-
bitcoin = "0.32.2"
71+
bitcoin = "0.32.4"
7272
bip39 = "2.0.0"
7373
bip21 = { version = "0.5", features = ["std"], default-features = false }
7474

7575
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
7676
rand = "0.8.5"
7777
chrono = { version = "0.4", default-features = false, features = ["clock"] }
7878
tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros" ] }
79-
esplora-client = { version = "0.11", default-features = false, features = ["tokio", "async-https-rustls"] }
80-
electrum-client = { version = "0.22.0", default-features = true }
79+
esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] }
80+
81+
# FIXME: This was introduced to decouple the `bdk_esplora` and
82+
# `lightning-transaction-sync` APIs. We should drop it as part of the upgrade
83+
# to LDK 0.2.
84+
esplora-client_0_11 = { package = "esplora-client", version = "0.11", default-features = false, features = ["tokio", "async-https-rustls"] }
85+
electrum-client = { version = "0.23.1", default-features = true }
8186
libc = "0.2"
8287
uniffi = { version = "0.27.3", features = ["build"], optional = true }
8388
serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] }
@@ -98,10 +103,10 @@ proptest = "1.0.0"
98103
regex = "1.5.6"
99104

100105
[target.'cfg(not(no_download))'.dev-dependencies]
101-
electrsd = { version = "0.33.0", default-features = false, features = ["legacy", "esplora_a33e97e1", "corepc-node_27_2"] }
106+
electrsd = { version = "0.34.0", default-features = false, features = ["legacy", "esplora_a33e97e1", "corepc-node_27_2"] }
102107

103108
[target.'cfg(no_download)'.dev-dependencies]
104-
electrsd = { version = "0.33.0", default-features = false, features = ["legacy"] }
109+
electrsd = { version = "0.34.0", default-features = false, features = ["legacy"] }
105110
corepc-node = { version = "0.7.0", default-features = false, features = ["27_2"] }
106111

107112
[target.'cfg(cln_test)'.dev-dependencies]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
org.gradle.jvmargs=-Xmx1536m
22
kotlin.code.style=official
3-
libraryVersion=0.5.0
3+
libraryVersion=0.6.0

bindings/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ldk_node"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
authors = [
55
{ name="Elias Rohrer", email="[email protected]" },
66
]

src/chain/bitcoind_rpc.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,24 @@ impl BitcoindRpcClient {
190190
Ok(())
191191
}
192192

193+
/// Returns two `Vec`s:
194+
/// - mempool transactions, alongside their first-seen unix timestamps.
195+
/// - transactions that have been evicted from the mempool, alongside the last time they were seen absent.
196+
pub(crate) async fn get_updated_mempool_transactions(
197+
&self, best_processed_height: u32, unconfirmed_txids: Vec<Txid>,
198+
) -> std::io::Result<(Vec<(Transaction, u64)>, Vec<(Txid, u64)>)> {
199+
let mempool_txs =
200+
self.get_mempool_transactions_and_timestamp_at_height(best_processed_height).await?;
201+
let evicted_txids = self.get_evicted_mempool_txids_and_timestamp(unconfirmed_txids).await?;
202+
Ok((mempool_txs, evicted_txids))
203+
}
204+
193205
/// Get mempool transactions, alongside their first-seen unix timestamps.
194206
///
195207
/// This method is an adapted version of `bdk_bitcoind_rpc::Emitter::mempool`. It emits each
196208
/// transaction only once, unless we cannot assume the transaction's ancestors are already
197209
/// emitted.
198-
pub(crate) async fn get_mempool_transactions_and_timestamp_at_height(
210+
async fn get_mempool_transactions_and_timestamp_at_height(
199211
&self, best_processed_height: u32,
200212
) -> std::io::Result<Vec<(Transaction, u64)>> {
201213
let prev_mempool_time = self.latest_mempool_timestamp.load(Ordering::Relaxed);
@@ -252,6 +264,23 @@ impl BitcoindRpcClient {
252264
}
253265
Ok(txs_to_emit)
254266
}
267+
268+
// Retrieve a list of Txids that have been evicted from the mempool.
269+
//
270+
// To this end, we first update our local mempool_entries_cache and then return all unconfirmed
271+
// wallet `Txid`s that don't appear in the mempool still.
272+
async fn get_evicted_mempool_txids_and_timestamp(
273+
&self, unconfirmed_txids: Vec<Txid>,
274+
) -> std::io::Result<Vec<(Txid, u64)>> {
275+
let latest_mempool_timestamp = self.latest_mempool_timestamp.load(Ordering::Relaxed);
276+
let mempool_entries_cache = self.mempool_entries_cache.lock().await;
277+
let evicted_txids = unconfirmed_txids
278+
.into_iter()
279+
.filter(|txid| mempool_entries_cache.contains_key(txid))
280+
.map(|txid| (txid, latest_mempool_timestamp))
281+
.collect();
282+
Ok(evicted_txids)
283+
}
255284
}
256285

257286
impl BlockSource for BitcoindRpcClient {

src/chain/mod.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,18 @@ impl ChainSource {
237237
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
238238
node_metrics: Arc<RwLock<NodeMetrics>>,
239239
) -> Self {
240+
// FIXME / TODO: We introduced this to make `bdk_esplora` work separately without updating
241+
// `lightning-transaction-sync`. We should revert this as part of of the upgrade to LDK 0.2.
242+
let mut client_builder_0_11 = esplora_client_0_11::Builder::new(&server_url);
243+
client_builder_0_11 = client_builder_0_11.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS);
244+
let esplora_client_0_11 = client_builder_0_11.build_async().unwrap();
245+
let tx_sync =
246+
Arc::new(EsploraSyncClient::from_client(esplora_client_0_11, Arc::clone(&logger)));
247+
240248
let mut client_builder = esplora_client::Builder::new(&server_url);
241249
client_builder = client_builder.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS);
242250
let esplora_client = client_builder.build_async().unwrap();
243-
let tx_sync =
244-
Arc::new(EsploraSyncClient::from_client(esplora_client.clone(), Arc::clone(&logger)));
251+
245252
let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
246253
let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
247254
Self::Esplora {
@@ -1088,18 +1095,24 @@ impl ChainSource {
10881095
let cur_height = channel_manager.current_best_block().height;
10891096

10901097
let now = SystemTime::now();
1098+
let unconfirmed_txids = onchain_wallet.get_unconfirmed_txids();
10911099
match bitcoind_rpc_client
1092-
.get_mempool_transactions_and_timestamp_at_height(cur_height)
1100+
.get_updated_mempool_transactions(cur_height, unconfirmed_txids)
10931101
.await
10941102
{
1095-
Ok(unconfirmed_txs) => {
1103+
Ok((unconfirmed_txs, evicted_txids)) => {
10961104
log_trace!(
10971105
logger,
1098-
"Finished polling mempool of size {} in {}ms",
1106+
"Finished polling mempool of size {} and {} evicted transactions in {}ms",
10991107
unconfirmed_txs.len(),
1108+
evicted_txids.len(),
11001109
now.elapsed().unwrap().as_millis()
11011110
);
1102-
let _ = onchain_wallet.apply_unconfirmed_txs(unconfirmed_txs);
1111+
onchain_wallet
1112+
.apply_mempool_txs(unconfirmed_txs, evicted_txids)
1113+
.unwrap_or_else(|e| {
1114+
log_error!(logger, "Failed to apply mempool transactions: {:?}", e);
1115+
});
11031116
},
11041117
Err(e) => {
11051118
log_error!(logger, "Failed to poll for mempool transactions: {:?}", e);

src/wallet/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ where
129129
self.inner.lock().unwrap().tx_graph().full_txs().map(|tx_node| tx_node.tx).collect()
130130
}
131131

132+
pub(crate) fn get_unconfirmed_txids(&self) -> Vec<Txid> {
133+
self.inner
134+
.lock()
135+
.unwrap()
136+
.transactions()
137+
.filter(|t| t.chain_position.is_unconfirmed())
138+
.map(|t| t.tx_node.txid)
139+
.collect()
140+
}
141+
132142
pub(crate) fn current_best_block(&self) -> BestBlock {
133143
let checkpoint = self.inner.lock().unwrap().latest_checkpoint();
134144
BestBlock { block_hash: checkpoint.hash(), height: checkpoint.height() }
@@ -158,11 +168,12 @@ where
158168
}
159169
}
160170

161-
pub(crate) fn apply_unconfirmed_txs(
162-
&self, unconfirmed_txs: Vec<(Transaction, u64)>,
171+
pub(crate) fn apply_mempool_txs(
172+
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
163173
) -> Result<(), Error> {
164174
let mut locked_wallet = self.inner.lock().unwrap();
165175
locked_wallet.apply_unconfirmed_txs(unconfirmed_txs);
176+
locked_wallet.apply_evicted_txs(evicted_txids);
166177

167178
let mut locked_persister = self.persister.lock().unwrap();
168179
locked_wallet.persist(&mut locked_persister).map_err(|e| {

src/wallet/ser.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ impl<'a> Writeable for ChangeSetSerWrapper<'a, BdkTxGraphChangeSet<ConfirmationB
107107

108108
encode_tlv_stream!(writer, {
109109
(0, ChangeSetSerWrapper(&self.0.txs), required),
110+
(1, Some(&self.0.first_seen), option),
110111
(2, self.0.txouts, required),
112+
(3, Some(&self.0.last_evicted), option),
111113
(4, ChangeSetSerWrapper(&self.0.anchors), required),
112114
(6, self.0.last_seen, required),
113115
});
@@ -129,10 +131,14 @@ impl Readable for ChangeSetDeserWrapper<BdkTxGraphChangeSet<ConfirmationBlockTim
129131
ChangeSetDeserWrapper<BTreeSet<(ConfirmationBlockTime, Txid)>>,
130132
> = RequiredWrapper(None);
131133
let mut last_seen: RequiredWrapper<BTreeMap<Txid, u64>> = RequiredWrapper(None);
134+
let mut first_seen = None;
135+
let mut last_evicted = None;
132136

133137
decode_tlv_stream!(reader, {
134138
(0, txs, required),
139+
(1, first_seen, option),
135140
(2, txouts, required),
141+
(3, last_evicted, option),
136142
(4, anchors, required),
137143
(6, last_seen, required),
138144
});
@@ -142,6 +148,8 @@ impl Readable for ChangeSetDeserWrapper<BdkTxGraphChangeSet<ConfirmationBlockTim
142148
txouts: txouts.0.unwrap(),
143149
anchors: anchors.0.unwrap().0,
144150
last_seen: last_seen.0.unwrap(),
151+
first_seen: first_seen.unwrap_or_default(),
152+
last_evicted: last_evicted.unwrap_or_default(),
145153
}))
146154
}
147155
}
@@ -260,6 +268,7 @@ impl<'a> Writeable for ChangeSetSerWrapper<'a, BdkIndexerChangeSet> {
260268
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), lightning::io::Error> {
261269
CHANGESET_SERIALIZATION_VERSION.write(writer)?;
262270

271+
// Note we don't persist/use the optional spk_cache currently.
263272
encode_tlv_stream!(writer, { (0, ChangeSetSerWrapper(&self.0.last_revealed), required) });
264273
Ok(())
265274
}
@@ -275,9 +284,13 @@ impl Readable for ChangeSetDeserWrapper<BdkIndexerChangeSet> {
275284
let mut last_revealed: RequiredWrapper<ChangeSetDeserWrapper<BTreeMap<DescriptorId, u32>>> =
276285
RequiredWrapper(None);
277286

287+
// Note we don't persist/use the optional spk_cache currently.
278288
decode_tlv_stream!(reader, { (0, last_revealed, required) });
279289

280-
Ok(Self(BdkIndexerChangeSet { last_revealed: last_revealed.0.unwrap().0 }))
290+
Ok(Self(BdkIndexerChangeSet {
291+
last_revealed: last_revealed.0.unwrap().0,
292+
spk_cache: Default::default(),
293+
}))
281294
}
282295
}
283296

0 commit comments

Comments
 (0)