Skip to content

Commit 0090641

Browse files
committed
Expose the amount of funds available for claim in ChannelMonitor
In general, we should always allow users to query for how much is currently in-flight being claimed on-chain at any time. This does so by examining the confirmed claims on-chain and breaking down what is left to be claimed into a new `ClaimableBalance` enum. Fixes lightningdevkit#995.
1 parent f9febb0 commit 0090641

File tree

2 files changed

+676
-3
lines changed

2 files changed

+676
-3
lines changed

lightning/src/chain/channelmonitor.rs

+221-1
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,59 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
535535
},
536536
);
537537

538+
/// Details about the balance(s) available for spending once the channel appears on chain.
539+
///
540+
/// See [`ChannelMonitor::get_claimable_balances`] for more details on when these will or will not
541+
/// be provided.
542+
#[derive(Clone, Debug, PartialEq, Eq)]
543+
#[cfg_attr(test, derive(PartialOrd, Ord))]
544+
pub enum Balance {
545+
/// The channel is not yet closed (or the commitment or closing transaction has not yet
546+
/// appeared in a block). The given balance is claimable (less on-chain fees) if the channel is
547+
/// force-closed now.
548+
ClaimableOnChannelClose {
549+
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
550+
/// required to do so.
551+
claimable_amount_satoshis: u64,
552+
},
553+
/// The channel has been closed, and the given balance is ours but awaiting confirmations until
554+
/// we consider it spendable.
555+
ClaimableAwaitingConfirmations {
556+
/// The amount available to claim, in satoshis, possibly excluding the on-chain fees which
557+
/// were spent in broadcasting the transaction.
558+
claimable_amount_satoshis: u64,
559+
/// The height at which an [`Event::SpendableOutputs`] event will be generated for this
560+
/// amount.
561+
confirmation_height: u32,
562+
},
563+
/// The channel has been closed, and the given balance should be ours but awaiting spending
564+
/// transaction confirmation. If the spending transaction does not confirm in time, it is
565+
/// possible our counterparty can take the funds by broadcasting an HTLC timeout on-chain.
566+
///
567+
/// Once the spending transaction confirms, before it has reached enough confirmations to be
568+
/// considered safe from chain reorganizations, the balance will instead be provided via
569+
/// [`Balance::ClaimableAwaitingConfirmations`].
570+
ContentiousClaimable {
571+
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
572+
/// required to do so.
573+
claimable_amount_satoshis: u64,
574+
/// The height at which the counterparty may be able to claim the balance if we have not
575+
/// done so.
576+
timeout_height: u32,
577+
},
578+
/// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain
579+
/// fees) if the counterparty does not know the preimage for the HTLCs. These are somewhat
580+
/// likely to be claimed by our counterparty before we do.
581+
MaybeClaimableHTLCAwaitingTimeout {
582+
/// The amount available to claim, in satoshis, excluding the on-chain fees which will be
583+
/// required to do so.
584+
claimable_amount_satoshis: u64,
585+
/// The height at which we will be able to claim the balance if our counterparty has not
586+
/// done so.
587+
claimable_height: u32,
588+
},
589+
}
590+
538591
/// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY.
539592
#[derive(PartialEq)]
540593
struct IrrevocablyResolvedHTLC {
@@ -1302,6 +1355,173 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
13021355
pub fn current_best_block(&self) -> BestBlock {
13031356
self.inner.lock().unwrap().best_block.clone()
13041357
}
1358+
1359+
/// Gets the balances in this channel which are either claimable by us if we were to
1360+
/// force-close the channel now or which are claimable on-chain (possibly awaiting
1361+
/// confirmation).
1362+
///
1363+
/// Any balances in the channel which are available on-chain (excluding on-chain fees) are
1364+
/// included here until an [`Event::SpendableOutputs`] event has been generated for the
1365+
/// balance, or until our counterparty has claimed the balance and accrued several
1366+
/// confirmations on the claim transaction.
1367+
///
1368+
/// Note that the balances available when you or your counterparty have broadcasted revoked
1369+
/// state(s) may not be fully captured here.
1370+
// TODO, fix that ^
1371+
///
1372+
/// See [`Balance`] for additional details on the types of claimable balances which
1373+
/// may be returned here and their meanings.
1374+
pub fn get_claimable_balances(&self) -> Vec<Balance> {
1375+
let mut res = Vec::new();
1376+
let us = self.inner.lock().unwrap();
1377+
1378+
let mut confirmed_txid = us.funding_spend_confirmed;
1379+
let mut pending_commitment_tx_conf_thresh = None;
1380+
let funding_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1381+
if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
1382+
Some((event.txid, event.confirmation_threshold()))
1383+
} else { None }
1384+
});
1385+
if let Some((txid, conf_thresh)) = funding_spend_pending {
1386+
debug_assert!(us.funding_spend_confirmed.is_none(),
1387+
"We have a pending funding spend awaiting anti-reorg confirmation, we can't have confirmed it already!");
1388+
confirmed_txid = Some(txid);
1389+
pending_commitment_tx_conf_thresh = Some(conf_thresh);
1390+
}
1391+
1392+
macro_rules! walk_htlcs {
1393+
($holder_commitment: expr, $htlc_iter: expr) => {
1394+
for htlc in $htlc_iter {
1395+
if let Some(htlc_input_idx) = htlc.transaction_output_index {
1396+
if us.htlcs_resolved_on_chain.iter().any(|v| v.input_idx == htlc_input_idx) {
1397+
assert!(us.funding_spend_confirmed.is_some());
1398+
} else if htlc.offered == $holder_commitment {
1399+
// If the payment was outbound, check if there's an HTLCUpdate
1400+
// indicating we have spent this HTLC with a timeout, claiming it back
1401+
// and awaiting confirmations on it.
1402+
let htlc_update_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1403+
if let OnchainEvent::HTLCUpdate { input_idx: Some(input_idx), .. } = event.event {
1404+
if input_idx == htlc_input_idx { Some(event.confirmation_threshold()) } else { None }
1405+
} else { None }
1406+
});
1407+
if let Some(conf_thresh) = htlc_update_pending {
1408+
res.push(Balance::ClaimableAwaitingConfirmations {
1409+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1410+
confirmation_height: conf_thresh,
1411+
});
1412+
} else {
1413+
res.push(Balance::MaybeClaimableHTLCAwaitingTimeout {
1414+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1415+
claimable_height: htlc.cltv_expiry,
1416+
});
1417+
}
1418+
} else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
1419+
// Otherwise (the payment was inbound), only expose it as claimable if
1420+
// we know the preimage.
1421+
// Note that if there is a pending claim, but it did not use the
1422+
// preimage, we lost funds to our counterparty! We will then continue
1423+
// to show it as ContentiousClaimable until ANTI_REORG_DELAY.
1424+
let htlc_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1425+
if let OnchainEvent::HTLCSpendConfirmation { input_idx, preimage, .. } = event.event {
1426+
if input_idx == htlc_input_idx {
1427+
Some((event.confirmation_threshold(), preimage.is_some()))
1428+
} else { None }
1429+
} else { None }
1430+
});
1431+
if let Some((conf_thresh, true)) = htlc_spend_pending {
1432+
res.push(Balance::ClaimableAwaitingConfirmations {
1433+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1434+
confirmation_height: conf_thresh,
1435+
});
1436+
} else {
1437+
res.push(Balance::ContentiousClaimable {
1438+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1439+
timeout_height: htlc.cltv_expiry,
1440+
});
1441+
}
1442+
}
1443+
}
1444+
}
1445+
}
1446+
}
1447+
1448+
if let Some(txid) = confirmed_txid {
1449+
let mut found_commitment_tx = false;
1450+
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1451+
walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().map(|(a, _)| a));
1452+
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
1453+
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1454+
if let OnchainEvent::MaturingOutput {
1455+
descriptor: SpendableOutputDescriptor::StaticPaymentOutput(descriptor)
1456+
} = &event.event {
1457+
Some(descriptor.output.value)
1458+
} else { None }
1459+
}) {
1460+
res.push(Balance::ClaimableAwaitingConfirmations {
1461+
claimable_amount_satoshis: value,
1462+
confirmation_height: conf_thresh,
1463+
});
1464+
} else {
1465+
// If a counterparty commitment transaction is awaiting confirmation, we
1466+
// should either have a StaticPaymentOutput MaturingOutput event awaiting
1467+
// confirmation with the same height or have never met our dust amount.
1468+
}
1469+
}
1470+
found_commitment_tx = true;
1471+
} else if txid == us.current_holder_commitment_tx.txid {
1472+
walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
1473+
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
1474+
res.push(Balance::ClaimableAwaitingConfirmations {
1475+
claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
1476+
confirmation_height: conf_thresh,
1477+
});
1478+
}
1479+
found_commitment_tx = true;
1480+
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
1481+
if txid == prev_commitment.txid {
1482+
walk_htlcs!(true, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
1483+
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
1484+
res.push(Balance::ClaimableAwaitingConfirmations {
1485+
claimable_amount_satoshis: prev_commitment.to_self_value_sat,
1486+
confirmation_height: conf_thresh,
1487+
});
1488+
}
1489+
found_commitment_tx = true;
1490+
}
1491+
}
1492+
if !found_commitment_tx {
1493+
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
1494+
// We blindly assume this is a cooperative close transaction here, and that
1495+
// neither us nor our counterparty misbehaved. At worst we've under-estimated
1496+
// the amount we can claim as we'll punish a misbehaving counterparty.
1497+
res.push(Balance::ClaimableAwaitingConfirmations {
1498+
claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
1499+
confirmation_height: conf_thresh,
1500+
});
1501+
}
1502+
}
1503+
// TODO: Add logic to provide claimable balances for counterparty broadcasting revoked
1504+
// outputs.
1505+
} else {
1506+
let mut claimable_inbound_htlc_value_sat = 0;
1507+
for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {
1508+
if htlc.transaction_output_index.is_none() { continue; }
1509+
if htlc.offered {
1510+
res.push(Balance::MaybeClaimableHTLCAwaitingTimeout {
1511+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1512+
claimable_height: htlc.cltv_expiry,
1513+
});
1514+
} else if us.payment_preimages.get(&htlc.payment_hash).is_some() {
1515+
claimable_inbound_htlc_value_sat += htlc.amount_msat / 1000;
1516+
}
1517+
}
1518+
res.push(Balance::ClaimableOnChannelClose {
1519+
claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat + claimable_inbound_htlc_value_sat,
1520+
});
1521+
}
1522+
1523+
res
1524+
}
13051525
}
13061526

13071527
/// Compares a broadcasted commitment transaction's HTLCs with those in the latest state,
@@ -2488,7 +2708,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
24882708
// we've already failed the HTLC as the commitment transaction
24892709
// which was broadcasted was revoked. In that case, we should
24902710
// spend the HTLC output here immediately, and expose that fact
2491-
// as a ClaimableBalance, something which we do not yet do.
2711+
// as a Balance, something which we do not yet do.
24922712
// TODO: Track the above as claimable!
24932713
}
24942714
continue 'outer_loop;

0 commit comments

Comments
 (0)