Skip to content

Commit 5a8ede0

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 f712eb4 commit 5a8ede0

File tree

4 files changed

+799
-19
lines changed

4 files changed

+799
-19
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 124 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

@@ -382,6 +383,9 @@ enum OnchainEvent {
382383
on_local_output_csv: Option<u16>,
383384
/// If the funding spend transaction was a known remote commitment transaction, we track
384385
/// the output index and amount of the counterparty's `to_self` output here.
386+
///
387+
/// This allows us to generate a [`Balance::CounterpartyRevokedOutputClaimable`] for the
388+
/// counterparty output.
385389
commitment_tx_to_counterparty_output: CommitmentTxCounterpartyOutputInfo,
386390
},
387391
/// A spend of a commitment transaction HTLC output, set in the cases where *no* `HTLCUpdate`
@@ -582,6 +586,18 @@ pub enum Balance {
582586
/// done so.
583587
claimable_height: u32,
584588
},
589+
/// The channel has been closed, and our counterparty broadcasted a revoked commitment
590+
/// transaction.
591+
///
592+
/// Thus, we're able to claim all outputs in the commitment transaction, one of which has the
593+
/// following amount.
594+
CounterpartyRevokedOutputClaimable {
595+
/// The amount, in satoshis, of the output which we can claim.
596+
///
597+
/// Note that for outputs from HTLC balances this may be excluding some on-chain fees that
598+
/// were already spent.
599+
claimable_amount_satoshis: u64,
600+
},
585601
}
586602

587603
/// An HTLC which has been irrevocably resolved on-chain, and has reached ANTI_REORG_DELAY.
@@ -1421,9 +1437,9 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14211437
/// balance, or until our counterparty has claimed the balance and accrued several
14221438
/// confirmations on the claim transaction.
14231439
///
1424-
/// Note that the balances available when you or your counterparty have broadcasted revoked
1425-
/// state(s) may not be fully captured here.
1426-
// TODO, fix that ^
1440+
/// Note that for `ChannelMonitors` which track a channel which went on-chain with versions of
1441+
/// LDK prior to 0.0.108, balances may not be fully captured if our counterparty broadcasted
1442+
/// a revoked state.
14271443
///
14281444
/// See [`Balance`] for additional details on the types of claimable balances which
14291445
/// may be returned here and their meanings.
@@ -1432,9 +1448,13 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14321448
let us = self.inner.lock().unwrap();
14331449

14341450
let mut confirmed_txid = us.funding_spend_confirmed;
1451+
let mut confirmed_counterparty_output = us.confirmed_commitment_tx_counterparty_output;
14351452
let mut pending_commitment_tx_conf_thresh = None;
14361453
let funding_spend_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1437-
if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
1454+
if let OnchainEvent::FundingSpendConfirmation { commitment_tx_to_counterparty_output, .. } =
1455+
event.event
1456+
{
1457+
confirmed_counterparty_output = commitment_tx_to_counterparty_output;
14381458
Some((event.txid, event.confirmation_threshold()))
14391459
} else { None }
14401460
});
@@ -1446,22 +1466,27 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14461466
}
14471467

14481468
macro_rules! walk_htlcs {
1449-
($holder_commitment: expr, $htlc_iter: expr) => {
1469+
($holder_commitment: expr, $counterparty_revoked_commitment: expr, $htlc_iter: expr) => {
14501470
for htlc in $htlc_iter {
14511471
if let Some(htlc_commitment_tx_output_idx) = htlc.transaction_output_index {
1472+
let mut htlc_spend_txid_opt = None;
14521473
let mut htlc_update_pending = None;
14531474
let mut htlc_spend_pending = None;
14541475
let mut delayed_output_pending = None;
14551476
for event in us.onchain_events_awaiting_threshold_conf.iter() {
14561477
match event.event {
14571478
OnchainEvent::HTLCUpdate { commitment_tx_output_idx, htlc_value_satoshis, .. }
14581479
if commitment_tx_output_idx == Some(htlc_commitment_tx_output_idx) => {
1480+
debug_assert!(htlc_spend_txid_opt.is_none());
1481+
htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
14591482
debug_assert!(htlc_update_pending.is_none());
14601483
debug_assert_eq!(htlc_value_satoshis.unwrap(), htlc.amount_msat / 1000);
14611484
htlc_update_pending = Some(event.confirmation_threshold());
14621485
},
14631486
OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. }
14641487
if commitment_tx_output_idx == htlc_commitment_tx_output_idx => {
1488+
debug_assert!(htlc_spend_txid_opt.is_none());
1489+
htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid());
14651490
debug_assert!(htlc_spend_pending.is_none());
14661491
htlc_spend_pending = Some((event.confirmation_threshold(), preimage.is_some()));
14671492
},
@@ -1475,22 +1500,69 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
14751500
}
14761501
}
14771502
let htlc_resolved = us.htlcs_resolved_on_chain.iter()
1478-
.find(|v| v.commitment_tx_output_idx == htlc_commitment_tx_output_idx);
1503+
.find(|v| if v.commitment_tx_output_idx == htlc_commitment_tx_output_idx {
1504+
debug_assert!(htlc_spend_txid_opt.is_none());
1505+
htlc_spend_txid_opt = v.resolving_txid;
1506+
true
1507+
} else { false });
14791508
debug_assert!(htlc_update_pending.is_some() as u8 + htlc_spend_pending.is_some() as u8 + htlc_resolved.is_some() as u8 <= 1);
14801509

1510+
let htlc_output_to_spend =
1511+
if let Some(txid) = htlc_spend_txid_opt {
1512+
debug_assert!(
1513+
us.onchain_tx_handler.channel_transaction_parameters.opt_anchors.is_none(),
1514+
"This code needs updating for anchors");
1515+
BitcoinOutPoint::new(txid, 0)
1516+
} else {
1517+
BitcoinOutPoint::new(confirmed_txid.unwrap(), htlc_commitment_tx_output_idx)
1518+
};
1519+
let htlc_output_needs_spending = us.onchain_tx_handler.is_output_spend_pending(&htlc_output_to_spend);
1520+
14811521
if let Some(conf_thresh) = delayed_output_pending {
14821522
debug_assert!($holder_commitment);
14831523
res.push(Balance::ClaimableAwaitingConfirmations {
14841524
claimable_amount_satoshis: htlc.amount_msat / 1000,
14851525
confirmation_height: conf_thresh,
14861526
});
1487-
} else if htlc_resolved.is_some() {
1527+
} else if htlc_resolved.is_some() && !htlc_output_needs_spending {
14881528
// Funding transaction spends should be fully confirmed by the time any
14891529
// HTLC transactions are resolved, unless we're talking about a holder
14901530
// commitment tx, whose resolution is delayed until the CSV timeout is
14911531
// reached, even though HTLCs may be resolved after only
14921532
// ANTI_REORG_DELAY confirmations.
14931533
debug_assert!($holder_commitment || us.funding_spend_confirmed.is_some());
1534+
} else if $counterparty_revoked_commitment {
1535+
let htlc_output_claim_pending = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
1536+
if let OnchainEvent::MaturingOutput {
1537+
descriptor: SpendableOutputDescriptor::StaticOutput { .. }
1538+
} = &event.event {
1539+
if event.transaction.as_ref().map(|tx| tx.input.iter().any(|inp| {
1540+
if let Some(htlc_spend_txid) = htlc_spend_txid_opt {
1541+
Some(tx.txid()) == htlc_spend_txid_opt ||
1542+
inp.previous_output.txid == htlc_spend_txid
1543+
} else {
1544+
Some(inp.previous_output.txid) == confirmed_txid &&
1545+
inp.previous_output.vout == htlc_commitment_tx_output_idx
1546+
}
1547+
})).unwrap_or(false) {
1548+
Some(())
1549+
} else { None }
1550+
} else { None }
1551+
});
1552+
if htlc_output_claim_pending.is_some() {
1553+
// We already push `Balance`s onto the `res` list for every
1554+
// `StaticOutput` in a `MaturingOutput` in the revoked
1555+
// counterparty commitment transaction case generally, so don't
1556+
// need to do so again here.
1557+
} else {
1558+
debug_assert!(htlc_update_pending.is_none(),
1559+
"HTLCUpdate OnchainEvents should never appear for preimage claims");
1560+
debug_assert!(!htlc.offered || htlc_spend_pending.is_none() || !htlc_spend_pending.unwrap().1,
1561+
"We don't (currently) generate preimage claims against revoked outputs, where did you get one?!");
1562+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1563+
claimable_amount_satoshis: htlc.amount_msat / 1000,
1564+
});
1565+
}
14941566
} else {
14951567
if htlc.offered == $holder_commitment {
14961568
// If the payment was outbound, check if there's an HTLCUpdate
@@ -1534,8 +1606,8 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15341606

15351607
if let Some(txid) = confirmed_txid {
15361608
let mut found_commitment_tx = false;
1537-
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1538-
walk_htlcs!(false, us.counterparty_claimable_outpoints.get(&txid).unwrap().iter().map(|(a, _)| a));
1609+
if let Some(counterparty_tx_htlcs) = us.counterparty_claimable_outpoints.get(&txid) {
1610+
// First look for the to_remote output back to us.
15391611
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15401612
if let Some(value) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
15411613
if let OnchainEvent::MaturingOutput {
@@ -1554,9 +1626,50 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15541626
// confirmation with the same height or have never met our dust amount.
15551627
}
15561628
}
1629+
if Some(txid) == us.current_counterparty_commitment_txid || Some(txid) == us.prev_counterparty_commitment_txid {
1630+
walk_htlcs!(false, false, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1631+
} else {
1632+
walk_htlcs!(false, true, counterparty_tx_htlcs.iter().map(|(a, _)| a));
1633+
// The counterparty broadcasted a revoked state!
1634+
// Look for any StaticOutputs first, generating claimable balances for those.
1635+
// If any match the confirmed counterparty revoked to_self output, skip
1636+
// generating a CounterpartyRevokedOutputClaimable.
1637+
let mut spent_counterparty_output = false;
1638+
for event in us.onchain_events_awaiting_threshold_conf.iter() {
1639+
if let OnchainEvent::MaturingOutput {
1640+
descriptor: SpendableOutputDescriptor::StaticOutput { output, .. }
1641+
} = &event.event {
1642+
res.push(Balance::ClaimableAwaitingConfirmations {
1643+
claimable_amount_satoshis: output.value,
1644+
confirmation_height: event.confirmation_threshold(),
1645+
});
1646+
if let Some(confirmed_to_self_idx) = confirmed_counterparty_output.map(|(idx, _)| idx) {
1647+
if event.transaction.as_ref().map(|tx|
1648+
tx.input.iter().any(|inp| inp.previous_output.vout == confirmed_to_self_idx)
1649+
).unwrap_or(false) {
1650+
spent_counterparty_output = true;
1651+
}
1652+
}
1653+
}
1654+
}
1655+
1656+
if spent_counterparty_output {
1657+
} else if let Some((confirmed_to_self_idx, amt)) = confirmed_counterparty_output {
1658+
let output_spendable = us.onchain_tx_handler
1659+
.is_output_spend_pending(&BitcoinOutPoint::new(txid, confirmed_to_self_idx));
1660+
if output_spendable {
1661+
res.push(Balance::CounterpartyRevokedOutputClaimable {
1662+
claimable_amount_satoshis: amt,
1663+
});
1664+
}
1665+
} else {
1666+
// Counterparty output is missing, either it was broadcasted on a
1667+
// previous version of LDK or the counterparty hadn't met dust.
1668+
}
1669+
}
15571670
found_commitment_tx = true;
15581671
} else if txid == us.current_holder_commitment_tx.txid {
1559-
walk_htlcs!(true, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
1672+
walk_htlcs!(true, false, us.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, _)| a));
15601673
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15611674
res.push(Balance::ClaimableAwaitingConfirmations {
15621675
claimable_amount_satoshis: us.current_holder_commitment_tx.to_self_value_sat,
@@ -1566,7 +1679,7 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15661679
found_commitment_tx = true;
15671680
} else if let Some(prev_commitment) = &us.prev_holder_signed_commitment_tx {
15681681
if txid == prev_commitment.txid {
1569-
walk_htlcs!(true, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
1682+
walk_htlcs!(true, false, prev_commitment.htlc_outputs.iter().map(|(a, _, _)| a));
15701683
if let Some(conf_thresh) = pending_commitment_tx_conf_thresh {
15711684
res.push(Balance::ClaimableAwaitingConfirmations {
15721685
claimable_amount_satoshis: prev_commitment.to_self_value_sat,
@@ -1587,8 +1700,6 @@ impl<Signer: Sign> ChannelMonitor<Signer> {
15871700
});
15881701
}
15891702
}
1590-
// TODO: Add logic to provide claimable balances for counterparty broadcasting revoked
1591-
// outputs.
15921703
} else {
15931704
let mut claimable_inbound_htlc_value_sat = 0;
15941705
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
@@ -691,6 +691,10 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
691691
}
692692
}
693693

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

lightning/src/ln/functional_test_utils.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,13 +1534,11 @@ macro_rules! expect_payment_failed {
15341534
};
15351535
}
15361536

1537-
pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(
1538-
node: &'a Node<'b, 'c, 'd>, expected_payment_hash: PaymentHash, expected_rejected_by_dest: bool,
1539-
conditions: PaymentFailedConditions<'e>
1537+
pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>(
1538+
node: &'a Node<'b, 'c, 'd>, payment_failed_event: Event, expected_payment_hash: PaymentHash,
1539+
expected_rejected_by_dest: bool, conditions: PaymentFailedConditions<'e>
15401540
) {
1541-
let mut events = node.node.get_and_clear_pending_events();
1542-
assert_eq!(events.len(), 1);
1543-
let expected_payment_id = match events.pop().unwrap() {
1541+
let expected_payment_id = match payment_failed_event {
15441542
Event::PaymentPathFailed { payment_hash, rejected_by_dest, path, retry, payment_id, network_update, short_channel_id,
15451543
#[cfg(test)]
15461544
error_code,
@@ -1603,6 +1601,15 @@ pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(
16031601
}
16041602
}
16051603

1604+
pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(
1605+
node: &'a Node<'b, 'c, 'd>, expected_payment_hash: PaymentHash, expected_rejected_by_dest: bool,
1606+
conditions: PaymentFailedConditions<'e>
1607+
) {
1608+
let mut events = node.node.get_and_clear_pending_events();
1609+
assert_eq!(events.len(), 1);
1610+
expect_payment_failed_conditions_event(node, events.pop().unwrap(), expected_payment_hash, expected_rejected_by_dest, conditions);
1611+
}
1612+
16061613
pub fn send_along_route_with_secret<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, route: Route, expected_paths: &[&[&Node<'a, 'b, 'c>]], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: PaymentSecret) -> PaymentId {
16071614
let payment_id = origin_node.node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
16081615
check_added_monitors!(origin_node, expected_paths.len());

0 commit comments

Comments
 (0)