Repository: lightningdevkit/rust-lightning (Rust)

Issue:
Allow specifying an error when failing back
When we fail back because the user tells us to, we should let the user specify the error message we send to the peer.


Discussion: Hi! I’m interested in picking up this issue.

From what I understand, this would involve changing the `fail_htlc_backwards(&self, payment_hash: &PaymentHash)` function on `ChannelManager`? Particularly letting the user provide another input that could affect the `HTLCFailReason` that’s passed into `fail_htlc_backwards_internal(..)`?

I imagine just adding an argument to the function is probably not the best idea for backwards compatibility, in which case would it be better to add a new function, something along the lines of `fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, reason: &HTLCFailReason)`?

I also wanted to ask for a brief comprehension check on my understanding of how failing back an HTLC works in the `ChannelManager`. From digging through the codebase today, this is what I think happens:
- A payment is sent to the user, which emits an `Event::PaymentClaimable`.
- Usually the user would then call `ChannelManager::claim_funds`, but if the preimage for the payment hash is unknown and they can’t claim the payment, or something else is wrong like the amount received is wrong, the user will fail back the HTLC by calling this function, `ChannelManager::fail_htlc_backwards`.
- This function goes through each source for the payment (there are multiple in case of a multi-path payment?) and calls `ChannelManager::fail_htlc_backwards_internal`, notably with an `HTLCSource::PreviousHopData`, and an `HTLCFailReason`.
- This function now adds an `HTLCForwardInfo::FailHTLC` (with the fail reason as an `OnionErrorPacket`) to `self.forward_htlcs`, and emits an `Event::PendingHTLCsForwardable`, and `Event::HTLCHandlingFailed`.
- The user is then expected to handle `Event::PendingHTLCsForwardable`, which includes calling `ChannelManager::process_pending_htlc_forwards`, which does a lot of things, but importantly loops through `self.forward_htlcs` and if the channel exists, for `HTLCForwardInfo::FailHTLC`, it calls `Channel::queue_fail_htlc`, which calls `Channel::fail_htlc` which actually fails it. I couldn’t find where it actually sends a message to a peer, but that’s about as far as I got.

Any feedback is appreciated!
> something along the lines of `fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, reason: &HTLCFailReason)`?

That looks accurate to me. And your comprehension about how HTLCs are failed back looks good too! 
Cool! I'll probably open a PR sometime today or tomorrow.



Fix (patch):
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index d08f8da5558..72bf349e9b4 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -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
@@ -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) {
 		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.
 	///
diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs
index b3e9c236138..432a532d68c 100644
--- a/lightning/src/util/events.rs
+++ b/lightning/src/util/events.rs
@@ -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
@@ -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].