Skip to content

Commit 17a6c9f

Browse files
committed
Expose sweeper balances via BalanceDetails
1 parent 8495bc7 commit 17a6c9f

File tree

5 files changed

+157
-12
lines changed

5 files changed

+157
-12
lines changed

bindings/ldk_node.udl

+11
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,19 @@ interface LightningBalance {
234234
CounterpartyRevokedOutputClaimable ( ChannelId channel_id, PublicKey counterparty_node_id, u64 amount_satoshis );
235235
};
236236

237+
[Enum]
238+
interface SweeperBalance {
239+
PendingBroadcast ( ChannelId? channel_id, u64 amount_satoshis );
240+
BroadcastAwaitingConfirmation ( ChannelId? channel_id, u32 latest_broadcast_height, Txid latest_spending_txid, u64 amount_satoshis );
241+
AwaitingThresholdConfirmations ( ChannelId? channel_id, Txid latest_spending_txid, BlockHash confirmation_hash, u32 confirmation_height, u64 amount_satoshis);
242+
};
243+
237244
dictionary BalanceDetails {
238245
u64 total_onchain_balance_sats;
239246
u64 spendable_onchain_balance_sats;
240247
u64 total_lightning_balance_sats;
241248
sequence<LightningBalance> lightning_balances;
249+
sequence<SweeperBalance> sweeper_balances;
242250
};
243251

244252
interface ChannelConfig {
@@ -269,6 +277,9 @@ enum LogLevel {
269277
[Custom]
270278
typedef string Txid;
271279

280+
[Custom]
281+
typedef string BlockHash;
282+
272283
[Custom]
273284
typedef string SocketAddress;
274285

src/balance.rs

+101-1
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
@@ -15,6 +19,15 @@ pub struct BalanceDetails {
1519
pub total_lightning_balance_sats: u64,
1620
/// A detailed list of all known Lightning balances.
1721
pub lightning_balances: Vec<LightningBalance>,
22+
/// A detailed list of balances currently being swept from the Lightning to the on-chain
23+
/// wallet.
24+
///
25+
/// These are balances resulting from channel closures that may have been encumbered by a
26+
/// delay, but are now being claimed and useable once sufficiently confirmed on-chain.
27+
///
28+
/// Note that, depending on the sync status of the wallets, swept balances listed here might or
29+
/// might not already be accounted for in [`Self::total_onchain_balance_sats`].
30+
pub sweeper_balances: Vec<SweeperBalance>,
1831
}
1932

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

src/lib.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub use bitcoin;
9898
pub use lightning;
9999
pub use lightning_invoice;
100100

101-
pub use balance::{BalanceDetails, LightningBalance};
101+
pub use balance::{BalanceDetails, LightningBalance, SweeperBalance};
102102
pub use error::Error as NodeError;
103103
use error::Error;
104104

@@ -108,7 +108,10 @@ pub use types::ChannelConfig;
108108
pub use io::utils::generate_entropy_mnemonic;
109109

110110
#[cfg(feature = "uniffi")]
111-
use {bip39::Mnemonic, bitcoin::OutPoint, lightning::ln::PaymentSecret, uniffi_types::*};
111+
use {
112+
bip39::Mnemonic, bitcoin::BlockHash, bitcoin::OutPoint, lightning::ln::PaymentSecret,
113+
uniffi_types::*,
114+
};
112115

113116
#[cfg(feature = "uniffi")]
114117
pub use builder::ArcedNodeBuilder as Builder;
@@ -1579,11 +1582,19 @@ impl<K: KVStore + Sync + Send + 'static> Node<K> {
15791582
}
15801583
}
15811584

1585+
let sweeper_balances = self
1586+
.output_sweeper
1587+
.tracked_spendable_outputs()
1588+
.into_iter()
1589+
.map(|o| SweeperBalance::from_tracked_spendable_output(o))
1590+
.collect();
1591+
15821592
BalanceDetails {
15831593
total_onchain_balance_sats,
15841594
spendable_onchain_balance_sats,
15851595
total_lightning_balance_sats,
15861596
lightning_balances,
1597+
sweeper_balances,
15871598
}
15881599
}
15891600

src/sweep.rs

+20-8
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

+12-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{Node, SocketAddress, UserChannelId};
88
use bitcoin::hashes::sha256::Hash as Sha256;
99
use bitcoin::hashes::Hash;
1010
use bitcoin::secp256k1::PublicKey;
11-
use bitcoin::{Address, Txid};
11+
use bitcoin::{Address, BlockHash, Txid};
1212
use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
1313
use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice};
1414

@@ -165,6 +165,17 @@ impl UniffiCustomTypeConverter for Txid {
165165
}
166166
}
167167

168+
impl UniffiCustomTypeConverter for BlockHash {
169+
type Builtin = String;
170+
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
171+
Ok(BlockHash::from_str(&val)?)
172+
}
173+
174+
fn from_custom(obj: Self) -> Self::Builtin {
175+
obj.to_string()
176+
}
177+
}
178+
168179
impl UniffiCustomTypeConverter for Mnemonic {
169180
type Builtin = String;
170181
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {

0 commit comments

Comments
 (0)