1use std::{fmt, ops::Deref};
2
3use lightning::{
4 routing::{
5 gossip::{NetworkGraph, NodeId},
6 router::{BlindedTail, Path, Route, RouteHop},
7 },
8 util::logger::Logger,
9};
10#[cfg(any(test, feature = "test-utils"))]
11use proptest_derive::Arbitrary;
12use serde::{Deserialize, Serialize};
13
14use super::{amount::Amount, node_alias::LxNodeAlias};
15use crate::api::user::{NodePk, Scid};
16
17#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
19#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
20pub struct LxRoute {
21 pub paths: Vec<LxPath>,
26}
27
28impl LxRoute {
29 pub fn from_ldk<L: Deref<Target: Logger>>(
30 route: Route,
31 network_graph: &NetworkGraph<L>,
32 ) -> Self {
33 let mut paths = route
34 .paths
35 .into_iter()
36 .map(LxPath::from)
37 .collect::<Vec<_>>();
38
39 {
41 let locked_graph = network_graph.read_only();
42
43 let lookup_alias = |node_pk: &NodePk| -> Option<LxNodeAlias> {
44 let node_id = NodeId::from_pubkey(&node_pk.0);
45 locked_graph
46 .node(&node_id)
47 .and_then(|node_info| node_info.announcement_info.as_ref())
48 .map(|ann_info| LxNodeAlias::from(*ann_info.alias()))
49 };
50
51 for path in paths.iter_mut() {
52 for hop in path.hops.iter_mut() {
53 hop.node_alias = lookup_alias(&hop.node_pk);
54 }
55 }
56 }
57
58 Self { paths }
59 }
60
61 pub fn amount(&self) -> Amount {
63 self.paths.iter().map(LxPath::amount).sum()
64 }
65
66 pub fn fees(&self) -> Amount {
68 self.paths.iter().map(LxPath::fees).sum()
69 }
70}
71
72impl fmt::Display for LxRoute {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 let num_paths = self.paths.len();
75 for (i, path) in self.paths.iter().enumerate() {
76 write!(f, "{path}")?;
77 if i != num_paths - 1 {
78 write!(f, " | ")?;
79 }
80 }
81 Ok(())
82 }
83}
84
85#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
87#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
88pub struct LxPath {
89 pub hops: Vec<LxRouteHop>,
91 pub blinded_tail: Option<LxBlindedTail>,
93}
94
95impl From<Path> for LxPath {
96 fn from(path: Path) -> Self {
97 LxPath {
98 hops: path.hops.into_iter().map(LxRouteHop::from).collect(),
99 blinded_tail: path.blinded_tail.map(LxBlindedTail::from),
100 }
101 }
102}
103
104impl fmt::Display for LxPath {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 let amount = self.amount();
107 let fees = self.fees();
108 write!(f, "[{amount} sat, {fees} fees: ")?;
109 let num_hops = self.hops.len();
110 for (i, hop) in self.hops.iter().enumerate() {
111 let hop_node_pk = hop.node_pk;
112 match hop.node_alias {
113 Some(alias) => write!(f, "({alias}) {hop_node_pk}")?,
114 None => write!(f, "{hop_node_pk}")?,
115 }
116 if i != num_hops - 1 {
117 write!(f, " -> ")?;
118 }
119 }
120 if let Some(tail) = &self.blinded_tail {
121 let num_hops = tail.num_hops;
122 write!(f, " -> blinded tail with {num_hops} hops")?;
123 }
124 write!(f, "]")?;
125 Ok(())
126 }
127}
128
129impl LxPath {
130 pub fn amount(&self) -> Amount {
132 match self.blinded_tail.as_ref() {
133 Some(tail) => tail.final_value,
134 None => self
135 .hops
136 .last()
137 .map_or(Amount::ZERO, |hop| hop.fee_or_amount),
138 }
139 }
140
141 pub fn fees(&self) -> Amount {
144 match &self.blinded_tail {
145 Some(_) => self
150 .hops
151 .iter()
152 .map(|hop| hop.fee_or_amount)
153 .sum::<Amount>(),
154 None => match self.hops.split_last() {
158 Some((_last, non_last)) =>
159 non_last.iter().map(|hop| hop.fee_or_amount).sum::<Amount>(),
160 None => Amount::ZERO,
161 },
162 }
163 }
164}
165
166#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
168#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
169pub struct LxRouteHop {
170 pub node_pk: NodePk,
172 pub node_alias: Option<LxNodeAlias>,
174 pub scid: Scid,
176 pub fee_or_amount: Amount,
185 pub announced: bool,
187}
188
189impl From<RouteHop> for LxRouteHop {
190 fn from(hop: RouteHop) -> Self {
191 Self {
192 node_pk: NodePk(hop.pubkey),
193 node_alias: None,
194 scid: Scid(hop.short_channel_id),
195 fee_or_amount: Amount::from_msat(hop.fee_msat),
196 announced: hop.maybe_announced_channel,
197 }
198 }
199}
200
201#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
203#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
204pub struct LxBlindedTail {
205 pub num_hops: usize,
206 pub final_value: Amount,
208}
209
210impl From<BlindedTail> for LxBlindedTail {
211 fn from(tail: BlindedTail) -> Self {
212 Self {
213 num_hops: tail.hops.len(),
214 final_value: Amount::from_msat(tail.final_value_msat),
215 }
216 }
217}
218
219#[cfg(test)]
220mod test {
221 use lexe_crypto::rng::{FastRng, SysRng};
222 use proptest::prelude::any;
223
224 use super::*;
225 use crate::test_utils::arbitrary;
226
227 #[ignore]
231 #[test]
232 fn display_route() {
233 let mut rng = FastRng::from_sysrng(&mut SysRng::new());
234 let mut route = arbitrary::gen_value(&mut rng, any::<LxRoute>());
235 route.paths.truncate(3);
236 for path in route.paths.iter_mut() {
237 path.hops.truncate(3);
238 }
239 println!("Route: {route}");
240 }
241}