Skip to content

Commit 0b901fc

Browse files
committed
Expose sweeper balances via BalanceDetails
1 parent 8e45c2a commit 0b901fc

File tree

5 files changed

+155
-11
lines changed

5 files changed

+155
-11
lines changed

bindings/ldk_node.udl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,19 @@ interface LightningBalance {
273273
CounterpartyRevokedOutputClaimable ( ChannelId channel_id, PublicKey counterparty_node_id, u64 amount_satoshis );
274274
};
275275

276+
[Enum]
277+
interface PendingSweepBalance {
278+
PendingBroadcast ( ChannelId? channel_id, u64 amount_satoshis );
279+
BroadcastAwaitingConfirmation ( ChannelId? channel_id, u32 latest_broadcast_height, Txid latest_spending_txid, u64 amount_satoshis );
280+
AwaitingThresholdConfirmations ( ChannelId? channel_id, Txid latest_spending_txid, BlockHash confirmation_hash, u32 confirmation_height, u64 amount_satoshis);
281+
};
282+
276283
dictionary BalanceDetails {
277284
u64 total_onchain_balance_sats;
278285
u64 spendable_onchain_balance_sats;
279286
u64 total_lightning_balance_sats;
280287
sequence<LightningBalance> lightning_balances;
288+
sequence<PendingSweepBalance> pending_balances_from_channel_closures;
281289
};
282290

283291
interface ChannelConfig {
@@ -308,6 +316,9 @@ enum LogLevel {
308316
[Custom]
309317
typedef string Txid;
310318

319+
[Custom]
320+
typedef string BlockHash;
321+
311322
[Custom]
312323
typedef string SocketAddress;
313324

src/balance.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
use bitcoin::secp256k1::PublicKey;
21
use lightning::chain::channelmonitor::Balance as LdkBalance;
32
use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage};
43

4+
use bitcoin::secp256k1::PublicKey;
5+
use bitcoin::{BlockHash, Txid};
6+
7+
use crate::sweep::SpendableOutputInfo;
8+
59
/// Details of the known available balances returned by [`Node::list_balances`].
610
///
711
/// [`Node::list_balances`]: crate::Node::list_balances
@@ -33,6 +37,17 @@ pub struct BalanceDetails {
3337
/// [`ChannelDetails::next_outbound_htlc_limit_msat`]: crate::ChannelDetails::next_outbound_htlc_limit_msat
3438
/// [`Node::list_channels`]: crate::Node::list_channels
3539
pub lightning_balances: Vec<LightningBalance>,
40+
/// A detailed list of balances currently being swept from the Lightning to the on-chain
41+
/// wallet.
42+
///
43+
/// These are balances resulting from channel closures that may have been encumbered by a
44+
/// delay, but are now being claimed and useable once sufficiently confirmed on-chain.
45+
///
46+
/// Note that, depending on the sync status of the wallets, swept balances listed here might or
47+
/// might not already be accounted for in [`total_onchain_balance_sats`].
48+
///
49+
/// [`total_onchain_balance_sats`]: Self::total_onchain_balance_sats
50+
pub pending_balances_from_channel_closures: Vec<PendingSweepBalance>,
3651
}
3752

3853
/// Details about the status of a known Lightning balance.
@@ -199,3 +214,90 @@ impl LightningBalance {
199214
}
200215
}
201216
}
217+
218+
/// Details about the status of a known balance currently being swept to our on-chain wallet.
219+
#[derive(Debug, Clone)]
220+
pub enum PendingSweepBalance {
221+
/// The spendable output is about to be swept, but a spending transaction has yet to be generated and
222+
/// broadcast.
223+
PendingBroadcast {
224+
/// The identifier of the channel this balance belongs to.
225+
channel_id: Option<ChannelId>,
226+
/// The amount, in satoshis, of the output being swept.
227+
amount_satoshis: u64,
228+
},
229+
/// A spending transaction has been generated and broadcast and is awaiting confirmation
230+
/// on-chain.
231+
BroadcastAwaitingConfirmation {
232+
/// The identifier of the channel this balance belongs to.
233+
channel_id: Option<ChannelId>,
234+
/// The best height when we last broadcast a transaction spending the output being swept.
235+
latest_broadcast_height: u32,
236+
/// The identifier of the transaction spending the swept output we last broadcast.
237+
latest_spending_txid: Txid,
238+
/// The amount, in satoshis, of the output being swept.
239+
amount_satoshis: u64,
240+
},
241+
/// A spending transaction has been confirmed on-chain and is awaiting threshold confirmations.
242+
///
243+
/// It will be considered irrevocably confirmed after reaching [`ANTI_REORG_DELAY`].
244+
///
245+
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
246+
AwaitingThresholdConfirmations {
247+
/// The identifier of the channel this balance belongs to.
248+
channel_id: Option<ChannelId>,
249+
/// The identifier of the confirmed transaction spending the swept output.
250+
latest_spending_txid: Txid,
251+
/// The hash of the block in which the spending transaction was confirmed.
252+
confirmation_hash: BlockHash,
253+
/// The height at which the spending transaction was confirmed.
254+
confirmation_height: u32,
255+
/// The amount, in satoshis, of the output being swept.
256+
amount_satoshis: u64,
257+
},
258+
}
259+
260+
impl PendingSweepBalance {
261+
pub(crate) fn from_tracked_spendable_output(output_info: SpendableOutputInfo) -> Self {
262+
if let Some(confirmation_hash) = output_info.confirmation_hash {
263+
debug_assert!(output_info.confirmation_height.is_some());
264+
debug_assert!(output_info.latest_spending_tx.is_some());
265+
let channel_id = output_info.channel_id;
266+
let confirmation_height = output_info
267+
.confirmation_height
268+
.expect("Height must be set if the output is confirmed");
269+
let latest_spending_txid = output_info
270+
.latest_spending_tx
271+
.as_ref()
272+
.expect("Spending tx must be set if the output is confirmed")
273+
.txid();
274+
let amount_satoshis = output_info.value_satoshis();
275+
Self::AwaitingThresholdConfirmations {
276+
channel_id,
277+
latest_spending_txid,
278+
confirmation_hash,
279+
confirmation_height,
280+
amount_satoshis,
281+
}
282+
} else if let Some(latest_broadcast_height) = output_info.latest_broadcast_height {
283+
debug_assert!(output_info.latest_spending_tx.is_some());
284+
let channel_id = output_info.channel_id;
285+
let latest_spending_txid = output_info
286+
.latest_spending_tx
287+
.as_ref()
288+
.expect("Spending tx must be set if the spend was broadcast")
289+
.txid();
290+
let amount_satoshis = output_info.value_satoshis();
291+
Self::BroadcastAwaitingConfirmation {
292+
channel_id,
293+
latest_broadcast_height,
294+
latest_spending_txid,
295+
amount_satoshis,
296+
}
297+
} else {
298+
let channel_id = output_info.channel_id;
299+
let amount_satoshis = output_info.value_satoshis();
300+
Self::PendingBroadcast { channel_id, amount_satoshis }
301+
}
302+
}
303+
}

src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub use bitcoin;
100100
pub use lightning;
101101
pub use lightning_invoice;
102102

103-
pub use balance::{BalanceDetails, LightningBalance};
103+
pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance};
104104
pub use error::Error as NodeError;
105105
use error::Error;
106106

@@ -1769,11 +1769,19 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
17691769
}
17701770
}
17711771

1772+
let pending_balances_from_channel_closures = self
1773+
.output_sweeper
1774+
.tracked_spendable_outputs()
1775+
.into_iter()
1776+
.map(|o| PendingSweepBalance::from_tracked_spendable_output(o))
1777+
.collect();
1778+
17721779
BalanceDetails {
17731780
total_onchain_balance_sats,
17741781
spendable_onchain_balance_sats,
17751782
total_lightning_balance_sats,
17761783
lightning_balances,
1784+
pending_balances_from_channel_closures,
17771785
}
17781786
}
17791787

src/sweep.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ const REGENERATE_SPEND_THRESHOLD: u32 = 144;
2929

3030
#[derive(Clone, Debug, PartialEq, Eq)]
3131
pub(crate) struct SpendableOutputInfo {
32-
id: [u8; 32],
33-
descriptor: SpendableOutputDescriptor,
34-
channel_id: Option<ChannelId>,
35-
first_broadcast_hash: Option<BlockHash>,
36-
latest_broadcast_height: Option<u32>,
37-
latest_spending_tx: Option<Transaction>,
38-
confirmation_height: Option<u32>,
39-
confirmation_hash: Option<BlockHash>,
32+
pub(crate) id: [u8; 32],
33+
pub(crate) descriptor: SpendableOutputDescriptor,
34+
pub(crate) channel_id: Option<ChannelId>,
35+
pub(crate) first_broadcast_hash: Option<BlockHash>,
36+
pub(crate) latest_broadcast_height: Option<u32>,
37+
pub(crate) latest_spending_tx: Option<Transaction>,
38+
pub(crate) confirmation_height: Option<u32>,
39+
pub(crate) confirmation_hash: Option<BlockHash>,
4040
}
4141

4242
impl SpendableOutputInfo {
@@ -77,6 +77,14 @@ impl SpendableOutputInfo {
7777

7878
false
7979
}
80+
81+
pub(crate) fn value_satoshis(&self) -> u64 {
82+
match &self.descriptor {
83+
SpendableOutputDescriptor::StaticOutput { output, .. } => output.value,
84+
SpendableOutputDescriptor::DelayedPaymentOutput(output) => output.output.value,
85+
SpendableOutputDescriptor::StaticPaymentOutput(output) => output.output.value,
86+
}
87+
}
8088
}
8189

8290
impl_writeable_tlv_based!(SpendableOutputInfo, {
@@ -184,6 +192,10 @@ where
184192
self.rebroadcast_if_necessary();
185193
}
186194

195+
pub(crate) fn tracked_spendable_outputs(&self) -> Vec<SpendableOutputInfo> {
196+
self.outputs.lock().unwrap().clone()
197+
}
198+
187199
fn rebroadcast_if_necessary(&self) {
188200
let (cur_height, cur_hash) = {
189201
let best_block = self.best_block.lock().unwrap();

src/uniffi_types.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub use lightning::ln::ChannelId;
33
pub use lightning::ln::PaymentSecret;
44
pub use lightning::util::string::UntrustedString;
55

6-
pub use bitcoin::OutPoint;
6+
pub use bitcoin::{BlockHash, OutPoint};
77

88
pub use bip39::Mnemonic;
99

@@ -172,6 +172,17 @@ impl UniffiCustomTypeConverter for Txid {
172172
}
173173
}
174174

175+
impl UniffiCustomTypeConverter for BlockHash {
176+
type Builtin = String;
177+
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
178+
Ok(BlockHash::from_str(&val)?)
179+
}
180+
181+
fn from_custom(obj: Self) -> Self::Builtin {
182+
obj.to_string()
183+
}
184+
}
185+
175186
impl UniffiCustomTypeConverter for Mnemonic {
176187
type Builtin = String;
177188
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {

0 commit comments

Comments
 (0)