1use std::collections::HashSet;
2use std::{collections::HashMap, str::FromStr};
3
4use crate::{
5 serializers, LightningError, LightningNode, NodeId, NodeInfo, PaymentOutcome, PaymentResult,
6};
7use async_trait::async_trait;
8use bitcoin::hashes::{sha256, Hash};
9use bitcoin::secp256k1::PublicKey;
10use bitcoin::Network;
11use lightning::ln::features::NodeFeatures;
12use lightning::ln::{PaymentHash, PaymentPreimage};
13use serde::{Deserialize, Serialize};
14use tonic_lnd::lnrpc::{payment::PaymentStatus, GetInfoRequest, GetInfoResponse};
15use tonic_lnd::lnrpc::{ListChannelsRequest, NodeInfoRequest, PaymentFailureReason};
16use tonic_lnd::routerrpc::TrackPaymentRequest;
17use tonic_lnd::tonic::Code::Unavailable;
18use tonic_lnd::tonic::Status;
19use tonic_lnd::{routerrpc::SendPaymentRequest, Client};
20use triggered::Listener;
21
22const KEYSEND_KEY: u64 = 5482373484;
23const SEND_PAYMENT_TIMEOUT_SECS: i32 = 300;
24
25#[derive(Serialize, Deserialize, Debug, Clone)]
26pub struct LndConnection {
27 #[serde(with = "serializers::serde_node_id")]
28 pub id: NodeId,
29 pub address: String,
30 #[serde(deserialize_with = "serializers::deserialize_path")]
31 pub macaroon: String,
32 #[serde(deserialize_with = "serializers::deserialize_path")]
33 pub cert: String,
34}
35
36pub struct LndNode {
37 client: Client,
38 info: NodeInfo,
39}
40
41fn parse_node_features(features: HashSet<u32>) -> NodeFeatures {
44 let mut flags = vec![0; 256];
45
46 for f in features.into_iter() {
47 let byte_offset = (f / 8) as usize;
48 let mask = 1 << (f - 8 * byte_offset as u32);
49 if flags.len() <= byte_offset {
50 flags.resize(byte_offset + 1, 0u8);
51 }
52
53 flags[byte_offset] |= mask
54 }
55
56 NodeFeatures::from_le_bytes(flags)
57}
58
59impl LndNode {
60 pub async fn new(connection: LndConnection) -> Result<Self, LightningError> {
61 let mut client =
62 tonic_lnd::connect(connection.address, connection.cert, connection.macaroon)
63 .await
64 .map_err(|err| LightningError::ConnectionError(err.to_string()))?;
65
66 let GetInfoResponse {
67 identity_pubkey,
68 features,
69 mut alias,
70 ..
71 } = client
72 .lightning()
73 .get_info(GetInfoRequest {})
74 .await
75 .map_err(|err| LightningError::GetInfoError(err.to_string()))?
76 .into_inner();
77
78 let pubkey = PublicKey::from_str(&identity_pubkey)
79 .map_err(|err| LightningError::GetInfoError(err.to_string()))?;
80 connection.id.validate(&pubkey, &mut alias)?;
81
82 Ok(Self {
83 client,
84 info: NodeInfo {
85 pubkey,
86 features: parse_node_features(features.keys().cloned().collect()),
87 alias,
88 },
89 })
90 }
91}
92
93#[async_trait]
94impl LightningNode for LndNode {
95 fn get_info(&self) -> &NodeInfo {
99 &self.info
100 }
101
102 async fn get_network(&mut self) -> Result<Network, LightningError> {
103 let info = self
104 .client
105 .lightning()
106 .get_info(GetInfoRequest {})
107 .await
108 .map_err(|err| LightningError::GetInfoError(err.to_string()))?
109 .into_inner();
110
111 if info.chains.is_empty() {
112 return Err(LightningError::ValidationError(format!(
113 "{} is not connected any chain",
114 self.get_info()
115 )));
116 } else if info.chains.len() > 1 {
117 return Err(LightningError::ValidationError(format!(
118 "{} is connected to more than one chain: {:?}",
119 self.get_info(),
120 info.chains.iter().map(|c| c.chain.to_string())
121 )));
122 }
123
124 Ok(Network::from_str(match info.chains[0].network.as_str() {
125 "mainnet" => "bitcoin",
126 "simnet" => {
127 return Err(LightningError::ValidationError(
128 "simnet is not supported".to_string(),
129 ))
130 },
131 x => x,
132 })
133 .map_err(|err| LightningError::ValidationError(err.to_string()))?)
134 }
135
136 async fn send_payment(
137 &mut self,
138 dest: PublicKey,
139 amount_msat: u64,
140 ) -> Result<PaymentHash, LightningError> {
141 let amt_msat: i64 = amount_msat
142 .try_into()
143 .map_err(|_| LightningError::SendPaymentError("Invalid send amount".to_string()))?;
144
145 let preimage = PaymentPreimage(rand::random());
146
147 let mut dest_custom_records = HashMap::new();
148 let payment_hash = sha256::Hash::hash(&preimage.0).to_byte_array().to_vec();
149 dest_custom_records.insert(KEYSEND_KEY, preimage.0.to_vec());
150
151 let response = self
152 .client
153 .router()
154 .send_payment_v2(SendPaymentRequest {
155 amt_msat,
156 dest: dest.serialize().to_vec(),
157 dest_custom_records,
158 payment_hash,
159 timeout_seconds: SEND_PAYMENT_TIMEOUT_SECS,
160 fee_limit_msat: i64::max_value(),
161 ..Default::default()
162 })
163 .await
164 .map_err(status_to_lightning_error)?;
165
166 let mut stream = response.into_inner();
167
168 let payment_hash = match stream.message().await.map_err(status_to_lightning_error)? {
169 Some(payment) => string_to_payment_hash(&payment.payment_hash)?,
170 None => return Err(LightningError::SendPaymentError("No payment".to_string())),
171 };
172
173 Ok(payment_hash)
174 }
175
176 async fn track_payment(
177 &mut self,
178 hash: &PaymentHash,
179 shutdown: Listener,
180 ) -> Result<PaymentResult, LightningError> {
181 let response = self
182 .client
183 .router()
184 .track_payment_v2(TrackPaymentRequest {
185 payment_hash: hash.0.to_vec(),
186 no_inflight_updates: true,
187 })
188 .await
189 .map_err(|err| LightningError::TrackPaymentError(err.to_string()))?;
190
191 let mut stream = response.into_inner();
192
193 tokio::select! {
194 biased;
195 _ = shutdown => { Err(LightningError::TrackPaymentError("Shutdown before tracking results".to_string())) },
196 stream = stream.message() => {
197 let payment = stream.map_err(|err| LightningError::TrackPaymentError(err.to_string()))?;
198 match payment {
199 Some(payment) => {
200 let payment_status: PaymentStatus = payment.status.try_into()
201 .map_err(|_| LightningError::TrackPaymentError("Invalid payment status".to_string()))?;
202 let failure_reason: PaymentFailureReason = payment.failure_reason.try_into()
203 .map_err(|_| LightningError::TrackPaymentError("Invalid failure reason".to_string()))?;
204
205 let payment_outcome = match payment_status {
206 PaymentStatus::Succeeded => PaymentOutcome::Success,
207 PaymentStatus::Failed => match failure_reason {
208 PaymentFailureReason::FailureReasonTimeout => PaymentOutcome::PaymentExpired,
209 PaymentFailureReason::FailureReasonNoRoute => PaymentOutcome::RouteNotFound,
210 PaymentFailureReason::FailureReasonError => PaymentOutcome::UnexpectedError,
211 PaymentFailureReason::FailureReasonIncorrectPaymentDetails => PaymentOutcome::IncorrectPaymentDetails,
212 PaymentFailureReason::FailureReasonInsufficientBalance => PaymentOutcome::InsufficientBalance,
213 _ => return Err(LightningError::TrackPaymentError("Unexpected failure reason".to_string())),
215 },
216 _ => PaymentOutcome::Unknown,
218 };
219 return Ok(PaymentResult {
220 htlc_count: payment.htlcs.len(),
221 payment_outcome
222 });
223 },
224 None => {
225 return Err(LightningError::TrackPaymentError(
226 "No payment".to_string(),
227 ));
228 },
229 }
230 },
231 }
232 }
233
234 async fn get_node_info(&mut self, node_id: &PublicKey) -> Result<NodeInfo, LightningError> {
235 let node_info = self
236 .client
237 .lightning()
238 .get_node_info(NodeInfoRequest {
239 pub_key: node_id.to_string(),
240 include_channels: false,
241 })
242 .await
243 .map_err(|err| LightningError::GetNodeInfoError(err.to_string()))?
244 .into_inner();
245
246 if let Some(node_info) = node_info.node {
247 Ok(NodeInfo {
248 pubkey: *node_id,
249 alias: node_info.alias,
250 features: parse_node_features(node_info.features.keys().cloned().collect()),
251 })
252 } else {
253 Err(LightningError::GetNodeInfoError(
254 "Node not found".to_string(),
255 ))
256 }
257 }
258
259 async fn list_channels(&mut self) -> Result<Vec<u64>, LightningError> {
260 let channels = self
261 .client
262 .lightning()
263 .list_channels(ListChannelsRequest {
264 ..Default::default()
265 })
266 .await
267 .map_err(|err| LightningError::ListChannelsError(err.to_string()))?
268 .into_inner();
269
270 Ok(channels
272 .channels
273 .iter()
274 .map(|channel| 1000 * channel.capacity as u64)
275 .collect())
276 }
277}
278
279fn string_to_payment_hash(hash: &str) -> Result<PaymentHash, LightningError> {
280 let bytes = hex::decode(hash).map_err(|_| LightningError::InvalidPaymentHash)?;
281 let slice: [u8; 32] = bytes
282 .as_slice()
283 .try_into()
284 .map_err(|_| LightningError::InvalidPaymentHash)?;
285 Ok(PaymentHash(slice))
286}
287
288fn status_to_lightning_error(s: Status) -> LightningError {
289 let code = s.code();
290 let message = s.message();
291 match code {
292 Unavailable => LightningError::SendPaymentError(format!("Node unavailable: {message}")),
293 _ => LightningError::PermanentError(message.to_string()),
294 }
295}