Skip to content

Commit

Permalink
Send and handle probes differently in InvoicePayer
Browse files Browse the repository at this point in the history
  • Loading branch information
tnull committed Jun 24, 2022
1 parent 4793c80 commit f6c9a47
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 1 deletion.
80 changes: 80 additions & 0 deletions lightning-invoice/src/payment.rs
Expand Up @@ -74,6 +74,12 @@
//! # &self, route: &Route, payment_id: PaymentId
//! # ) -> Result<(), PaymentSendFailure> { unimplemented!() }
//! # fn abandon_payment(&self, payment_id: PaymentId) { unimplemented!() }
//! # fn send_probe_payment(
//! # &self, route: &Route
//! # ) -> Result<PaymentId, PaymentSendFailure> { unimplemented!() }
//! # fn payment_is_probe(
//! # &self, payment_hash: PaymentHash, payment_id: PaymentId
//! # ) -> bool { unimplemented!() }
//! # }
//! #
//! # struct FakeRouter {}
Expand All @@ -82,6 +88,10 @@
//! # &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash,
//! # first_hops: Option<&[&ChannelDetails]>, scorer: &S
//! # ) -> Result<Route, LightningError> { unimplemented!() }
//! #
//! # fn build_route_from_hops(
//! # &self, _payer: &PublicKey, _hops: &[PublicKey], route_params: &RouteParameters
//! # ) -> Result<Route, LightningError> { unimplemented!() }
//! # }
//! #
//! # struct FakeScorer {}
Expand Down Expand Up @@ -247,6 +257,12 @@ pub trait Payer {

/// Signals that no further retries for the given payment will occur.
fn abandon_payment(&self, payment_id: PaymentId);

/// Send a payment probe over the given [`Route`].
fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure>;

/// Returns whether payment with the given [`PaymentId`] and [`PaymentHash`] is a probe.
fn payment_is_probe(&self, payment_hash: PaymentHash, payment_id: PaymentId) -> bool;
}

/// A trait defining behavior for routing an [`Invoice`] payment.
Expand All @@ -256,6 +272,11 @@ pub trait Router<S: Score> {
&self, payer: &PublicKey, route_params: &RouteParameters, payment_hash: &PaymentHash,
first_hops: Option<&[&ChannelDetails]>, scorer: &S
) -> Result<Route, LightningError>;

/// Builds a [`Route`] from `payer` along the given path.
fn build_route_from_hops(
&self, payer: &PublicKey, hops: &[PublicKey], params: &RouteParameters
) -> Result<Route, LightningError>;
}

/// Strategies available to retry payment path failures for an [`Invoice`].
Expand Down Expand Up @@ -411,6 +432,23 @@ where
.map_err(|e| { self.payment_cache.lock().unwrap().remove(&payment_hash); e })
}

/// Sends a probe payment along the given path. The resulting payment will not be cached and
/// resulting failures will be handled differently from regular payments.
pub fn send_probe_along_path(
&self, pubkey: PublicKey, hops: &[PublicKey], amount_msats: u64, final_cltv_expiry_delta: u32
) -> Result<PaymentId, PaymentError> {
let route_params = RouteParameters {
payment_params: PaymentParameters::for_keysend(pubkey),
final_value_msat: amount_msats,
final_cltv_expiry_delta,
};
let payer = self.payer.node_id();
let route = self.router.build_route_from_hops(&payer, hops, &route_params)
.map_err(|e| PaymentError::Routing(e))?;

self.payer.send_probe_payment(&route).map_err(|e| PaymentError::Sending(e))
}

fn pay_internal<F: FnOnce(&Route) -> Result<PaymentId, PaymentSendFailure> + Copy>(
&self, params: &RouteParameters, payment_hash: PaymentHash, send_payment: F,
) -> Result<PaymentId, PaymentError> {
Expand Down Expand Up @@ -550,6 +588,17 @@ where
Event::PaymentPathFailed {
payment_id, payment_hash, rejected_by_dest, path, short_channel_id, retry, ..
} => {
if let Some(payment_id) = payment_id {
// When the failed payment was a probe, we make sure to not penalize the last
// hop and then drop the event instead of handing it up to the user's event
// handler.
if self.payer.payment_is_probe(*payment_hash, *payment_id) {
let path = path.iter().collect::<Vec<_>>();
self.scorer.lock().payment_path_failed(&path, u64::max_value());
return;
}
}

if let Some(short_channel_id) = short_channel_id {
let path = path.iter().collect::<Vec<_>>();
self.scorer.lock().payment_path_failed(&path, *short_channel_id);
Expand Down Expand Up @@ -1402,6 +1451,14 @@ mod tests {
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
})
}

fn build_route_from_hops(
&self, _payer: &PublicKey, _hops: &[PublicKey], route_params: &RouteParameters
) -> Result<Route, LightningError> {
Ok(Route {
payment_params: Some(route_params.payment_params.clone()), ..Self::route_for_value(route_params.final_value_msat)
})
}
}

struct FailingRouter;
Expand All @@ -1413,6 +1470,12 @@ mod tests {
) -> Result<Route, LightningError> {
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
}

fn build_route_from_hops(
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
) -> Result<Route, LightningError> {
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError })
}
}

struct TestScorer {
Expand Down Expand Up @@ -1604,6 +1667,17 @@ mod tests {
}

fn abandon_payment(&self, _payment_id: PaymentId) { }

fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure> {
// TODO: for now copied from spontaneous, figure out what to do here.
self.check_value_msats(Amount::Spontaneous(route.get_total_amount()));
self.check_attempts()
}

fn payment_is_probe(&self, _payment_hash: PaymentHash, _payment_id: PaymentId) -> bool {
// TODO: figure out what to do here.
false
}
}

// *** Full Featured Functional Tests with a Real ChannelManager ***
Expand All @@ -1616,6 +1690,12 @@ mod tests {
) -> Result<Route, LightningError> {
self.0.borrow_mut().pop_front().unwrap()
}

fn build_route_from_hops(
&self, _payer: &PublicKey, _hops: &[PublicKey], _route_params: &RouteParameters
) -> Result<Route, LightningError> {
self.0.borrow_mut().pop_front().unwrap()
}
}
impl ManualRouter {
fn expect_find_route(&self, result: Result<Route, LightningError>) {
Expand Down
22 changes: 21 additions & 1 deletion lightning-invoice/src/utils.rs
Expand Up @@ -16,7 +16,7 @@ use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey};
use lightning::ln::msgs::LightningError;
use lightning::routing::gossip::{NetworkGraph, RoutingFees};
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route};
use lightning::routing::router::{Route, RouteHint, RouteHintHop, RouteParameters, find_route, build_route_from_hops};
use lightning::routing::scoring::Score;
use lightning::util::logger::Logger;
use secp256k1::PublicKey;
Expand Down Expand Up @@ -469,6 +469,18 @@ where L::Target: Logger {
};
find_route(payer, params, &network_graph, first_hops, &*self.logger, scorer, &random_seed_bytes)
}

fn build_route_from_hops(
&self, payer: &PublicKey, hops: &[PublicKey], params: &RouteParameters
) -> Result<Route, LightningError> {
let network_graph = self.network_graph.read_only();
let random_seed_bytes = {
let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
*locked_random_seed_bytes = sha256::Hash::hash(&*locked_random_seed_bytes).into_inner();
*locked_random_seed_bytes
};
build_route_from_hops(payer, hops, params, &network_graph, &*self.logger, &random_seed_bytes)
}
}

impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Payer for ChannelManager<Signer, M, T, K, F, L>
Expand Down Expand Up @@ -509,6 +521,14 @@ where
fn abandon_payment(&self, payment_id: PaymentId) {
self.abandon_payment(payment_id)
}

fn send_probe_payment(&self, route: &Route) -> Result<PaymentId, PaymentSendFailure> {
self.send_probe_payment(route).map(|(_, payment_id)| payment_id)
}

fn payment_is_probe(&self, payment_hash: PaymentHash, payment_id: PaymentId) -> bool {
self.payment_is_probe(payment_hash, payment_id)
}
}

#[cfg(test)]
Expand Down

0 comments on commit f6c9a47

Please sign in to comment.