From 24577344fa4eb31b41e1fb2d42787bec409b8ceb Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 21 Dec 2018 14:32:44 -0500 Subject: [PATCH 1/5] Move fail-backwards up for no to-remote output claims This fixes HTLC fail-backwards in case we haven't yet sent enough to have a to_remote output to claim, plus some edge cases where it could be removed due to a fee update, though hopefully that goes away with simplified_commitment. --- src/ln/channelmonitor.rs | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/ln/channelmonitor.rs b/src/ln/channelmonitor.rs index 13cdfd2fe..ecb9f3a18 100644 --- a/src/ln/channelmonitor.rs +++ b/src/ln/channelmonitor.rs @@ -1179,11 +1179,34 @@ impl ChannelMonitor { } } - if !inputs.is_empty() || !txn_to_broadcast.is_empty() { // ie we're confident this is actually ours + if !inputs.is_empty() || !txn_to_broadcast.is_empty() || per_commitment_option.is_some() { // ie we're confident this is actually ours // We're definitely a remote commitment transaction! log_trace!(self, "Got broadcast of revoked remote commitment transaction, generating general spend tx with {} inputs and {} other txn to broadcast", inputs.len(), txn_to_broadcast.len()); watch_outputs.append(&mut tx.output.clone()); self.remote_commitment_txn_on_chain.insert(commitment_txid, (commitment_number, tx.output.iter().map(|output| { output.script_pubkey.clone() }).collect())); + + // TODO: We really should only fail backwards after our revocation claims have been + // confirmed, but we also need to do more other tracking of in-flight pre-confirm + // on-chain claims, so we can do that at the same time. + if let Storage::Local { ref current_remote_commitment_txid, ref prev_remote_commitment_txid, .. } = self.key_storage { + if let &Some(ref txid) = current_remote_commitment_txid { + if let Some(&(_, ref latest_outpoints)) = self.remote_claimable_outpoints.get(&txid) { + for &(ref payment_hash, ref source, _) in latest_outpoints.iter() { + log_trace!(self, "Failing HTLC with payment_hash {} from current remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); + htlc_updated.push(((*source).clone(), None, payment_hash.clone())); + } + } + } + if let &Some(ref txid) = prev_remote_commitment_txid { + if let Some(&(_, ref prev_outpoint)) = self.remote_claimable_outpoints.get(&txid) { + for &(ref payment_hash, ref source, _) in prev_outpoint.iter() { + log_trace!(self, "Failing HTLC with payment_hash {} from previous remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); + htlc_updated.push(((*source).clone(), None, payment_hash.clone())); + } + } + } + } + // No need to check local commitment txn, symmetric HTLCSource must be present as per-htlc data on remote commitment tx } if inputs.is_empty() { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs, htlc_updated); } // Nothing to be done...probably a false positive/local tx @@ -1211,29 +1234,6 @@ impl ChannelMonitor { output: spend_tx.output[0].clone(), }); txn_to_broadcast.push(spend_tx); - - // TODO: We really should only fail backwards after our revocation claims have been - // confirmed, but we also need to do more other tracking of in-flight pre-confirm - // on-chain claims, so we can do that at the same time. - if let Storage::Local { ref current_remote_commitment_txid, ref prev_remote_commitment_txid, .. } = self.key_storage { - if let &Some(ref txid) = current_remote_commitment_txid { - if let Some(&(_, ref latest_outpoints)) = self.remote_claimable_outpoints.get(&txid) { - for &(ref payment_hash, ref source, _) in latest_outpoints.iter() { - log_trace!(self, "Failing HTLC with payment_hash {} from current remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); - htlc_updated.push(((*source).clone(), None, payment_hash.clone())); - } - } - } - if let &Some(ref txid) = prev_remote_commitment_txid { - if let Some(&(_, ref prev_outpoint)) = self.remote_claimable_outpoints.get(&txid) { - for &(ref payment_hash, ref source, _) in prev_outpoint.iter() { - log_trace!(self, "Failing HTLC with payment_hash {} from previous remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); - htlc_updated.push(((*source).clone(), None, payment_hash.clone())); - } - } - } - } - // No need to check local commitment txn, symmetric HTLCSource must be present as per-htlc data on remote commitment tx } else if let Some(per_commitment_data) = per_commitment_option { // While this isn't useful yet, there is a potential race where if a counterparty // revokes a state at the same time as the commitment transaction for that state is From dbbf4914ea8eb3d484dcad9b835ebf90509ee29b Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 27 Dec 2018 13:53:58 -0500 Subject: [PATCH 2/5] Add dust mode to test_commitment_revoked_fail_backward_exhaustive This tests that we fail back HTLCs that don't make it into commitment transactions the same as we test ones that do. --- src/ln/functional_tests.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ln/functional_tests.rs b/src/ln/functional_tests.rs index 3a8e3b861..99bad11d1 100644 --- a/src/ln/functional_tests.rs +++ b/src/ln/functional_tests.rs @@ -2951,7 +2951,7 @@ fn test_simple_commitment_revoked_fail_backward() { } } -fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool) { +fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use_dust: bool) { // Test that if our counterparty broadcasts a revoked commitment transaction we fail all // pending HTLCs on that channel backwards even if the HTLCs aren't present in our latest // commitment transaction anymore. @@ -2979,9 +2979,15 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool) { // Revoke the old state claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); - let (_, first_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000); - let (_, second_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000); - let (_, third_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000); + let value = if use_dust { + // The dust limit applied to HTLC outputs considers the fee of the HTLC transaction as + // well, so HTLCs at exactly the dust limit will not be included in commitment txn. + nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().our_dust_limit_satoshis * 1000 + } else { 3000000 }; + + let (_, first_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value); + let (_, second_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value); + let (_, third_payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], value); assert!(nodes[2].node.fail_htlc_backwards(&first_payment_hash, 0)); expect_pending_htlcs_forwardable!(nodes[2]); @@ -3153,8 +3159,10 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool) { #[test] fn test_commitment_revoked_fail_backward_exhaustive() { - do_test_commitment_revoked_fail_backward_exhaustive(false); - do_test_commitment_revoked_fail_backward_exhaustive(true); + do_test_commitment_revoked_fail_backward_exhaustive(false, true); + do_test_commitment_revoked_fail_backward_exhaustive(true, true); + do_test_commitment_revoked_fail_backward_exhaustive(false, false); + do_test_commitment_revoked_fail_backward_exhaustive(true, false); } #[test] From e344b0a8fe009ffc95261bccf02d68192c32a047 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 27 Dec 2018 13:55:40 -0500 Subject: [PATCH 3/5] Add test_commitment_revoked_fail_backward_exhaustive no_to_remote This tests a case we previously didn't handle correctly where we returned early if there was no to_remote output to claim and thus failed to fail-backwards HTLCs which were present. --- src/ln/functional_tests.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ln/functional_tests.rs b/src/ln/functional_tests.rs index 99bad11d1..9677b28e5 100644 --- a/src/ln/functional_tests.rs +++ b/src/ln/functional_tests.rs @@ -2951,7 +2951,7 @@ fn test_simple_commitment_revoked_fail_backward() { } } -fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use_dust: bool) { +fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use_dust: bool, no_to_remote: bool) { // Test that if our counterparty broadcasts a revoked commitment transaction we fail all // pending HTLCs on that channel backwards even if the HTLCs aren't present in our latest // commitment transaction anymore. @@ -2973,9 +2973,10 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use create_announced_chan_between_nodes(&nodes, 0, 1); let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2); - let (payment_preimage, _payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 3000000); + let (payment_preimage, _payment_hash) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], if no_to_remote { 10_000 } else { 3_000_000 }); // Get the will-be-revoked local txn from nodes[2] let revoked_local_txn = nodes[2].node.channel_state.lock().unwrap().by_id.get(&chan_2.2).unwrap().last_local_commitment_txn.clone(); + assert_eq!(revoked_local_txn[0].output.len(), if no_to_remote { 1 } else { 2 }); // Revoke the old state claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); @@ -3158,11 +3159,19 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use } #[test] -fn test_commitment_revoked_fail_backward_exhaustive() { - do_test_commitment_revoked_fail_backward_exhaustive(false, true); - do_test_commitment_revoked_fail_backward_exhaustive(true, true); - do_test_commitment_revoked_fail_backward_exhaustive(false, false); - do_test_commitment_revoked_fail_backward_exhaustive(true, false); +fn test_commitment_revoked_fail_backward_exhaustive_a() { + do_test_commitment_revoked_fail_backward_exhaustive(false, true, false); + do_test_commitment_revoked_fail_backward_exhaustive(true, true, false); + do_test_commitment_revoked_fail_backward_exhaustive(false, false, false); + do_test_commitment_revoked_fail_backward_exhaustive(true, false, false); +} + +#[test] +fn test_commitment_revoked_fail_backward_exhaustive_b() { + do_test_commitment_revoked_fail_backward_exhaustive(false, true, true); + do_test_commitment_revoked_fail_backward_exhaustive(true, true, true); + do_test_commitment_revoked_fail_backward_exhaustive(false, false, true); + do_test_commitment_revoked_fail_backward_exhaustive(true, false, true); } #[test] From 913d56ecabc25c5323c7c0d789a728e6e89e4c22 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 27 Dec 2018 14:12:11 -0500 Subject: [PATCH 4/5] Macro-DRY HTLC back-fails for revoked remote transaction broadcast --- src/ln/channelmonitor.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ln/channelmonitor.rs b/src/ln/channelmonitor.rs index ecb9f3a18..9cb0e531a 100644 --- a/src/ln/channelmonitor.rs +++ b/src/ln/channelmonitor.rs @@ -1188,22 +1188,22 @@ impl ChannelMonitor { // TODO: We really should only fail backwards after our revocation claims have been // confirmed, but we also need to do more other tracking of in-flight pre-confirm // on-chain claims, so we can do that at the same time. - if let Storage::Local { ref current_remote_commitment_txid, ref prev_remote_commitment_txid, .. } = self.key_storage { - if let &Some(ref txid) = current_remote_commitment_txid { - if let Some(&(_, ref latest_outpoints)) = self.remote_claimable_outpoints.get(&txid) { - for &(ref payment_hash, ref source, _) in latest_outpoints.iter() { - log_trace!(self, "Failing HTLC with payment_hash {} from current remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); + macro_rules! check_htlc_fails { + ($txid: expr, $commitment_tx: expr) => { + if let Some(&(_, ref outpoints)) = self.remote_claimable_outpoints.get(&$txid) { + for &(ref payment_hash, ref source, _) in outpoints.iter() { + log_trace!(self, "Failing HTLC with payment_hash {} from {} remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0), $commitment_tx); htlc_updated.push(((*source).clone(), None, payment_hash.clone())); } } } + } + if let Storage::Local { ref current_remote_commitment_txid, ref prev_remote_commitment_txid, .. } = self.key_storage { + if let &Some(ref txid) = current_remote_commitment_txid { + check_htlc_fails!(txid, "current"); + } if let &Some(ref txid) = prev_remote_commitment_txid { - if let Some(&(_, ref prev_outpoint)) = self.remote_claimable_outpoints.get(&txid) { - for &(ref payment_hash, ref source, _) in prev_outpoint.iter() { - log_trace!(self, "Failing HTLC with payment_hash {} from previous remote commitment tx due to broadcast of revoked remote commitment transaction", log_bytes!(payment_hash.0)); - htlc_updated.push(((*source).clone(), None, payment_hash.clone())); - } - } + check_htlc_fails!(txid, "remote"); } } // No need to check local commitment txn, symmetric HTLCSource must be present as per-htlc data on remote commitment tx From e662fa14af7c978f2f6a7daf8ae7960d07621e7b Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 5 Jan 2019 16:18:09 -0500 Subject: [PATCH 5/5] Fix stale comment in functional_tests after failing back async --- src/ln/functional_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ln/functional_tests.rs b/src/ln/functional_tests.rs index 9677b28e5..4baaf64d8 100644 --- a/src/ln/functional_tests.rs +++ b/src/ln/functional_tests.rs @@ -3050,8 +3050,8 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use if deliver_bs_raa { nodes[1].node.handle_revoke_and_ack(&nodes[2].node.get_our_node_id(), &bs_raa).unwrap(); - // One monitor for the new revocation preimage, one as we generate a commitment for - // nodes[0] to fail first_payment_hash backwards. + // One monitor for the new revocation preimage, no second on as we won't generate a new + // commitment transaction for nodes[0] until process_pending_htlc_forwards(). check_added_monitors!(nodes[1], 1); let events = nodes[1].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1);