Skip to content

Commit 8a6a3ce

Browse files
committed
f handle non-latest transactions confirming correctly and update test
1 parent c34a63b commit 8a6a3ce

File tree

3 files changed

+166
-62
lines changed

3 files changed

+166
-62
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2986,35 +2986,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
29862986
/// Gets the set of outbound HTLCs which hit the chain and ultimately were claimed by us via
29872987
/// the timeout path and reached [`ANTI_REORG_DELAY`] confirmations. This is used to determine
29882988
/// if an HTLC has failed without the `ChannelManager` having seen it prior to being persisted.
2989-
pub(crate) fn get_onchain_failed_outbound_htlcs(
2990-
&self,
2991-
) -> HashMap<HTLCSource, HTLCOutputInCommitment> {
2989+
pub(crate) fn get_onchain_failed_outbound_htlcs(&self) -> HashMap<HTLCSource, PaymentHash> {
29922990
let mut res = new_hash_map();
29932991
let us = self.inner.lock().unwrap();
2994-
macro_rules! walk_htlcs {
2995-
($holder_commitment: expr, $htlc_iter: expr) => {
2996-
for (htlc, source) in $htlc_iter {
2997-
let filter = |v: &&IrrevocablyResolvedHTLC| {
2998-
v.commitment_tx_output_idx == htlc.transaction_output_index
2999-
};
3000-
if htlc.transaction_output_index.is_none() {
3001-
if let Some(source) = source {
3002-
// Dust HTLCs are always implicitly failed once the commitment
3003-
// transaction reaches ANTI_REORG_DELAY confirmations.
3004-
res.insert(source.clone(), htlc.clone());
3005-
}
3006-
} else if let Some(state) =
3007-
us.htlcs_resolved_on_chain.iter().filter(filter).next()
3008-
{
3009-
if let Some(source) = source {
3010-
if state.payment_preimage.is_none() {
3011-
res.insert(source.clone(), htlc.clone());
3012-
}
3013-
}
3014-
}
3015-
}
3016-
};
3017-
}
30182992

30192993
// We only want HTLCs with ANTI_REORG_DELAY confirmations, which implies the commitment
30202994
// transaction has least ANTI_REORG_DELAY confirmations for any dependent HTLC transactions
@@ -3033,18 +3007,75 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
30333007
})
30343008
});
30353009

3036-
let txid = if let Some(txid) = confirmed_txid {
3010+
let confirmed_txid = if let Some(txid) = confirmed_txid {
30373011
txid
30383012
} else {
30393013
return res;
30403014
};
30413015

3042-
if Some(txid) == us.funding.current_counterparty_commitment_txid
3043-
|| Some(txid) == us.funding.prev_counterparty_commitment_txid
3016+
macro_rules! walk_htlcs {
3017+
($holder_commitment: expr, $htlc_iter: expr) => {
3018+
let mut walk_candidate_htlcs = |htlcs| {
3019+
for &(ref candidate_htlc, ref candidate_source) in htlcs {
3020+
let candidate_htlc: &HTLCOutputInCommitment = &candidate_htlc;
3021+
let candidate_source: &Option<Box<HTLCSource>> = &candidate_source;
3022+
3023+
let source: &HTLCSource = if let Some(source) = candidate_source {
3024+
&*source
3025+
} else {
3026+
continue;
3027+
};
3028+
let confirmed = $htlc_iter.find(|(_, conf_src)| Some(source) == *conf_src);
3029+
if let Some((confirmed_htlc, _)) = confirmed {
3030+
let filter = |v: &&IrrevocablyResolvedHTLC| {
3031+
v.commitment_tx_output_idx
3032+
== confirmed_htlc.transaction_output_index
3033+
};
3034+
3035+
// The HTLC was included in the confirmed commitment transaction, so we
3036+
// need to see if it has been irrevocably failed yet.
3037+
if confirmed_htlc.transaction_output_index.is_none() {
3038+
// Dust HTLCs are always implicitly failed once the commitment
3039+
// transaction reaches ANTI_REORG_DELAY confirmations.
3040+
res.insert(source.clone(), confirmed_htlc.payment_hash);
3041+
} else if let Some(state) =
3042+
us.htlcs_resolved_on_chain.iter().filter(filter).next()
3043+
{
3044+
if state.payment_preimage.is_none() {
3045+
res.insert(source.clone(), confirmed_htlc.payment_hash);
3046+
}
3047+
}
3048+
} else {
3049+
// The HTLC was not included in the confirmed commitment transaction,
3050+
// which has now reached ANTI_REORG_DELAY confirmations and thus the
3051+
// HTLC has been failed.
3052+
res.insert(source.clone(), candidate_htlc.payment_hash);
3053+
}
3054+
}
3055+
};
3056+
3057+
// We walk the set of HTLCs in the unrevoked counterparty commitment transactions (see
3058+
// `fail_unbroadcast_htlcs` for a description of why).
3059+
if let Some(ref txid) = us.funding.current_counterparty_commitment_txid {
3060+
if let Some(htlcs) = us.funding.counterparty_claimable_outpoints.get(txid) {
3061+
walk_candidate_htlcs(htlcs);
3062+
}
3063+
}
3064+
if let Some(ref txid) = us.funding.prev_counterparty_commitment_txid {
3065+
if let Some(htlcs) = us.funding.counterparty_claimable_outpoints.get(txid) {
3066+
walk_candidate_htlcs(htlcs);
3067+
}
3068+
}
3069+
};
3070+
}
3071+
3072+
if Some(confirmed_txid) == us.funding.current_counterparty_commitment_txid
3073+
|| Some(confirmed_txid) == us.funding.prev_counterparty_commitment_txid
30443074
{
3075+
let htlcs = funding.counterparty_claimable_outpoints.get(&confirmed_txid).unwrap();
30453076
walk_htlcs!(
30463077
false,
3047-
us.funding.counterparty_claimable_outpoints.get(&txid).unwrap().iter().filter_map(
3078+
htlcs.iter().filter_map(
30483079
|(a, b)| {
30493080
if let &Some(ref source) = b {
30503081
Some((a, Some(&**source)))
@@ -3054,12 +3085,18 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
30543085
}
30553086
)
30563087
);
3057-
} else if txid == us.funding.current_holder_commitment_tx.trust().txid() {
3088+
} else if confirmed_txid == us.funding.current_holder_commitment_tx.trust().txid() {
30583089
walk_htlcs!(true, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES));
30593090
} else if let Some(prev_commitment_tx) = &us.funding.prev_holder_commitment_tx {
3060-
if txid == prev_commitment_tx.trust().txid() {
3091+
if confirmed_txid == prev_commitment_tx.trust().txid() {
30613092
walk_htlcs!(true, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap());
3093+
} else {
3094+
let htlcs_confirmed: &[(&HTLCOutputInCommitment, _)] = &[];
3095+
walk_htlcs!(false, htlcs_confirmed.iter());
30623096
}
3097+
} else {
3098+
let htlcs_confirmed: &[(&HTLCOutputInCommitment, _)] = &[];
3099+
walk_htlcs!(false, htlcs_confirmed.iter());
30633100
}
30643101

30653102
res

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16541,15 +16541,15 @@ where
1654116541
},
1654216542
}
1654316543
}
16544-
for (htlc_source, htlc) in monitor.get_onchain_failed_outbound_htlcs() {
16544+
for (htlc_source, payment_hash) in monitor.get_onchain_failed_outbound_htlcs() {
1654516545
log_info!(
1654616546
args.logger,
1654716547
"Failing HTLC with payment hash {} as it was resolved on-chain.",
16548-
htlc.payment_hash
16548+
payment_hash
1654916549
);
1655016550
failed_htlcs.push((
1655116551
htlc_source,
16552-
htlc.payment_hash,
16552+
payment_hash,
1655316553
monitor.get_counterparty_node_id(),
1655416554
monitor.channel_id(),
1655516555
LocalHTLCFailureReason::OnChainTimeout,

lightning/src/ln/monitor_tests.rs

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,7 +3464,16 @@ fn test_lost_preimage_monitor_events() {
34643464
do_test_lost_preimage_monitor_events(false);
34653465
}
34663466

3467-
fn do_test_lost_timeout_monitor_events(on_counterparty_tx: bool, dust_htlcs: bool) {
3467+
#[derive(PartialEq)]
3468+
enum CommitmentType {
3469+
RevokedCounterparty,
3470+
LatestCounterparty,
3471+
PreviousCounterparty,
3472+
LocalWithoutLastHTLC,
3473+
LocalWithLastHTLC,
3474+
}
3475+
3476+
fn do_test_lost_timeout_monitor_events(confirm_tx: CommitmentType, dust_htlcs: bool) {
34683477
// `MonitorEvent`s aren't delivered to the `ChannelManager` in a durable fasion - if the
34693478
// `ChannelManager` fetches the pending `MonitorEvent`s, then the `ChannelMonitor` gets
34703479
// persisted (i.e. due to a block update) then the node crashes, prior to persisting the
@@ -3511,9 +3520,37 @@ fn do_test_lost_timeout_monitor_events(on_counterparty_tx: bool, dust_htlcs: boo
35113520

35123521
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 25_000_000);
35133522

3523+
let cs_revoked_commit = get_local_commitment_txn!(nodes[2], chan_b);
3524+
assert_eq!(cs_revoked_commit.len(), 1);
3525+
35143526
let amt = if dust_htlcs { 1_000 } else { 10_000_000 };
35153527
let (_, hash_a, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], amt);
3516-
let (_, hash_b, ..) = route_payment(&nodes[1], &[&nodes[2]], amt);
3528+
3529+
let cs_previous_commit = get_local_commitment_txn!(nodes[2], chan_b);
3530+
assert_eq!(cs_previous_commit.len(), 1);
3531+
3532+
let (route, hash_b, _, payment_secret_b) =
3533+
get_route_and_payment_hash!(nodes[1], nodes[2], amt);
3534+
let onion = RecipientOnionFields::secret_only(payment_secret_b);
3535+
nodes[1].node.send_payment_with_route(route, hash_b, onion, PaymentId(hash_b.0)).unwrap();
3536+
check_added_monitors(&nodes[1], 1);
3537+
3538+
let updates = get_htlc_update_msgs(&nodes[1], &node_c_id);
3539+
nodes[2].node.handle_update_add_htlc(node_b_id, &updates.update_add_htlcs[0]);
3540+
nodes[2].node.handle_commitment_signed_batch_test(node_b_id, &updates.commitment_signed);
3541+
check_added_monitors(&nodes[2], 1);
3542+
3543+
let (cs_raa, cs_cs) = get_revoke_commit_msgs!(nodes[2], node_b_id);
3544+
if confirm_tx == CommitmentType::LocalWithLastHTLC {
3545+
// Only deliver the last RAA + CS if we need to update the local commitment with the third
3546+
// HTLC.
3547+
nodes[1].node.handle_revoke_and_ack(node_c_id, &cs_raa);
3548+
check_added_monitors(&nodes[1], 1);
3549+
nodes[1].node.handle_commitment_signed_batch_test(node_c_id, &cs_cs);
3550+
check_added_monitors(&nodes[1], 1);
3551+
3552+
let _bs_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, node_c_id);
3553+
}
35173554

35183555
nodes[1].node.peer_disconnected(nodes[2].node.get_our_node_id());
35193556
nodes[2].node.peer_disconnected(nodes[1].node.get_our_node_id());
@@ -3547,44 +3584,68 @@ fn do_test_lost_timeout_monitor_events(on_counterparty_tx: bool, dust_htlcs: boo
35473584
let bs_commit_tx = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
35483585
assert_eq!(bs_commit_tx.len(), 1);
35493586

3550-
let selected_commit_tx = if on_counterparty_tx {
3551-
&cs_commit_tx[0]
3552-
} else {
3553-
&bs_commit_tx[0]
3587+
let selected_commit_tx = match confirm_tx {
3588+
CommitmentType::RevokedCounterparty => &cs_revoked_commit[0],
3589+
CommitmentType::PreviousCounterparty => &cs_previous_commit[0],
3590+
CommitmentType::LatestCounterparty => &cs_commit_tx[0],
3591+
CommitmentType::LocalWithoutLastHTLC|CommitmentType::LocalWithLastHTLC => &bs_commit_tx[0],
35543592
};
35553593

35563594
mine_transaction(&nodes[1], selected_commit_tx);
35573595
// If the block gets connected first we may re-broadcast B's commitment transaction before
3558-
// seeing the C's confirm.
3559-
nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
3596+
// seeing the C's confirm. In any case, if we confirmed the revoked counterparty commitment
3597+
// transaction, we want to go ahead and confirm the spend of it.
3598+
let bs_transactions = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
3599+
if confirm_tx == CommitmentType::RevokedCounterparty {
3600+
assert!(bs_transactions.len() == 1 || bs_transactions.len() == 2);
3601+
mine_transaction(&nodes[1], bs_transactions.last().unwrap());
3602+
} else {
3603+
assert!(bs_transactions.len() == 1 || bs_transactions.len() == 0);
3604+
}
3605+
35603606
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
35613607
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
3562-
if on_counterparty_tx {
3563-
assert_eq!(events.len(), 1, "{events:?}");
3564-
match events[0] {
3565-
Event::SpendableOutputs { .. } => {},
3566-
_ => panic!("Unexpected event {events:?}"),
3567-
}
3568-
} else {
3569-
assert_eq!(events.len(), 0);
3608+
match confirm_tx {
3609+
CommitmentType::LocalWithoutLastHTLC|CommitmentType::LocalWithLastHTLC => {
3610+
assert_eq!(events.len(), 0, "{events:?}");
3611+
},
3612+
CommitmentType::PreviousCounterparty|CommitmentType::LatestCounterparty => {
3613+
assert_eq!(events.len(), 1, "{events:?}");
3614+
match events[0] {
3615+
Event::SpendableOutputs { .. } => {},
3616+
_ => panic!("Unexpected event {events:?}"),
3617+
}
3618+
},
3619+
CommitmentType::RevokedCounterparty => {
3620+
assert_eq!(events.len(), 2, "{events:?}");
3621+
for event in events {
3622+
match event {
3623+
Event::SpendableOutputs { .. } => {},
3624+
_ => panic!("Unexpected event {event:?}"),
3625+
}
3626+
}
3627+
},
35703628
}
35713629

3572-
connect_blocks(&nodes[1], TEST_FINAL_CLTV - ANTI_REORG_DELAY + 1);
3573-
if !on_counterparty_tx {
3574-
if !dust_htlcs {
3575-
handle_bump_events(&nodes[1], false, 1);
3630+
if confirm_tx != CommitmentType::RevokedCounterparty {
3631+
connect_blocks(&nodes[1], TEST_FINAL_CLTV - ANTI_REORG_DELAY + 1);
3632+
if confirm_tx == CommitmentType::LocalWithoutLastHTLC || confirm_tx == CommitmentType::LocalWithLastHTLC {
3633+
if !dust_htlcs {
3634+
handle_bump_events(&nodes[1], false, 1);
3635+
}
35763636
}
35773637
}
3638+
35783639
let bs_htlc_timeouts =
35793640
nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
3580-
if dust_htlcs {
3641+
if dust_htlcs || confirm_tx == CommitmentType::RevokedCounterparty {
35813642
assert_eq!(bs_htlc_timeouts.len(), 0);
35823643
} else {
35833644
assert_eq!(bs_htlc_timeouts.len(), 1);
35843645

35853646
// Now replay the timeouts on node B, which after 6 confirmations should fail the HTLCs via
35863647
// `MonitorUpdate`s
3587-
mine_transactions(&nodes[1], &bs_htlc_timeouts.iter().collect::<Vec<_>>());
3648+
mine_transaction(&nodes[1], &bs_htlc_timeouts[0]);
35883649
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
35893650
}
35903651

@@ -3637,8 +3698,14 @@ fn do_test_lost_timeout_monitor_events(on_counterparty_tx: bool, dust_htlcs: boo
36373698

36383699
#[test]
36393700
fn test_lost_timeout_monitor_events() {
3640-
do_test_lost_timeout_monitor_events(true, false);
3641-
do_test_lost_timeout_monitor_events(false, false);
3642-
do_test_lost_timeout_monitor_events(true, true);
3643-
do_test_lost_timeout_monitor_events(false, true);
3701+
do_test_lost_timeout_monitor_events(CommitmentType::RevokedCounterparty, false);
3702+
do_test_lost_timeout_monitor_events(CommitmentType::RevokedCounterparty, true);
3703+
do_test_lost_timeout_monitor_events(CommitmentType::PreviousCounterparty, false);
3704+
do_test_lost_timeout_monitor_events(CommitmentType::PreviousCounterparty, true);
3705+
do_test_lost_timeout_monitor_events(CommitmentType::LatestCounterparty, false);
3706+
do_test_lost_timeout_monitor_events(CommitmentType::LatestCounterparty, true);
3707+
do_test_lost_timeout_monitor_events(CommitmentType::LocalWithoutLastHTLC, false);
3708+
do_test_lost_timeout_monitor_events(CommitmentType::LocalWithoutLastHTLC, true);
3709+
do_test_lost_timeout_monitor_events(CommitmentType::LocalWithLastHTLC, false);
3710+
do_test_lost_timeout_monitor_events(CommitmentType::LocalWithLastHTLC, true);
36443711
}

0 commit comments

Comments
 (0)