X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=8867b2e96ae14c3e75bb960d5dd9fa97ed10d746;hb=9b40e8f14e42adda68f71da83fac3d7b4b6f909e;hp=4b6278d194904419dcb189ffdb1c125215f3e7c4;hpb=d776dee3a553ee9ba3d2ce217207f42f10ed091f;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 4b6278d1..8867b2e9 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -378,6 +378,25 @@ impl OutboundPayments { } } + pub(super) fn send_payment( + &self, payment_hash: PaymentHash, payment_secret: &Option, payment_id: PaymentId, + retry_strategy: Retry, route_params: RouteParameters, router: &R, + first_hops: Vec, inflight_htlcs: InFlightHtlcs, entropy_source: &ES, + node_signer: &NS, best_block_height: u32, send_payment_along_path: F + ) -> Result<(), PaymentSendFailure> + where + R::Target: Router, + ES::Target: EntropySource, + NS::Target: NodeSigner, + F: Fn(&Vec, &Option, &PaymentHash, &Option, u64, + u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError>, + { + self.pay_internal(payment_id, Some((payment_hash, payment_secret, retry_strategy)), + route_params, router, first_hops, inflight_htlcs, entropy_source, 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_payment_with_route( &self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option, payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32, @@ -390,8 +409,8 @@ impl OutboundPayments { u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError> { let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, Retry::Attempts(0), None, entropy_source, best_block_height)?; - self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, - onion_session_privs, node_signer, best_block_height, send_payment_along_path) + 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 }) } @@ -412,7 +431,7 @@ impl OutboundPayments { 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, Retry::Attempts(0), None, entropy_source, best_block_height)?; - match self.send_payment_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { + 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), Err(e) => { self.remove_outbound_if_all_failed(payment_id, &e); @@ -451,7 +470,7 @@ impl OutboundPayments { } if let Some((payment_id, route_params)) = retry_id_route_params { core::mem::drop(outbounds); - if let Err(e) = self.pay_internal(payment_id, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, &send_payment_along_path) { + if let Err(e) = self.pay_internal(payment_id, None, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, &send_payment_along_path) { log_trace!(logger, "Errored retrying payment: {:?}", e); } } else { break } @@ -459,9 +478,11 @@ impl OutboundPayments { } fn pay_internal( - &self, payment_id: PaymentId, route_params: RouteParameters, router: &R, - first_hops: Vec, inflight_htlcs: InFlightHtlcs, entropy_source: &ES, - node_signer: &NS, best_block_height: u32, send_payment_along_path: &F + &self, payment_id: PaymentId, + initial_send_info: Option<(PaymentHash, &Option, Retry)>, + route_params: RouteParameters, router: &R, first_hops: Vec, + inflight_htlcs: InFlightHtlcs, entropy_source: &ES, node_signer: &NS, best_block_height: u32, + send_payment_along_path: &F ) -> Result<(), PaymentSendFailure> where R::Target: Router, @@ -485,7 +506,12 @@ impl OutboundPayments { err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound }))?; - let res = self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path); + 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, retry_strategy, Some(route_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) + }; match res { Err(PaymentSendFailure::AllFailedResendSafe(_)) => { let mut outbounds = self.pending_outbound_payments.lock().unwrap(); @@ -496,7 +522,7 @@ impl OutboundPayments { } else { return res } } else { return res } core::mem::drop(outbounds); - self.pay_internal(payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path) + self.pay_internal(payment_id, None, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path) }, Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. }) => { let mut outbounds = self.pending_outbound_payments.lock().unwrap(); @@ -511,7 +537,7 @@ impl OutboundPayments { // Some paths were sent, even if we failed to send the full MPP value our recipient may // misbehave and claim the funds, at which point we have to consider the payment sent, so // return `Ok()` here, ignoring any retry errors. - let _ = self.pay_internal(payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path); + let _ = self.pay_internal(payment_id, None, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path); Ok(()) }, Err(PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. }) => { @@ -591,7 +617,7 @@ impl OutboundPayments { })), } }; - self.send_payment_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, None, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path) } pub(super) fn send_probe( @@ -617,7 +643,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, Retry::Attempts(0), None, entropy_source, best_block_height)?; - match self.send_payment_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { + 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)), Err(e) => { self.remove_outbound_if_all_failed(payment_id, &e); @@ -670,11 +696,11 @@ impl OutboundPayments { } } - fn send_payment_internal( + fn pay_route_internal( &self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option, keysend_preimage: Option, payment_id: PaymentId, recv_value_msat: Option, onion_session_privs: Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32, - send_payment_along_path: F + send_payment_along_path: &F ) -> Result<(), PaymentSendFailure> where NS::Target: NodeSigner, @@ -787,9 +813,9 @@ impl OutboundPayments { F: Fn(&Vec, &Option, &PaymentHash, &Option, u64, u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError> { - self.send_payment_internal(route, payment_hash, payment_secret, keysend_preimage, payment_id, + self.pay_route_internal(route, payment_hash, payment_secret, keysend_preimage, payment_id, recv_value_msat, onion_session_privs, node_signer, best_block_height, - send_payment_along_path) + &send_payment_along_path) .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e }) } @@ -1088,3 +1114,99 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (2, payment_hash, required), }, ); + +#[cfg(test)] +mod tests { + use bitcoin::blockdata::constants::genesis_block; + use bitcoin::network::constants::Network; + use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + + use crate::ln::PaymentHash; + use crate::ln::channelmanager::{PaymentId, PaymentSendFailure}; + use crate::ln::msgs::{ErrorAction, LightningError}; + use crate::ln::outbound_payment::{OutboundPayments, Retry}; + use crate::routing::gossip::NetworkGraph; + use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters}; + use crate::sync::Arc; + use crate::util::errors::APIError; + use crate::util::test_utils; + + #[test] + #[cfg(feature = "std")] + fn fails_paying_after_expiration() { + do_fails_paying_after_expiration(false); + do_fails_paying_after_expiration(true); + } + #[cfg(feature = "std")] + fn do_fails_paying_after_expiration(on_retry: bool) { + let outbound_payments = OutboundPayments::new(); + let logger = test_utils::TestLogger::new(); + let genesis_hash = genesis_block(Network::Testnet).header.block_hash(); + let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger)); + let router = test_utils::TestRouter::new(network_graph); + let secp_ctx = Secp256k1::new(); + let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); + + let past_expiry_time = std::time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() - 2; + let payment_params = PaymentParameters::from_node_id( + PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())) + .with_expiry_time(past_expiry_time); + let expired_route_params = RouteParameters { + payment_params, + final_value_msat: 0, + final_cltv_expiry_delta: 0, + }; + let err = if on_retry { + outbound_payments.pay_internal( + PaymentId([0; 32]), None, expired_route_params, &&router, vec![], InFlightHtlcs::new(), + &&keys_manager, &&keys_manager, 0, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err() + } else { + outbound_payments.send_payment( + PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), expired_route_params, + &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err() + }; + if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err { + assert!(err.contains("Invoice expired")); + } else { panic!("Unexpected error"); } + } + + #[test] + fn find_route_error() { + do_find_route_error(false); + do_find_route_error(true); + } + fn do_find_route_error(on_retry: bool) { + let outbound_payments = OutboundPayments::new(); + let logger = test_utils::TestLogger::new(); + let genesis_hash = genesis_block(Network::Testnet).header.block_hash(); + let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger)); + let router = test_utils::TestRouter::new(network_graph); + let secp_ctx = Secp256k1::new(); + let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); + + router.expect_find_route(Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })); + + let payment_params = PaymentParameters::from_node_id( + PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())); + let route_params = RouteParameters { + payment_params, + final_value_msat: 0, + final_cltv_expiry_delta: 0, + }; + let err = if on_retry { + outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]), + &Route { paths: vec![], payment_params: None }, Retry::Attempts(1), Some(route_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, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err() + } else { + outbound_payments.send_payment( + PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), route_params, + &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err() + }; + if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err { + assert!(err.contains("Failed to find a route")); + } else { panic!("Unexpected error"); } + } +}