mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
Automatically fail intercepts back on timeout
This commit is contained in:
parent
ddcd9b0463
commit
7809c5515c
2 changed files with 73 additions and 12 deletions
|
@ -3067,6 +3067,9 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
|
|||
/// Note that LDK does not enforce fee requirements in `amt_to_forward_msat`, and will not stop
|
||||
/// you from forwarding more than you received.
|
||||
///
|
||||
/// Errors if the event was not handled in time, in which case the HTLC was automatically failed
|
||||
/// backwards.
|
||||
///
|
||||
/// [`UserConfig::accept_intercept_htlcs`]: crate::util::config::UserConfig::accept_intercept_htlcs
|
||||
/// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
|
||||
// TODO: when we move to deciding the best outbound channel at forward time, only take
|
||||
|
@ -3109,6 +3112,9 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
|
|||
/// Fails the intercepted HTLC indicated by intercept_id. Should only be called in response to
|
||||
/// an [`HTLCIntercepted`] event. See [`ChannelManager::forward_intercepted_htlc`].
|
||||
///
|
||||
/// Errors if the event was not handled in time, in which case the HTLC was automatically failed
|
||||
/// backwards.
|
||||
///
|
||||
/// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
|
||||
pub fn fail_intercepted_htlc(&self, intercept_id: InterceptId) -> Result<(), APIError> {
|
||||
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
|
||||
|
@ -6256,7 +6262,6 @@ where
|
|||
if height >= htlc.cltv_expiry - HTLC_FAIL_BACK_BUFFER {
|
||||
let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
|
||||
htlc_msat_height_data.extend_from_slice(&byte_utils::be32_to_array(height));
|
||||
|
||||
timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.prev_hop.clone()), payment_hash.clone(), HTLCFailReason::Reason {
|
||||
failure_code: 0x4000 | 15,
|
||||
data: htlc_msat_height_data
|
||||
|
@ -6266,6 +6271,29 @@ where
|
|||
});
|
||||
!htlcs.is_empty() // Only retain this entry if htlcs has at least one entry.
|
||||
});
|
||||
|
||||
let mut intercepted_htlcs = self.pending_intercepted_htlcs.lock().unwrap();
|
||||
intercepted_htlcs.retain(|_, htlc| {
|
||||
if height >= htlc.forward_info.outgoing_cltv_value - HTLC_FAIL_BACK_BUFFER {
|
||||
let prev_hop_data = HTLCSource::PreviousHopData(HTLCPreviousHopData {
|
||||
short_channel_id: htlc.prev_short_channel_id,
|
||||
htlc_id: htlc.prev_htlc_id,
|
||||
incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret,
|
||||
phantom_shared_secret: None,
|
||||
outpoint: htlc.prev_funding_outpoint,
|
||||
});
|
||||
|
||||
let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing {
|
||||
PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
timed_out_htlcs.push((prev_hop_data, htlc.forward_info.payment_hash,
|
||||
HTLCFailReason::Reason { failure_code: 0x2000 | 2, data: Vec::new() },
|
||||
HTLCDestination::InvalidForward { requested_forward_scid }));
|
||||
log_trace!(self.logger, "Timing out intercepted HTLC with requested forward scid {}", requested_forward_scid);
|
||||
false
|
||||
} else { true }
|
||||
});
|
||||
}
|
||||
|
||||
self.handle_init_event_channel_failures(failed_channels);
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS
|
|||
use crate::chain::transaction::OutPoint;
|
||||
use crate::chain::keysinterface::KeysInterface;
|
||||
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
|
||||
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, InterceptId, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
|
||||
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
|
||||
use crate::ln::msgs;
|
||||
use crate::ln::msgs::ChannelMessageHandler;
|
||||
use crate::routing::gossip::RoutingFees;
|
||||
|
@ -1243,6 +1243,13 @@ fn abandoned_send_payment_idempotent() {
|
|||
claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum InterceptTest {
|
||||
Forward,
|
||||
Fail,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trivial_inflight_htlc_tracking(){
|
||||
// In this test, we test three scenarios:
|
||||
|
@ -1378,11 +1385,13 @@ fn intercepted_payment() {
|
|||
// Test that detecting an intercept scid on payment forward will signal LDK to generate an
|
||||
// intercept event, which the LSP can then use to either (a) open a JIT channel to forward the
|
||||
// payment or (b) fail the payment.
|
||||
do_test_intercepted_payment(false);
|
||||
do_test_intercepted_payment(true);
|
||||
do_test_intercepted_payment(InterceptTest::Forward);
|
||||
do_test_intercepted_payment(InterceptTest::Fail);
|
||||
// Make sure that intercepted payments will be automatically failed back if too many blocks pass.
|
||||
do_test_intercepted_payment(InterceptTest::Timeout);
|
||||
}
|
||||
|
||||
fn do_test_intercepted_payment(fail_intercept: bool) {
|
||||
fn do_test_intercepted_payment(test: InterceptTest) {
|
||||
let chanmon_cfgs = create_chanmon_cfgs(3);
|
||||
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
||||
|
||||
|
@ -1459,7 +1468,7 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
|
|||
let unknown_chan_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &[42; 32], nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
|
||||
assert_eq!(unknown_chan_id_err , APIError::APIMisuseError { err: format!("Channel with id {:?} not found", [42; 32]) });
|
||||
|
||||
if fail_intercept {
|
||||
if test == InterceptTest::Fail {
|
||||
// Ensure we can fail the intercepted payment back.
|
||||
nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap();
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]);
|
||||
|
@ -1477,15 +1486,10 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
|
|||
.blamed_chan_closed(true)
|
||||
.expected_htlc_error_data(0x4000 | 10, &[]);
|
||||
expect_payment_failed_conditions(&nodes[0], payment_hash, false, fail_conditions);
|
||||
} else {
|
||||
} else if test == InterceptTest::Forward {
|
||||
// Open the just-in-time channel so the payment can then be forwarded.
|
||||
let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None);
|
||||
|
||||
// Check for unknown intercept id error.
|
||||
let unknown_intercept_id = InterceptId([42; 32]);
|
||||
let unknown_intercept_id_err = nodes[1].node.forward_intercepted_htlc(unknown_intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
|
||||
assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", unknown_intercept_id.0) });
|
||||
|
||||
// Finally, forward the intercepted payment through and claim it.
|
||||
nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap();
|
||||
expect_pending_htlcs_forwardable!(nodes[1]);
|
||||
|
@ -1523,5 +1527,34 @@ fn do_test_intercepted_payment(fail_intercept: bool) {
|
|||
},
|
||||
_ => panic!("Unexpected event")
|
||||
}
|
||||
} else if test == InterceptTest::Timeout {
|
||||
let mut block = Block {
|
||||
header: BlockHeader { version: 0x20000000, prev_blockhash: nodes[0].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 },
|
||||
txdata: vec![],
|
||||
};
|
||||
connect_block(&nodes[0], &block);
|
||||
connect_block(&nodes[1], &block);
|
||||
let block_count = 183; // find_route adds a random CLTV offset, so hardcode rather than summing consts
|
||||
for _ in 0..block_count {
|
||||
block.header.prev_blockhash = block.block_hash();
|
||||
connect_block(&nodes[0], &block);
|
||||
connect_block(&nodes[1], &block);
|
||||
}
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::InvalidForward { requested_forward_scid: intercept_scid }]);
|
||||
check_added_monitors!(nodes[1], 1);
|
||||
let htlc_timeout_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||
assert!(htlc_timeout_updates.update_add_htlcs.is_empty());
|
||||
assert_eq!(htlc_timeout_updates.update_fail_htlcs.len(), 1);
|
||||
assert!(htlc_timeout_updates.update_fail_malformed_htlcs.is_empty());
|
||||
assert!(htlc_timeout_updates.update_fee.is_none());
|
||||
|
||||
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_timeout_updates.update_fail_htlcs[0]);
|
||||
commitment_signed_dance!(nodes[0], nodes[1], htlc_timeout_updates.commitment_signed, false);
|
||||
expect_payment_failed!(nodes[0], payment_hash, false, 0x2000 | 2, []);
|
||||
|
||||
// Check for unknown intercept id error.
|
||||
let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None);
|
||||
let unknown_intercept_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err();
|
||||
assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", intercept_id.0) });
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue