rustywallet_lightning/
route.rs

1//! Route hints for Lightning payments.
2//!
3//! This module provides types for working with route hints,
4//! which help payers find a path to private channels.
5
6use crate::channel::ShortChannelId;
7use crate::node::NodeId;
8
9/// A route hint for finding a path to a destination.
10///
11/// Route hints are used when the destination has private channels
12/// that are not publicly announced.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RouteHint {
15    /// Hops in this route hint
16    hops: Vec<RouteHintHop>,
17}
18
19impl RouteHint {
20    /// Create a new empty route hint.
21    pub fn new() -> Self {
22        Self { hops: Vec::new() }
23    }
24
25    /// Create a route hint with the given hops.
26    pub fn with_hops(hops: Vec<RouteHintHop>) -> Self {
27        Self { hops }
28    }
29
30    /// Add a hop to the route hint.
31    pub fn add_hop(&mut self, hop: RouteHintHop) {
32        self.hops.push(hop);
33    }
34
35    /// Get the hops in this route hint.
36    pub fn hops(&self) -> &[RouteHintHop] {
37        &self.hops
38    }
39
40    /// Get the number of hops.
41    pub fn len(&self) -> usize {
42        self.hops.len()
43    }
44
45    /// Check if the route hint is empty.
46    pub fn is_empty(&self) -> bool {
47        self.hops.is_empty()
48    }
49}
50
51impl Default for RouteHint {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// A single hop in a route hint.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct RouteHintHop {
60    /// Public key of the node at the start of this hop
61    pub src_node_id: NodeId,
62    /// Short channel ID
63    pub short_channel_id: ShortChannelId,
64    /// Base fee in millisatoshis
65    pub fee_base_msat: u32,
66    /// Proportional fee in millionths
67    pub fee_proportional_millionths: u32,
68    /// CLTV expiry delta
69    pub cltv_expiry_delta: u16,
70}
71
72impl RouteHintHop {
73    /// Create a new route hint hop.
74    pub fn new(
75        src_node_id: NodeId,
76        short_channel_id: ShortChannelId,
77        fee_base_msat: u32,
78        fee_proportional_millionths: u32,
79        cltv_expiry_delta: u16,
80    ) -> Self {
81        Self {
82            src_node_id,
83            short_channel_id,
84            fee_base_msat,
85            fee_proportional_millionths,
86            cltv_expiry_delta,
87        }
88    }
89
90    /// Calculate the fee for routing a given amount.
91    pub fn fee_for_amount(&self, amount_msat: u64) -> u64 {
92        let base = self.fee_base_msat as u64;
93        let proportional = (amount_msat * self.fee_proportional_millionths as u64) / 1_000_000;
94        base + proportional
95    }
96}
97
98/// Builder for creating route hints.
99pub struct RouteHintBuilder {
100    hops: Vec<RouteHintHop>,
101}
102
103impl RouteHintBuilder {
104    /// Create a new route hint builder.
105    pub fn new() -> Self {
106        Self { hops: Vec::new() }
107    }
108
109    /// Add a hop to the route hint.
110    pub fn hop(
111        mut self,
112        src_node_id: NodeId,
113        short_channel_id: ShortChannelId,
114        fee_base_msat: u32,
115        fee_proportional_millionths: u32,
116        cltv_expiry_delta: u16,
117    ) -> Self {
118        self.hops.push(RouteHintHop::new(
119            src_node_id,
120            short_channel_id,
121            fee_base_msat,
122            fee_proportional_millionths,
123            cltv_expiry_delta,
124        ));
125        self
126    }
127
128    /// Build the route hint.
129    pub fn build(self) -> RouteHint {
130        RouteHint::with_hops(self.hops)
131    }
132}
133
134impl Default for RouteHintBuilder {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    fn test_node_id() -> NodeId {
145        NodeId::from_bytes([2u8; 33])
146    }
147
148    #[test]
149    fn test_route_hint_creation() {
150        let hint = RouteHint::new();
151        assert!(hint.is_empty());
152    }
153
154    #[test]
155    fn test_route_hint_with_hops() {
156        let hop = RouteHintHop::new(
157            test_node_id(),
158            ShortChannelId::new(700000, 1, 0),
159            1000,
160            100,
161            144,
162        );
163
164        let hint = RouteHint::with_hops(vec![hop]);
165        assert_eq!(hint.len(), 1);
166    }
167
168    #[test]
169    fn test_fee_calculation() {
170        let hop = RouteHintHop::new(
171            test_node_id(),
172            ShortChannelId::new(700000, 1, 0),
173            1000,  // 1 sat base fee
174            1000,  // 0.1% proportional
175            144,
176        );
177
178        // For 1,000,000 msat (1000 sats):
179        // base: 1000 msat
180        // proportional: 1,000,000 * 1000 / 1,000,000 = 1000 msat
181        // total: 2000 msat
182        assert_eq!(hop.fee_for_amount(1_000_000), 2000);
183    }
184
185    #[test]
186    fn test_route_hint_builder() {
187        let hint = RouteHintBuilder::new()
188            .hop(
189                test_node_id(),
190                ShortChannelId::new(700000, 1, 0),
191                1000,
192                100,
193                144,
194            )
195            .hop(
196                test_node_id(),
197                ShortChannelId::new(700001, 2, 1),
198                500,
199                50,
200                40,
201            )
202            .build();
203
204        assert_eq!(hint.len(), 2);
205    }
206}