1use std::fmt;
2
3use anyhow::Context;
4use bitcoin::{Amount, SignedAmount};
5use bitcoin::hex::DisplayHex;
6use lightning::util::ser::Writeable;
7use lnurllib::lightning_address::LightningAddress;
8use log::{debug, error, info, trace, warn};
9use server_rpc::protos::{self, lightning_payment_status::PaymentStatus};
10
11use ark::{musig, VtxoPolicy};
12use ark::arkoor::ArkoorDestination;
13use ark::arkoor::package::{ArkoorPackageBuilder, ArkoorPackageCosignResponse};
14use ark::lightning::{Bolt12Invoice, Bolt12InvoiceExt, Invoice, Offer, PaymentHash, Preimage};
15use ark::util::IteratorExt;
16use bitcoin_ext::BlockHeight;
17
18use crate::{Wallet, WalletVtxo};
19use crate::lightning::lnaddr_invoice;
20use crate::movement::{MovementDestination, MovementStatus, PaymentMethod};
21use crate::movement::update::MovementUpdate;
22use crate::persist::models::LightningSend;
23use crate::subsystem::{LightningMovement, LightningSendMovement, Subsystem};
24
25
26impl Wallet {
27 pub async fn pending_lightning_sends(&self) -> anyhow::Result<Vec<LightningSend>> {
29 Ok(self.db.get_all_pending_lightning_send().await?)
30 }
31
32 pub async fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
34 let vtxos = self.db.get_all_pending_lightning_send().await?.into_iter()
35 .flat_map(|pending_lightning_send| pending_lightning_send.htlc_vtxos)
36 .collect::<Vec<_>>();
37
38 Ok(vtxos)
39 }
40
41 pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
44 let pending_payments = self.pending_lightning_sends().await?;
45
46 if pending_payments.is_empty() {
47 return Ok(());
48 }
49
50 info!("Syncing {} pending lightning sends", pending_payments.len());
51
52 for payment in pending_payments {
53 let payment_hash = payment.invoice.payment_hash();
54 self.check_lightning_payment(payment_hash, false).await?;
55 }
56
57 Ok(())
58 }
59
60 async fn process_lightning_revocation(&self, payment: &LightningSend) -> anyhow::Result<()> {
81 let (mut srv, _) = self.require_server().await?;
82 let htlc_vtxos = payment.htlc_vtxos.clone().into_iter()
83 .map(|v| v.vtxo).collect::<Vec<_>>();
84
85 debug!("Processing {} HTLC VTXOs for revocation", htlc_vtxos.len());
86
87 let mut secs = Vec::with_capacity(htlc_vtxos.len());
88 let mut pubs = Vec::with_capacity(htlc_vtxos.len());
89 let mut htlc_keypairs = Vec::with_capacity(htlc_vtxos.len());
90 for input in htlc_vtxos.iter() {
91 let keypair = self.get_vtxo_key(input).await?;
92 let (s, p) = musig::nonce_pair(&keypair);
93 secs.push(s);
94 pubs.push(p);
95 htlc_keypairs.push(keypair);
96 }
97
98 let (revocation_keypair, _) = self.derive_store_next_keypair().await?;
99
100 let revocation_claim_policy = VtxoPolicy::new_pubkey(revocation_keypair.public_key());
101 let builder = ArkoorPackageBuilder::new_claim_all_with_checkpoints(
102 htlc_vtxos.iter().cloned(),
103 revocation_claim_policy,
104 )
105 .context("Failed to construct arkoor package")?
106 .generate_user_nonces(&htlc_keypairs)?;
107
108 let cosign_request = protos::ArkoorPackageCosignRequest::from(
109 builder.cosign_request().convert_vtxo(|vtxo| vtxo.id())
110 );
111
112 let response = srv.client
113 .request_lightning_pay_htlc_revocation(cosign_request).await
114 .context("server failed to cosign arkoor")?.into_inner();
115
116 let cosign_resp = ArkoorPackageCosignResponse::try_from(response)
117 .context("Failed to parse cosign response from server")?;
118
119 let vtxos = builder
120 .user_cosign(&htlc_keypairs, cosign_resp)
121 .context("Failed to cosign vtxos")?
122 .build_signed_vtxos();
123
124 let mut revoked = Amount::ZERO;
125 for vtxo in &vtxos {
126 debug!("Got revocation VTXO: {}: {}", vtxo.id(), vtxo.amount());
127 revoked += vtxo.amount();
128 }
129
130 let count = vtxos.len();
131 let effective = -payment.amount.to_signed()? - payment.fee.to_signed()? + revoked.to_signed()?;
132 if effective != SignedAmount::ZERO {
133 warn!("Movement {} should have fee of zero, but got {}: amount = {}, fee = {}, revoked = {}",
134 payment.movement_id, effective, payment.amount, payment.fee, revoked,
135 );
136 }
137 self.movements.finish_movement_with_update(
138 payment.movement_id,
139 MovementStatus::Failed,
140 MovementUpdate::new()
141 .effective_balance(effective)
142 .fee(effective.unsigned_abs())
143 .produced_vtxos(&vtxos)
144 ).await?;
145 self.store_spendable_vtxos(&vtxos).await?;
146 self.mark_vtxos_as_spent(&htlc_vtxos).await?;
147
148 self.db.remove_lightning_send(payment.invoice.payment_hash()).await?;
149
150 debug!("Revoked {} HTLC VTXOs", count);
151
152 Ok(())
153 }
154
155 async fn process_lightning_send_server_preimage(
170 &self,
171 preimage: Option<Vec<u8>>,
172 payment: &LightningSend,
173 ) -> anyhow::Result<Option<Preimage>> {
174 let payment_hash = payment.invoice.payment_hash();
175 let preimage_res = preimage
176 .context("preimage is missing")
177 .map(|p| Ok(Preimage::try_from(p)?))
178 .flatten();
179
180 match preimage_res {
181 Ok(preimage) if preimage.compute_payment_hash() == payment_hash => {
182 info!("Lightning payment succeeded! Preimage: {}. Payment hash: {}",
183 preimage.as_hex(), payment.invoice.payment_hash().as_hex());
184
185 self.db.finish_lightning_send(payment_hash, Some(preimage)).await?;
187 self.mark_vtxos_as_spent(&payment.htlc_vtxos).await?;
188 self.movements.finish_movement_with_update(
189 payment.movement_id,
190 MovementStatus::Successful,
191 MovementUpdate::new().metadata([(
192 "payment_preimage".into(),
193 serde_json::to_value(preimage).expect("payment preimage can serde"),
194 )])
195 ).await?;
196
197 Ok(Some(preimage))
198 },
199 _ => {
200 error!("Server failed to provide a valid preimage. \
201 Payment hash: {}. Preimage result: {:#?}", payment_hash, preimage_res
202 );
203 Ok(None)
204 }
205 }
206 }
207
208 pub async fn check_lightning_payment(&self, payment_hash: PaymentHash, wait: bool)
235 -> anyhow::Result<Option<Preimage>>
236 {
237 trace!("Checking lightning payment status for payment hash: {}", payment_hash);
238
239 {
243 let mut inflight = self.inflight_lightning_payments.lock().await;
244 if !inflight.insert(payment_hash) {
245 bail!("Payment operation already in progress for this invoice");
246 }
247 }
248
249 let result = self.check_lightning_payment_inner(payment_hash, wait).await;
250
251 {
253 let mut inflight = self.inflight_lightning_payments.lock().await;
254 inflight.remove(&payment_hash);
255 }
256
257 result
258 }
259
260 async fn check_lightning_payment_inner(&self, payment_hash: PaymentHash, wait: bool)
262 -> anyhow::Result<Option<Preimage>>
263 {
264 let (mut srv, _) = self.require_server().await?;
265
266 let payment = self.db.get_lightning_send(payment_hash).await?
267 .context("no lightning send found for payment hash")?;
268
269 if let Some(preimage) = payment.preimage {
271 trace!("Payment already completed with preimage: {}", preimage.as_hex());
272 return Ok(Some(preimage));
273 }
274
275 if payment.htlc_vtxos.is_empty() {
276 bail!("No HTLC VTXOs found for payment");
277 }
278
279 let policy = payment.htlc_vtxos.iter()
280 .all_same(|v| v.vtxo.policy())
281 .ok_or(anyhow::anyhow!("All lightning htlc should have the same policy"))?;
282
283 let policy = policy.as_server_htlc_send().context("VTXO is not an HTLC send")?;
284 if policy.payment_hash != payment_hash {
285 bail!("Payment hash mismatch");
286 }
287
288 let req = protos::CheckLightningPaymentRequest {
289 hash: payment_hash.to_vec(),
290 wait,
291 };
292 let response = srv.client.check_lightning_payment(req).await
295 .map(|r| r.into_inner().payment_status);
296
297 let tip = self.chain.tip().await?;
298 let min_vtxo_expiry = payment.htlc_vtxos.iter()
299 .map(|v| v.vtxo.expiry_height())
300 .min().context("no HTLC VTXOs for expiry check")?;
301 let expired = tip > policy.htlc_expiry
302 || tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold);
303
304 let should_revoke = match response {
305 Ok(Some(PaymentStatus::Success(status))) => {
306 let preimage_opt = self.process_lightning_send_server_preimage(
307 Some(status.preimage), &payment,
308 ).await?;
309
310 if let Some(preimage) = preimage_opt {
311 return Ok(Some(preimage));
312 } else {
313 trace!("Server said payment is complete, but has no valid preimage: {:?}", preimage_opt);
314 expired
315 }
316 },
317 Ok(Some(PaymentStatus::Failed(_))) => {
318 info!("Payment failed, revoking VTXO");
319 true
320 },
321 Ok(Some(PaymentStatus::Pending(_))) => {
322 trace!("Payment is still pending");
323 expired
324 },
325 Ok(None) | Err(_) => expired,
327 };
328
329 if should_revoke {
330 debug!("Revoking HTLC VTXOs for payment {} (tip: {}, expiry: {})",
331 payment_hash, tip, policy.htlc_expiry);
332
333 if let Err(e) = self.process_lightning_revocation(&payment).await {
334 warn!("Failed to revoke VTXO: {}", e);
335
336 if tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold) {
340 warn!("HTLC VTXOs for payment {} are near VTXO expiry, marking to exit", payment_hash);
341
342 let vtxos = payment.htlc_vtxos
343 .iter()
344 .map(|v| v.vtxo.clone())
345 .collect::<Vec<_>>();
346 self.exit.write().await.start_exit_for_vtxos(&vtxos).await?;
347
348 let exited = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
349 let effective = -payment.amount.to_signed()? - payment.fee.to_signed()? + exited.to_signed()?;
350 if effective != SignedAmount::ZERO {
351 warn!("Movement {} should have fee of zero, but got {}: amount = {}, fee = {}, exited = {}",
352 payment.movement_id, effective, payment.amount, payment.fee, exited,
353 );
354 }
355 self.movements.finish_movement_with_update(
356 payment.movement_id,
357 MovementStatus::Failed,
358 MovementUpdate::new()
359 .effective_balance(effective)
360 .fee(effective.unsigned_abs())
361 .exited_vtxos(&vtxos)
362 ).await?;
363 self.db.finish_lightning_send(payment.invoice.payment_hash(), None).await?;
364 }
365
366 return Err(e)
367 }
368 }
369
370 Ok(None)
371 }
372
373 pub async fn pay_lightning_invoice<T>(
380 &self,
381 invoice: T,
382 user_amount: Option<Amount>,
383 ) -> anyhow::Result<LightningSend>
384 where
385 T: TryInto<Invoice>,
386 T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
387 {
388 let invoice = invoice.try_into().context("failed to parse invoice")?;
389 let amount = invoice.get_final_amount(user_amount)?;
390 info!("Sending bolt11 payment of {} to invoice {}", amount, invoice);
391 self.make_lightning_payment(&invoice, invoice.clone().into(), user_amount).await
392 }
393
394 pub async fn pay_lightning_address(
396 &self,
397 addr: &LightningAddress,
398 amount: Amount,
399 comment: Option<impl AsRef<str>>,
400 ) -> anyhow::Result<LightningSend> {
401 let comment = comment.as_ref();
402 let invoice = lnaddr_invoice(addr, amount, comment).await
403 .context("lightning address error")?;
404 info!("Sending {} to lightning address {}", amount, addr);
405 let ret = self.make_lightning_payment(&invoice.into(), addr.clone().into(), None).await
406 .context("bolt11 payment error")?;
407 info!("Paid invoice {}", ret.invoice);
408 Ok(ret)
409 }
410
411 pub async fn pay_lightning_offer(
413 &self,
414 offer: Offer,
415 user_amount: Option<Amount>,
416 ) -> anyhow::Result<LightningSend> {
417 let (mut srv, _) = self.require_server().await?;
418
419 let offer_bytes = {
420 let mut bytes = Vec::new();
421 offer.write(&mut bytes).context("failed to serialize BOLT12 offer")?;
422 bytes
423 };
424
425 let req = protos::FetchBolt12InvoiceRequest {
426 offer: offer_bytes,
427 amount_sat: user_amount.map(|a| a.to_sat()),
428 };
429
430 if let Some(amt) = user_amount {
431 info!("Sending bolt12 payment of {} (user amount) to offer {}", amt, offer);
432 } else if let Some(amt) = offer.amount() {
433 info!("Sending bolt12 payment of {:?} (invoice amount) to offer {}", amt, offer);
434 } else {
435 warn!("Paying offer without amount nor user amount provided: {}", offer);
436 }
437
438 let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
439 let invoice = Bolt12Invoice::try_from(resp.invoice)
440 .map_err(|e| anyhow!("invalid invoice: {:?}", e))?;
441
442 invoice.validate_issuance(&offer)
443 .context("invalid BOLT12 invoice received from offer")?;
444
445 let ret = self.make_lightning_payment(&invoice.into(), offer.into(), None).await
446 .context("bolt12 payment error")?;
447 info!("Paid invoice: {:?}", ret.invoice);
448
449 Ok(ret)
450 }
451
452 pub async fn make_lightning_payment(
489 &self,
490 invoice: &Invoice,
491 original_payment_method: PaymentMethod,
492 user_amount: Option<Amount>,
493 ) -> anyhow::Result<LightningSend> {
494 if !original_payment_method.is_lightning() && !original_payment_method.is_custom() {
495 bail!("Invalid original payment method for lightning payment");
496 }
497
498 let payment_hash = invoice.payment_hash();
499
500 {
504 let mut inflight = self.inflight_lightning_payments.lock().await;
505 if !inflight.insert(payment_hash) {
506 bail!("Payment already in progress for this invoice");
507 }
508 }
509
510 let result = self.make_lightning_payment_inner(
512 invoice, original_payment_method, user_amount, payment_hash
513 ).await;
514
515 {
517 let mut inflight = self.inflight_lightning_payments.lock().await;
518 inflight.remove(&payment_hash);
519 }
520
521 result
522 }
523
524 async fn make_lightning_payment_inner(
526 &self,
527 invoice: &Invoice,
528 original_payment_method: PaymentMethod,
529 user_amount: Option<Amount>,
530 payment_hash: PaymentHash,
531 ) -> anyhow::Result<LightningSend> {
532 let (mut srv, ark_info) = self.require_server().await?;
533
534 let tip = self.chain.tip().await?;
535
536 let properties = self.db.read_properties().await?.context("Missing config")?;
537 if invoice.network() != properties.network {
538 bail!("Invoice is for wrong network: {}", invoice.network());
539 }
540
541 let lightning_send = self.db.get_lightning_send(payment_hash).await?;
542 if lightning_send.is_some() {
543 bail!("Invoice has already been paid");
544 }
545
546 invoice.check_signature()?;
547
548 let amount = invoice.get_final_amount(user_amount)?;
549 if amount == Amount::ZERO {
550 bail!("Cannot pay invoice for 0 sats (0 sat invoices are not any-amount invoices)");
551 }
552
553 let (user_keypair, _) = self.derive_store_next_keypair().await?;
554
555 let (inputs, fee) = self.select_vtxos_to_cover_with_fee(
556 amount, |a, v| ark_info.fees.lightning_send.calculate(a, v).context("fee overflowed"),
557 ).await.context("Could not find enough suitable VTXOs to cover lightning payment")?;
558 let total_amount = amount + fee;
559
560 let mut secs = Vec::with_capacity(inputs.len());
561 let mut pubs = Vec::with_capacity(inputs.len());
562 let mut input_keypairs = Vec::with_capacity(inputs.len());
563 let mut input_ids = Vec::with_capacity(inputs.len());
564 for input in inputs.iter() {
565 let keypair = self.get_vtxo_key(input).await?;
566 let (s, p) = musig::nonce_pair(&keypair);
567 secs.push(s);
568 pubs.push(p);
569 input_keypairs.push(keypair);
570 input_ids.push(input.id());
571 }
572
573 let expiry = tip + ark_info.htlc_send_expiry_delta as BlockHeight;
574 let policy = VtxoPolicy::new_server_htlc_send(
575 user_keypair.public_key(), invoice.payment_hash(), expiry,
576 );
577
578 let input_amount = inputs.iter().map(|v| v.amount()).sum::<Amount>();
579 let pay_dest = ArkoorDestination { total_amount, policy };
580 let outputs = if input_amount == total_amount {
581 vec![pay_dest]
582 } else {
583 let change_dest = ArkoorDestination {
584 total_amount: input_amount - total_amount,
585 policy: VtxoPolicy::new_pubkey(user_keypair.public_key()),
586 };
587 vec![pay_dest, change_dest]
588 };
589 let builder = ArkoorPackageBuilder::new_with_checkpoints(
590 inputs.iter().map(|v| &v.vtxo).cloned(),
591 outputs,
592 )
593 .context("Failed to construct arkoor package")?
594 .generate_user_nonces(&input_keypairs)
595 .context("invalid nb of keypairs")?;
596
597 let cosign_request = protos::LightningPayHtlcCosignRequest {
598 invoice: invoice.to_string(),
599 payment_amount_sat: amount.to_sat(),
600 parts: builder.cosign_request()
601 .convert_vtxo(|vtxo| vtxo.id())
602 .requests.into_iter()
603 .map(|r| r.into()).collect(),
604 };
605
606 let response = srv.client.request_lightning_pay_htlc_cosign(cosign_request).await
607 .context("htlc request failed")?.into_inner();
608
609 let cosign_responses = ArkoorPackageCosignResponse::try_from(response)
610 .context("Failed to parse cosign response from server")?;
611
612 let vtxos = builder
613 .user_cosign(&input_keypairs, cosign_responses)
614 .context("Failed to cosign vtxos")?
615 .build_signed_vtxos();
616
617 let (htlc_vtxos, change_vtxos) = vtxos.into_iter()
618 .partition::<Vec<_>, _>(|v| matches!(v.policy(), VtxoPolicy::ServerHtlcSend(_)));
619
620 let mut effective_balance = Amount::ZERO;
622 for vtxo in &htlc_vtxos {
623 self.validate_vtxo(vtxo).await?;
624 effective_balance += vtxo.amount();
625 }
626
627 let movement_id = self.movements.new_movement_with_update(
628 Subsystem::LIGHTNING_SEND,
629 LightningSendMovement::Send.to_string(),
630 MovementUpdate::new()
631 .intended_balance(-amount.to_signed()?)
632 .effective_balance(-effective_balance.to_signed()?)
633 .fee(fee)
634 .consumed_vtxos(&inputs)
635 .sent_to([MovementDestination::new(original_payment_method, amount)])
636 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
637 ).await?;
638 self.store_locked_vtxos(&htlc_vtxos, Some(movement_id)).await?;
639 self.mark_vtxos_as_spent(&input_ids).await?;
640
641 for change in &change_vtxos {
643 let last_input = inputs.last().context("no inputs provided")?;
644 let tx = self.chain.get_tx(&last_input.chain_anchor().txid).await?;
645 let tx = tx.with_context(|| {
646 format!("input vtxo chain anchor not found for lightning change vtxo: {}", last_input.chain_anchor().txid)
647 })?;
648 change.validate(&tx).context("invalid lightning change vtxo")?;
649 self.store_spendable_vtxos([change]).await?;
650 }
651
652 self.movements.update_movement(
653 movement_id,
654 MovementUpdate::new()
655 .produced_vtxos(change_vtxos)
656 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
657 ).await?;
658
659 let lightning_send = self.db.store_new_pending_lightning_send(
660 &invoice,
661 amount,
662 fee,
663 &htlc_vtxos.iter().map(|v| v.id()).collect::<Vec<_>>(),
664 movement_id,
665 ).await?;
666
667 self.register_vtxos_with_server(&htlc_vtxos).await?;
669
670 let req = protos::InitiateLightningPaymentRequest {
671 invoice: invoice.to_string(),
672 htlc_vtxo_ids: htlc_vtxos.iter().map(|v| v.id().to_bytes().to_vec()).collect(),
673 requested_payment_sat: amount.to_sat(),
674 };
675
676 srv.client.initiate_lightning_payment(req).await?;
677
678 Ok(lightning_send)
679 }
680}