Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -289,6 +289,25 @@ struct ReceiveError {
msg: &'static str,
}

/// This enum is used to specify which error data to send to peers when failing back an HTLC
/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
///
/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
#[derive(Clone, Copy)]
pub enum FailureCode {
/// We had a temporary error processing the payment. Useful if no other error codes fit
/// and you want to indicate that the payer may want to retry.
TemporaryNodeFailure = 0x2000 | 2,
/// We have a required feature which was not in this onion. For example, you may require
/// some additional metadata that was not provided with this payment.
RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
/// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
/// the HTLC is too close to the current block height for safe handling.
/// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
/// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
}

type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);

/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
Expand DownExpand Up@@ -3472,21 +3491,40 @@ where
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
}

/// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
/// reason for the failure.
///
/// See [`FailureCode`] for valid failure codes.
pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
Comment thread
alecchendev marked this conversation as resolved.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);

let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}

/// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
match failure_code {
FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
}
}
}

/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
Expand Down
69 changes: 68 additions & 1 deletion lightning/src/ln/onion_route_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,7 @@ use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCK
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
Expand DownExpand Up@@ -831,6 +831,73 @@ fn test_always_create_tlv_format_onion_payloads() {
}
}

fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes(&nodes, 0, 1);

let payment_amount = 100_000;
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(nodes[1]);
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);

expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
check_added_monitors!(nodes[1], 1);

let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let (update_fail_htlc, commitment_signed) = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
assert!(update_add_htlcs.is_empty());
assert!(update_fulfill_htlcs.is_empty());
assert_eq!(update_fail_htlcs.len(), 1);
assert!(update_fail_malformed_htlcs.is_empty());
assert!(update_fee.is_none());
(update_fail_htlcs[0].clone(), commitment_signed)
},
_ => panic!("Unexpected event"),
};

nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);

let failure_data = match failure_code {
FailureCode::TemporaryNodeFailure => vec![],
FailureCode::RequiredNodeFeatureMissing => vec![],
FailureCode::IncorrectOrUnknownPaymentDetails => {
let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
htlc_msat_height_data
}
};

let failure_code = failure_code as u16;
let permanent_flag = 0x4000;
let permanent_fail = (failure_code & permanent_flag) != 0;
expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);

}

#[test]
fn test_fail_htlc_backwards_with_reason() {
do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
}

macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
Expand Down
9 changes: 5 additions & 4 deletions lightning/src/util/events.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,10 +491,10 @@ pub enum Event {
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
/// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
/// network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`] or
/// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
/// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
/// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
Expand All@@ -506,6 +506,7 @@ pub enum Event {
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
Expand Down