Skip to content

Commit 6064d96

Browse files
committed
Expose counterparty-revoked-outputs in get_claimable_balance
This uses the various new tracking added in the prior commits to expose a new `Balance` type - `CounterpartyRevokedOutputClaimable`. Some nontrivial work is required, however, as we now have to track HTLC outputs as spendable in a transaction that comes *after* an HTLC-Success/HTLC-Timeout transaction, which we previously didn't need to do. Thus, we have to check if an `onchain_events_awaiting_threshold_conf` event spends a commitment transaction's HTLC output while walking events. Further, because we now need to track HTLC outputs after the HTLC-Success/HTLC-Timeout confirms, and because we have to track the counterparty's `to_self` output as a contentious output which could be claimed by either party, we have to examine the `OnchainTxHandler`'s set of outputs to spend when determining if certain outputs are still spendable. Two new tests are added which test various different transaction formats, and hopefully provide good test coverage of the various revoked output paths.
1 parent bd56dc1 commit 6064d96

File tree

3 files changed

+565
-13
lines changed

3 files changed

+565
-13
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
2323
use bitcoin::blockdata::block::BlockHeader;
2424
use bitcoin::blockdata::transaction::{TxOut,Transaction};
25+
use bitcoin::blockdata::transaction::OutPoint as BitcoinOutPoint;
2526
use bitcoin::blockdata::script::{Script, Builder};
2627
use bitcoin::blockdata::opcodes;
2728

@@ -377,6 +378,9 @@ enum OnchainEvent {
377378
on_local_output_csv: Option<u16>,
378379
/// If the funding spend transaction was a known remote commitment transaction, we track
379380
/// the output index and amount of the counterparty's `to_self` output here.
381+
///
382+
/// This allows us to generate a [`Balance::CounterpartyRevokedOutputClaimable`] for the
383+
/// counterparty output.
380384
commitment_tx_to_counterparty_output: CommitmentTxCounterpartyOutputInfo,
381385
},
382386
/// A spend of a commitment transaction HTLC output, set in the cases where *no* `HTLCUpdate`
@@ -577,6 +581,15 @@ pub enum Balance {
577581
/// done so.
578582
claimable_height: u32,
579583
},
584+
/// The channel has been closed, and our counterparty broadcasted a revoked commitment
585+
/// transaction.
586+
///
587+
/// Thus, we're able to claim all outputs in the commitment transaction, one of which has the
588+
/// following amount.
589+
CounterpartyRevokedOutputClaimable {
590+
/// The amount, in satoshis, of the output which we can claim.
591+
claimable_amount_satoshis: u64,
592+
},
580593
}
581594

582595
/// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY.
@@ -1399,9 +1412,9 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
13991412
/// balance, or until our counterparty has claimed the balance and accrued several
14001413
/// confirmations on the claim transaction.
14011414
///
1402-
/// Note that the balances available when you or your counterparty have broadcasted revoked
1403-
/// state(s) may not be fully captured here.
1404-
// TODO, fix that ^
1415+
/// Note that for `ChannelMonitors` which track a channel which went on-chain with versions of
1416+
/// LDK prior to 0.0.108, balances may not be fully captured if our counterparty broadcasted
1417+
/// a revoked state.
14051418
///
14061419
/// See [`Balance`] for additional details on the types of claimable balances which
14071420
/// may be returned here and their meanings.
@@ -1410,9 +1423,13 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14101423
let us = self.inner.lock().unwrap();
14111424

14121425
let mut confirmed_txid = us.funding_spend_confirmed;
1426+
let mut confirmed_counterparty_output = us.confirmed_commitment_tx_counterparty_output;
14131427
let mut pending_commitment_tx_conf_thresh = None;
14141428
let funding_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1415-
if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
1429+
if let OnchainEvent::FundingSpendConfirmation { commitment_tx_to_counterparty_output, .. } =
1430+
event.event
1431+
{
1432+
confirmed_counterparty_output = commitment_tx_to_counterparty_output;
14161433
Some((event.txid, event.confirmation_threshold()))
14171434
} else { None }
14181435
});
@@ -1424,22 +1441,25 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14241441
}
14251442

14261443
macro_rules! walk_htlcs {
1427-
($holder_commitment: expr, $htlc_iter: expr) => {
1444+
($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
14281445
for htlc in $htlc_iter {
14291446
if let Some(htlc_commitment_tx_output_idx) = htlc.transaction_output_index {
1447+
let mut htlc_spend_txid_opt = None;
14301448
let mut htlc_update_pending = None;
14311449
let mut htlc_spend_pending = None;
14321450
let mut delayed_output_pending = None;
14331451
for event in us.onchain_events_awaiting_threshold_conf.iter() {
14341452
match event.event {
14351453
OnchainEvent::HTLCUpdate { commitment_tx_output_idx, htlc_value_satoshis, .. }
14361454
if commitment_tx_output_idx == Some(htlc_commitment_tx_output_idx) => {
1455+
htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
14371456
debug_assert!(htlc_update_pending.is_none());
14381457
debug_assert_eq!(htlc_value_satoshis.unwrap(), htlc.amount_msat / 1000);
14391458
htlc_update_pending = Some(event.confirmation_threshold());
14401459
},
14411460
OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. }
14421461
if commitment_tx_output_idx == htlc_commitment_tx_output_idx => {
1462+
htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
14431463
debug_assert!(htlc_spend_pending.is_none());
14441464
htlc_spend_pending = Some((event.confirmation_threshold(), preimage.is_some()));
14451465
},
@@ -1453,22 +1473,77 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14531473
}
14541474
}
14551475
let htlc_resolved = us.htlcs_resolved_on_chain.iter()
1456-
.find(|v| v.commitment_tx_output_idx == htlc_commitment_tx_output_idx);
1476+
.find(|v| if v.commitment_tx_output_idx == htlc_commitment_tx_output_idx {
1477+
debug_assert_ne!(v.resolving_txid, confirmed_txid);
1478+
htlc_spend_txid_opt = v.resolving_txid;
1479+
true
1480+
} else { false });
14571481
debug_assert!(htlc_update_pending.is_some() as u8 + htlc_spend_pending.is_some() as u8 + htlc_resolved.is_some() as u8 <= 1);
14581482

1483+
let htlc_output_needs_spending =
1484+
us.onchain_tx_handler.is_output_spend_pending(&
1485+
if let Some(txid) = htlc_spend_txid_opt {
1486+
debug_assert!(
1487+
us.onchain_tx_handler.channel_transaction_parameters.opt_anchors.is_none(),
1488+
"This code needs updating for anchors");
1489+
BitcoinOutPoint::new(txid, 0)
1490+
} else {
1491+
BitcoinOutPoint::new(confirmed_txid.unwrap(), htlc_commitment_tx_output_idx)
1492+
});
1493+
14591494
if let Some(conf_thresh) = delayed_output_pending {
14601495
debug_assert!($holder_commitment);
14611496
res.push(Balance::ClaimableAwaitingConfirmations {
14621497
claimable_amount_satoshis: htlc.amount_msat / 1000,
14631498
confirmation_height: conf_thresh,
14641499
});
1465-
} else if htlc_resolved.is_some() {
1500+
} else if htlc_resolved.is_some() && !htlc_output_needs_spending {
14661501
// Funding transaction spends should be fully confirmed by the time any
14671502
// HTLC transactions are resolved, unless we're talking about a holder
14681503
// commitment tx, whose resolution is delayed until the CSV timeout is
14691504
// reached, even though HTLCs may be resolved after only
14701505
// ANTI_REORG_DELAY confirmations.
14711506
debug_assert!($holder_commitment || us.funding_spend_confirmed.is_some());
1507+
} else if $counterparty_revoked_commitment {
1508+
let htlc_output_claim_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1509+
if let OnchainEvent::MaturingOutput {
1510+
descriptor: SpendableOutputDescriptor::StaticOutput { .. }
1511+
} = &event.event {
1512+
if event.transaction.as_ref().map(|tx| tx.input.iter().any(|inp| {
1513+
if let Some(htlc_spend_txid) = htlc_spend_txid_opt {
1514+
Some(tx.txid()) == htlc_spend_txid_opt ||
1515+
inp.previous_output.txid == htlc_spend_txid
1516+
} else {
1517+
Some(inp.previous_output.txid) == confirmed_txid &&
1518+
inp.previous_output.vout == htlc_commitment_tx_output_idx
1519+
}
1520+
})).unwrap_or(false) {
1521+
Some(())
1522+
} else { None }
1523+
} else { None }
1524+
});
1525+
if htlc_output_claim_pending.is_some() {
1526+
// We already push `Balance`s onto the `res` list for every
1527+
// `StaticOutput` in a `MaturingOutput` in the revoked
1528+
// counterparty commitment transaction case generally, so don't
1529+
// need to do so again here.
1530+
} else if htlc_spend_pending.is_some() {
1531+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1532+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1533+
});
1534+
} else if htlc_resolved.is_some() {
1535+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1536+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1537+
});
1538+
} else {
1539+
debug_assert!(htlc_update_pending.is_none(),
1540+
"HTLCUpdate OnchainEvents should never appear for preimage claims");
1541+
debug_assert!(htlc_spend_pending.is_none() || !htlc_spend_pending.unwrap().1,
1542+
"We don't (currently) generate preimage claims against revoked outputs, where did you get one?!");
1543+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1544+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1545+
});
1546+
}
14721547
} else {
14731548
if htlc.offered == $holder_commitment {
14741549
// If the payment was outbound, check if there's an HTLCUpdate
@@ -1512,8 +1587,8 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15121587

15131588
if let Some(txid) = confirmed_txid {
15141589
let mut found_commitment_tx = false;
1515-
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1516-
walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().map(|(a, _)| a));
1590+
if let Some(counterparty_tx_htlcs) = us.counterparty_claimable_outpoints.get(&txid) {
1591+
// First look for the to_remote output back to us.
15171592
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15181593
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
15191594
if let OnchainEvent::MaturingOutput {
@@ -1532,9 +1607,50 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15321607
// confirmation with the same height or have never met our dust amount.
15331608
}
15341609
}
1610+
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1611+
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1612+
} else {
1613+
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1614+
// The counterparty broadcasted a revoked state!
1615+
// Look for a StaticOutput spend first, as it should be spending the full set
1616+
// of commitment transaction outputs, if we see one, assume that it did and
1617+
// just return that.
1618+
let mut spent_counterparty_output = false;
1619+
for event in us.onchain_events_awaiting_threshold_conf.iter() {
1620+
if let OnchainEvent::MaturingOutput {
1621+
descriptor: SpendableOutputDescriptor::StaticOutput { output, .. }
1622+
} = &event.event {
1623+
res.push(Balance::ClaimableAwaitingConfirmations {
1624+
claimable_amount_satoshis: output.value,
1625+
confirmation_height: event.confirmation_threshold(),
1626+
});
1627+
if let Some(confirmed_to_self_idx) = confirmed_counterparty_output.map(|(idx, _)| idx) {
1628+
if event.transaction.as_ref().map(|tx|
1629+
tx.input.iter().any(|inp| inp.previous_output.vout == confirmed_to_self_idx)
1630+
).unwrap_or(false) {
1631+
spent_counterparty_output = true;
1632+
}
1633+
}
1634+
}
1635+
}
1636+
1637+
if spent_counterparty_output {
1638+
} else if let Some((confirmed_to_self_idx, amt)) = confirmed_counterparty_output {
1639+
let output_spendable = us.onchain_tx_handler
1640+
.is_output_spend_pending(&BitcoinOutPoint::new(txid, confirmed_to_self_idx));
1641+
if output_spendable {
1642+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1643+
claimable_amount_satoshis: amt,
1644+
});
1645+
}
1646+
} else {
1647+
// Counterparty output is missing, either it was broadcasted on a
1648+
// previous version of LDK or the counterparty hadn't met dust.
1649+
}
1650+
}
15351651
found_commitment_tx = true;
15361652
} else if txid == us.current_holder_commitment_tx.txid {
1537-
walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
1653+
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
15381654
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15391655
res.push(Balance::ClaimableAwaitingConfirmations {
15401656
claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -1544,7 +1660,7 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15441660
found_commitment_tx = true;
15451661
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
15461662
if txid == prev_commitment.txid {
1547-
walk_htlcs!(true, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
1663+
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
15481664
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15491665
res.push(Balance::ClaimableAwaitingConfirmations {
15501666
claimable_amount_satoshis: prev_commitment.to_self_value_sat,
@@ -1565,8 +1681,6 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15651681
});
15661682
}
15671683
}
1568-
// TODO: Add logic to provide claimable balances for counterparty broadcasting revoked
1569-
// outputs.
15701684
} else {
15711685
let mut claimable_inbound_htlc_value_sat = 0;
15721686
for (htlc, _, _) in us.current_holder_commitment_tx.htlc_outputs.iter() {

lightning/src/chain/onchaintx.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,10 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
690690
}
691691
}
692692

693+
pub(crate) fn is_output_spend_pending(&self, outpoint: &BitcoinOutPoint) -> bool {
694+
self.claimable_outpoints.get(outpoint).is_some()
695+
}
696+
693697
pub(crate) fn get_relevant_txids(&self) -> Vec<Txid> {
694698
let mut txids: Vec<Txid> = self.onchain_events_awaiting_threshold_conf
695699
.iter()

0 commit comments

Comments
 (0)