-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support auto-retrying keysend payments in ChannelManager
#2002
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ pub(crate) enum PendingOutboundPayment { | |
session_privs: HashSet<[u8; 32]>, | ||
payment_hash: PaymentHash, | ||
payment_secret: Option<PaymentSecret>, | ||
keysend_preimage: Option<PaymentPreimage>, | ||
pending_amt_msat: u64, | ||
/// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+. | ||
pending_fee_msat: Option<u64>, | ||
|
@@ -415,7 +416,7 @@ impl OutboundPayments { | |
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64, | ||
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>, | ||
{ | ||
self.pay_internal(payment_id, Some((payment_hash, payment_secret, retry_strategy)), | ||
self.pay_internal(payment_id, Some((payment_hash, payment_secret, None, retry_strategy)), | ||
route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, | ||
best_block_height, logger, &send_payment_along_path) | ||
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) | ||
|
@@ -432,13 +433,37 @@ impl OutboundPayments { | |
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64, | ||
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError> | ||
{ | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, None, None, entropy_source, best_block_height)?; | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, None, route, None, None, entropy_source, best_block_height)?; | ||
self.pay_route_internal(route, payment_hash, payment_secret, None, payment_id, None, | ||
onion_session_privs, node_signer, best_block_height, &send_payment_along_path) | ||
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) | ||
} | ||
|
||
pub(super) fn send_spontaneous_payment<ES: Deref, NS: Deref, F>( | ||
pub(super) fn send_spontaneous_payment<R: Deref, ES: Deref, NS: Deref, F, L: Deref>( | ||
&self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId, | ||
retry_strategy: Retry, route_params: RouteParameters, router: &R, | ||
first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES, | ||
node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F | ||
) -> Result<PaymentHash, PaymentSendFailure> | ||
where | ||
R::Target: Router, | ||
ES::Target: EntropySource, | ||
NS::Target: NodeSigner, | ||
L::Target: Logger, | ||
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64, | ||
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>, | ||
{ | ||
let preimage = payment_preimage | ||
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); | ||
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner()); | ||
self.pay_internal(payment_id, Some((payment_hash, &None, Some(preimage), retry_strategy)), | ||
route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, | ||
best_block_height, logger, &send_payment_along_path) | ||
.map(|()| payment_hash) | ||
.map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) | ||
} | ||
|
||
pub(super) fn send_spontaneous_payment_with_route<ES: Deref, NS: Deref, F>( | ||
&self, route: &Route, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId, | ||
entropy_source: &ES, node_signer: &NS, best_block_height: u32, send_payment_along_path: F | ||
) -> Result<PaymentHash, PaymentSendFailure> | ||
|
@@ -448,12 +473,10 @@ impl OutboundPayments { | |
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64, | ||
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError> | ||
{ | ||
let preimage = match payment_preimage { | ||
Some(p) => p, | ||
None => PaymentPreimage(entropy_source.get_secure_random_bytes()), | ||
}; | ||
let preimage = payment_preimage | ||
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); | ||
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner()); | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, None, None, entropy_source, best_block_height)?; | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?; | ||
|
||
match self.pay_route_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path) { | ||
Ok(()) => Ok(payment_hash), | ||
|
@@ -511,7 +534,7 @@ impl OutboundPayments { | |
|
||
fn pay_internal<R: Deref, NS: Deref, ES: Deref, F, L: Deref>( | ||
&self, payment_id: PaymentId, | ||
initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Retry)>, | ||
initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Option<PaymentPreimage>, Retry)>, | ||
route_params: RouteParameters, router: &R, first_hops: Vec<ChannelDetails>, | ||
inflight_htlcs: InFlightHtlcs, entropy_source: &ES, node_signer: &NS, best_block_height: u32, | ||
logger: &L, send_payment_along_path: &F, | ||
|
@@ -539,8 +562,8 @@ impl OutboundPayments { | |
err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound | ||
}))?; | ||
|
||
let res = if let Some((payment_hash, payment_secret, retry_strategy)) = initial_send_info { | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?; | ||
let res = if let Some((payment_hash, payment_secret, keysend_preimage, retry_strategy)) = initial_send_info { | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, keysend_preimage, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?; | ||
self.pay_route_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) | ||
} else { | ||
self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path) | ||
|
@@ -596,21 +619,21 @@ impl OutboundPayments { | |
onion_session_privs.push(entropy_source.get_secure_random_bytes()); | ||
} | ||
|
||
let (total_msat, payment_hash, payment_secret) = { | ||
let (total_msat, payment_hash, payment_secret, keysend_preimage) = { | ||
let mut outbounds = self.pending_outbound_payments.lock().unwrap(); | ||
match outbounds.get_mut(&payment_id) { | ||
Some(payment) => { | ||
let res = match payment { | ||
PendingOutboundPayment::Retryable { | ||
total_msat, payment_hash, payment_secret, pending_amt_msat, .. | ||
total_msat, payment_hash, keysend_preimage, payment_secret, pending_amt_msat, .. | ||
} => { | ||
let retry_amt_msat: u64 = route.paths.iter().map(|path| path.last().unwrap().fee_msat).sum(); | ||
if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 { | ||
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError { | ||
err: format!("retry_amt_msat of {} will put pending_amt_msat (currently: {}) more than 10% over total_payment_amt_msat of {}", retry_amt_msat, pending_amt_msat, total_msat).to_string() | ||
})) | ||
} | ||
(*total_msat, *payment_hash, *payment_secret) | ||
(*total_msat, *payment_hash, *payment_secret, *keysend_preimage) | ||
Comment on lines
+622
to
+636
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible follow-up opportunity to improve code cleanliness here. The first pattern is the only one that doesn't return early. So some of the indentations can be reduced by getting rid of the |
||
}, | ||
PendingOutboundPayment::Legacy { .. } => { | ||
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError { | ||
|
@@ -645,7 +668,7 @@ impl OutboundPayments { | |
})), | ||
} | ||
}; | ||
self.pay_route_internal(route, payment_hash, &payment_secret, None, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path) | ||
self.pay_route_internal(route, payment_hash, &payment_secret, keysend_preimage, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path) | ||
} | ||
|
||
pub(super) fn send_probe<ES: Deref, NS: Deref, F>( | ||
|
@@ -669,7 +692,7 @@ impl OutboundPayments { | |
} | ||
|
||
let route = Route { paths: vec![hops], payment_params: None }; | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, None, None, entropy_source, best_block_height)?; | ||
let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, None, &route, None, None, entropy_source, best_block_height)?; | ||
|
||
match self.pay_route_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path) { | ||
Ok(()) => Ok((payment_hash, payment_id)), | ||
|
@@ -685,13 +708,13 @@ impl OutboundPayments { | |
&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId, | ||
route: &Route, retry_strategy: Option<Retry>, entropy_source: &ES, best_block_height: u32 | ||
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource { | ||
self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, retry_strategy, None, entropy_source, best_block_height) | ||
self.add_new_pending_payment(payment_hash, payment_secret, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height) | ||
} | ||
|
||
pub(super) fn add_new_pending_payment<ES: Deref>( | ||
&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId, | ||
route: &Route, retry_strategy: Option<Retry>, payment_params: Option<PaymentParameters>, | ||
entropy_source: &ES, best_block_height: u32 | ||
keysend_preimage: Option<PaymentPreimage>, route: &Route, retry_strategy: Option<Retry>, | ||
payment_params: Option<PaymentParameters>, entropy_source: &ES, best_block_height: u32 | ||
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource { | ||
let mut onion_session_privs = Vec::with_capacity(route.paths.len()); | ||
for _ in 0..route.paths.len() { | ||
|
@@ -711,6 +734,7 @@ impl OutboundPayments { | |
pending_fee_msat: Some(0), | ||
payment_hash, | ||
payment_secret, | ||
keysend_preimage, | ||
starting_block_height: best_block_height, | ||
total_msat: route.get_total_amount(), | ||
}); | ||
|
@@ -1153,6 +1177,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, | |
(2, payment_hash, required), | ||
(3, payment_params, option), | ||
(4, payment_secret, option), | ||
(5, keysend_preimage, option), | ||
(6, total_msat, required), | ||
(8, pending_amt_msat, required), | ||
(10, starting_block_height, required), | ||
|
@@ -1247,9 +1272,9 @@ mod tests { | |
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })); | ||
|
||
let err = if on_retry { | ||
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]), | ||
&Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()), | ||
&&keys_manager, 0).unwrap(); | ||
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]), None, | ||
&Route { paths: vec![], payment_params: None }, Some(Retry::Attempts(1)), | ||
Some(route_params.payment_params.clone()), &&keys_manager, 0).unwrap(); | ||
outbound_payments.pay_internal( | ||
PaymentId([0; 32]), None, route_params, &&router, vec![], InFlightHtlcs::new(), | ||
&&keys_manager, &&keys_manager, 0, &&logger, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated this PR, but may want to consider using the
Payer
abstraction here and throughout in the future to reduce the number of parameters. Could possibly simplify the tests by using a custom function forsend_payment_along_path
instead of needing to setupChannelManager
... or maybe not yet if retries are still triggered inChannelManager
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retries upon payment failure are triggered in
ChannelManager
but they could always be triggered manually for unit testing. UsingPayer
to reduce the number of params sgtm 👍