1use anyhow::{bail, Context};
2use constants::FailureReason;
3use lnd_grpc_rust::{
4 lnrpc::{self, payment::PaymentStatus, FeatureBit, PaymentFailureReason},
5 routerrpc, LndClient,
6};
7use log::info;
8use std::result::Result::Ok;
9mod constants;
10mod utils;
11use utils::{filter_channels_from_pubkeys, generate_secret_for_probes};
12
13use crate::utils::{get_node_features, get_node_info, print_in_flight_payment};
14
15const TLV_ONION_REQ: i32 = FeatureBit::TlvOnionReq as i32;
16const DEFAULT_TIMEOUT_SECONDS: i32 = 60;
17const ZERO_AMOUNT: i64 = 0;
18
19pub struct ProbeDestination {
20 pub client: LndClient,
21 pub probe_amount_sat: Option<i64>,
22 pub destination_pubkey: Option<String>,
23 pub timeout_seconds: Option<i32>,
24 pub fee_limit_sat: i64,
25 pub payment_request: Option<String>,
26 pub outgoing_pubkeys: Option<Vec<String>>,
27 pub last_hop_pubkey: Option<String>,
28 pub max_paths: Option<u32>,
29}
30
31#[derive(Debug)]
32pub struct ProbeResult {
33 pub payment: lnrpc::Payment,
34 pub is_probe_success: bool,
35 pub failure_reason: PaymentFailureReason,
36}
37
38pub async fn probe_destination(mut args: ProbeDestination) -> anyhow::Result<ProbeResult> {
39 if args.payment_request.is_none() && args.destination_pubkey.is_none() {
40 bail!("ExpectedEitherPaymentRequestOrDestinationPubkey");
41 }
42
43 if args.payment_request.is_some() && args.destination_pubkey.is_some() {
44 bail!("ExpectedPaymentRequestOrDestinationPubkeyAndNotBoth");
45 }
46
47 let mut destination: String = "".to_string();
48 let mut features: Vec<i32> = vec![TLV_ONION_REQ];
49 let mut amount: i64 = args.probe_amount_sat.unwrap_or_default();
50 let mut outgoing_channel_ids: Vec<u64> = vec![];
51
52 if args.outgoing_pubkeys.is_some() {
53 let res =
54 filter_channels_from_pubkeys(&mut args.client, args.outgoing_pubkeys.unwrap()).await?;
55
56 outgoing_channel_ids = res.channels.into_iter().map(|n| n.chan_id).collect();
57 }
58
59 if args.destination_pubkey.is_some() {
60 destination = args.destination_pubkey.clone().unwrap();
61
62 let node_info = get_node_info(&mut args.client, args.destination_pubkey.unwrap())
63 .await
64 .context("failed to get nodeinfo")?;
65
66 let node_features = node_info.node.context("failed to get node info")?.features;
67
68 features = get_node_features(node_features);
69 }
70
71 if let Some(payment_request_string) = args.payment_request {
72 let request = lnrpc::PayReqString {
73 pay_req: payment_request_string,
74 };
75
76 let decoded_payment_request = args
77 .client
78 .lightning()
79 .decode_pay_req(request)
80 .await
81 .context("FailedToDecodePaymentRequest")?;
82
83 let inner = decoded_payment_request.into_inner();
84
85 if inner.num_satoshis == ZERO_AMOUNT || inner.num_msat == ZERO_AMOUNT {
86 bail!("Can't probe with 0 amount invoices")
87 }
88
89 amount = inner.num_satoshis;
90 features = get_node_features(inner.features);
91
92 destination = inner.destination;
93 }
94
95 let hash = generate_secret_for_probes();
96
97 let request = routerrpc::SendPaymentRequest {
98 amt: amount,
99 dest: hex::decode(destination).context("Failed to decode hex pubkey")?,
100 dest_features: features,
101 payment_hash: hash,
102 timeout_seconds: args.timeout_seconds.unwrap_or(DEFAULT_TIMEOUT_SECONDS),
103 fee_limit_sat: args.fee_limit_sat,
104 max_parts: args.max_paths.unwrap_or_default(),
105 outgoing_chan_ids: outgoing_channel_ids,
106
107 ..Default::default()
108 };
109
110 let res = args.client.router().send_payment_v2(request).await;
111
112 let mut response = match res {
113 Ok(response) => response.into_inner(),
114 Err(e) => bail!(
115 "Failed to get streaming response from send payment: {:?}",
116 e
117 ),
118 };
119
120 while let Some(payment) = response.message().await.context("Failed to get payment")? {
121 if let Some(status) = PaymentStatus::from_i32(payment.status) {
122 let failure_reason = FailureReason::from(payment.failure_reason);
123 if status == PaymentStatus::InFlight {
124 let details = print_in_flight_payment(payment.clone())?;
125 if let Some(last_detail) = details.last() {
126 info!("payment inflight {:?}", last_detail);
127 }
128 }
129
130 if status == PaymentStatus::Failed {
131 let is_probe_success = failure_reason == FailureReason::IncorrectPaymentDetails;
132 return Ok(ProbeResult {
133 failure_reason: payment.failure_reason(),
134 is_probe_success,
135 payment,
136 });
137 }
138 }
139 }
140
141 bail!("An Unexpected error occoured")
142}