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::{ProtocolEncoding, VtxoPolicy, musig};
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};
24use crate::vtxo::VtxoLockHolder;
25
26const LIGHTNING_PAY_LOCK_PREFIX: &str = "lightning_pay";
27
28impl Wallet {
29 pub async fn pending_lightning_sends(&self) -> anyhow::Result<Vec<LightningSend>> {
31 Ok(self.inner.db.get_all_pending_lightning_send().await?)
32 }
33
34 pub async fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
36 let vtxos = self.inner.db.get_all_pending_lightning_send().await?.into_iter()
37 .flat_map(|pending_lightning_send| pending_lightning_send.htlc_vtxos)
38 .collect::<Vec<_>>();
39
40 Ok(vtxos)
41 }
42
43 pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
46 let pending_payments = self.pending_lightning_sends().await?;
47
48 if pending_payments.is_empty() {
49 return Ok(());
50 }
51
52 info!("Syncing {} pending lightning sends", pending_payments.len());
53
54 for payment in pending_payments {
55 let payment_hash = payment.invoice.payment_hash();
56 self.check_lightning_payment(payment_hash, false).await?;
57 }
58
59 Ok(())
60 }
61
62 async fn process_lightning_revocation(&self, payment: &LightningSend) -> anyhow::Result<()> {
83 let (mut srv, _) = self.require_server().await?;
84 let htlc_ids = payment.htlc_vtxos.iter()
85 .map(|v| v.vtxo.id()).collect::<Vec<_>>();
86
87 debug!("Processing {} HTLC VTXOs for revocation", htlc_ids.len());
88
89 let htlc_vtxos = self.inner.db.get_full_vtxos(&htlc_ids).await
91 .context("failed to hydrate htlc input vtxos for revocation")?;
92
93 let mut secs = Vec::with_capacity(htlc_vtxos.len());
94 let mut pubs = Vec::with_capacity(htlc_vtxos.len());
95 let mut htlc_keypairs = Vec::with_capacity(htlc_vtxos.len());
96 for input in htlc_vtxos.iter() {
97 let keypair = self.get_vtxo_key(input).await?;
98 let (s, p) = musig::nonce_pair(&keypair);
99 secs.push(s);
100 pubs.push(p);
101 htlc_keypairs.push(keypair);
102 }
103
104 let (revocation_keypair, _) = self.derive_store_next_keypair().await?;
105
106 let revocation_claim_policy = VtxoPolicy::new_pubkey(revocation_keypair.public_key());
107 let builder = ArkoorPackageBuilder::new_claim_all_with_checkpoints(
108 htlc_vtxos,
109 revocation_claim_policy,
110 )
111 .context("Failed to construct arkoor package")?
112 .generate_user_nonces(&htlc_keypairs)?;
113
114 let cosign_request = protos::ArkoorPackageCosignRequest::from(
115 builder.cosign_request(),
116 );
117
118 let response = srv.client
119 .request_lightning_pay_htlc_revocation(cosign_request).await
120 .context("server failed to cosign arkoor")?.into_inner();
121
122 let cosign_resp = ArkoorPackageCosignResponse::try_from(response)
123 .context("Failed to parse cosign response from server")?;
124
125 let vtxos = builder
126 .user_cosign(&htlc_keypairs, cosign_resp)
127 .context("Failed to cosign vtxos")?
128 .build_signed_vtxos();
129
130 let mut revoked = Amount::ZERO;
131 for vtxo in &vtxos {
132 debug!("Got revocation VTXO: {}: {}", vtxo.id(), vtxo.amount());
133 revoked += vtxo.amount();
134 }
135
136 let count = vtxos.len();
137 let effective = -payment.amount.to_signed()? - payment.fee.to_signed()? + revoked.to_signed()?;
138 if effective != SignedAmount::ZERO {
139 warn!("Movement {} should have fee of zero, but got {}: amount = {}, fee = {}, revoked = {}",
140 payment.movement_id, effective, payment.amount, payment.fee, revoked,
141 );
142 }
143 self.inner.movements.finish_movement_with_update(
144 payment.movement_id,
145 MovementStatus::Failed,
146 MovementUpdate::new()
147 .effective_balance(effective)
148 .fee(effective.unsigned_abs())
149 .produced_vtxos(&vtxos)
150 ).await?;
151 self.store_spendable_vtxos(&vtxos).await?;
152 self.mark_vtxos_as_spent(&payment.htlc_vtxos).await?;
153
154 self.inner.db.remove_lightning_send(payment.invoice.payment_hash()).await?;
155
156 debug!("Revoked {} HTLC VTXOs", count);
157
158 Ok(())
159 }
160
161 async fn process_lightning_send_server_preimage(
176 &self,
177 preimage: Option<Vec<u8>>,
178 payment: &LightningSend,
179 ) -> anyhow::Result<Option<Preimage>> {
180 let payment_hash = payment.invoice.payment_hash();
181 let preimage_res = preimage
182 .context("preimage is missing")
183 .map(|p| Ok(Preimage::try_from(p)?))
184 .flatten();
185
186 match preimage_res {
187 Ok(preimage) if preimage.compute_payment_hash() == payment_hash => {
188 info!("Lightning payment succeeded! Preimage: {}. Payment hash: {}",
189 preimage.as_hex(), payment.invoice.payment_hash().as_hex());
190
191 self.inner.db.finish_lightning_send(payment_hash, Some(preimage)).await?;
193 self.mark_vtxos_as_spent(&payment.htlc_vtxos).await?;
194 self.inner.movements.finish_movement_with_update(
195 payment.movement_id,
196 MovementStatus::Successful,
197 MovementUpdate::new().metadata([(
198 "payment_preimage".into(),
199 serde_json::to_value(preimage).expect("payment preimage can serde"),
200 )])
201 ).await?;
202
203 Ok(Some(preimage))
204 },
205 _ => {
206 error!("Server failed to provide a valid preimage. \
207 Payment hash: {}. Preimage result: {:#?}", payment_hash, preimage_res
208 );
209 Ok(None)
210 }
211 }
212 }
213
214 pub async fn check_lightning_payment(&self, payment_hash: PaymentHash, wait: bool)
239 -> anyhow::Result<Option<LightningSend>>
240 {
241 trace!("Checking lightning payment status for payment hash: {}", payment_hash);
242
243 let key = format!("{}.{}", LIGHTNING_PAY_LOCK_PREFIX, payment_hash);
247 let _guard = self.inner.lock_manager.try_lock(&key).await
248 .context("Payment operation already in progress for this invoice")?;
249
250 self.check_lightning_payment_inner(payment_hash, wait, None).await
251 }
252
253 pub(crate) async fn check_lightning_payment_with_preimage(
257 &self,
258 payment_hash: PaymentHash,
259 known_preimage: Option<Preimage>,
260 ) -> anyhow::Result<Option<LightningSend>>
261 {
262 trace!("Checking lightning payment status for payment hash: {}", payment_hash);
263
264 let key = format!("{}.{}", LIGHTNING_PAY_LOCK_PREFIX, payment_hash);
268 let _guard = self.inner.lock_manager.try_lock(&key).await
269 .context("Payment operation already in progress for this invoice")?;
270
271 self.check_lightning_payment_inner(payment_hash, false, known_preimage).await
272 }
273
274 async fn check_lightning_payment_inner(
279 &self,
280 payment_hash: PaymentHash,
281 wait: bool,
282 known_preimage: Option<Preimage>,
283 ) -> anyhow::Result<Option<LightningSend>>
284 {
285 let payment = self.inner.db.get_lightning_send(payment_hash).await?
286 .context("no lightning send found for payment hash")?;
287
288 if payment.preimage.is_some() {
290 trace!("Payment already completed with preimage");
291 return Ok(Some(payment));
292 }
293
294 if payment.htlc_vtxos.is_empty() {
295 bail!("No HTLC VTXOs found for payment");
296 }
297
298 let policy = payment.htlc_vtxos.iter()
299 .all_same(|v| v.vtxo.policy())
300 .ok_or(anyhow::anyhow!("All lightning htlc should have the same policy"))?;
301
302 let policy = policy.as_server_htlc_send().context("VTXO is not an HTLC send")?;
303 if policy.payment_hash != payment_hash {
304 bail!("Payment hash mismatch");
305 }
306
307 if let Some(preimage) = known_preimage {
310 let preimage_opt = self.process_lightning_send_server_preimage(
311 Some(preimage.to_vec()), &payment,
312 ).await?;
313
314 if preimage_opt.is_some() {
315 let updated_payment = self.inner.db.get_lightning_send(payment_hash).await?
316 .context("payment disappeared from database")?;
317 return Ok(Some(updated_payment));
318 }
319 warn!("Known preimage was invalid, falling back to server RPC");
321 }
322
323 let (mut srv, _) = self.require_server().await?;
324 let req = protos::CheckLightningPaymentRequest {
325 hash: payment_hash.to_vec(),
326 wait,
327 };
328 let response = srv.client.check_lightning_payment(req).await
331 .map(|r| r.into_inner().payment_status);
332
333 let tip = self.inner.chain.tip().await?;
334 let min_vtxo_expiry = payment.htlc_vtxos.iter()
335 .map(|v| v.vtxo.expiry_height())
336 .min().context("no HTLC VTXOs for expiry check")?;
337 let expired = tip > policy.htlc_expiry
338 || tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold);
339
340 let should_revoke = match response {
341 Ok(Some(PaymentStatus::Success(status))) => {
342 let preimage_opt = self.process_lightning_send_server_preimage(
343 Some(status.preimage), &payment,
344 ).await?;
345
346 if preimage_opt.is_some() {
347 let updated_payment = self.inner.db.get_lightning_send(payment_hash).await?
349 .context("payment disappeared from database")?;
350 return Ok(Some(updated_payment));
351 } else {
352 trace!("Server said payment is complete, but has no valid preimage: {:?}", preimage_opt);
353 expired
354 }
355 },
356 Ok(Some(PaymentStatus::Failed(_))) => {
357 info!("Payment failed, revoking VTXO");
358 true
359 },
360 Ok(Some(PaymentStatus::Pending(_))) => {
361 trace!("Payment is still pending");
362 expired
363 },
364 Ok(None) | Err(_) => expired,
366 };
367
368 if should_revoke {
369 debug!("Revoking HTLC VTXOs for payment {} (tip: {}, expiry: {})",
370 payment_hash, tip, policy.htlc_expiry);
371
372 if let Err(e) = self.process_lightning_revocation(&payment).await {
373 warn!("Failed to revoke VTXO: {}", e);
374
375 if tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold) {
379 warn!("HTLC VTXOs for payment {} are near VTXO expiry, marking to exit", payment_hash);
380
381 let vtxos = payment.htlc_vtxos
382 .iter()
383 .map(|v| v.vtxo.clone())
384 .collect::<Vec<_>>();
385 self.inner.exit.start_exit_for_vtxos(&vtxos).await?;
386
387 let exited = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
388 let effective = -payment.amount.to_signed()? - payment.fee.to_signed()? + exited.to_signed()?;
389 if effective != SignedAmount::ZERO {
390 warn!("Movement {} should have fee of zero, but got {}: amount = {}, fee = {}, exited = {}",
391 payment.movement_id, effective, payment.amount, payment.fee, exited,
392 );
393 }
394 self.inner.movements.finish_movement_with_update(
395 payment.movement_id,
396 MovementStatus::Failed,
397 MovementUpdate::new()
398 .effective_balance(effective)
399 .fee(effective.unsigned_abs())
400 .exited_vtxos(&vtxos)
401 ).await?;
402 self.inner.db.finish_lightning_send(payment.invoice.payment_hash(), None).await?;
403 }
404
405 return Err(e)
406 }
407 }
408
409 Ok(self.inner.db.get_lightning_send(payment_hash).await?)
411 }
412
413 pub async fn pay_lightning_invoice<T>(
420 &self,
421 invoice: T,
422 user_amount: Option<Amount>,
423 ) -> anyhow::Result<LightningSend>
424 where
425 T: TryInto<Invoice>,
426 T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
427 {
428 let invoice = invoice.try_into().context("failed to parse invoice")?;
429 let amount = invoice.get_payment_amount(user_amount)?;
430 info!("Sending bolt11 payment of {} to invoice {}", amount, invoice);
431 self.make_lightning_payment(&invoice, invoice.clone().into(), user_amount).await
432 }
433
434 pub async fn pay_lightning_address(
436 &self,
437 addr: &LightningAddress,
438 amount: Amount,
439 comment: Option<impl AsRef<str>>,
440 ) -> anyhow::Result<LightningSend> {
441 let comment = comment.as_ref();
442 let invoice = lnaddr_invoice(addr, amount, comment).await
443 .context("lightning address error")?;
444 info!("Sending {} to lightning address {}", amount, addr);
445 let ret = self.make_lightning_payment(&invoice.into(), addr.clone().into(), None).await
446 .context("bolt11 payment error")?;
447 info!("Paid invoice {}", ret.invoice);
448 Ok(ret)
449 }
450
451 pub async fn pay_lightning_offer(
453 &self,
454 offer: Offer,
455 user_amount: Option<Amount>,
456 ) -> anyhow::Result<LightningSend> {
457 let (mut srv, _) = self.require_server().await?;
458
459 let offer_bytes = {
460 let mut bytes = Vec::new();
461 offer.write(&mut bytes).context("failed to serialize BOLT12 offer")?;
462 bytes
463 };
464
465 let req = protos::FetchBolt12InvoiceRequest {
466 offer: offer_bytes,
467 amount_sat: user_amount.map(|a| a.to_sat()),
468 };
469
470 if let Some(amt) = user_amount {
471 info!("Sending bolt12 payment of {} (user amount) to offer {}", amt, offer);
472 } else if let Some(amt) = offer.amount() {
473 info!("Sending bolt12 payment of {:?} (invoice amount) to offer {}", amt, offer);
474 } else {
475 warn!("Paying offer without amount nor user amount provided: {}", offer);
476 }
477
478 let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
479 let invoice = Bolt12Invoice::try_from(resp.invoice)
480 .map_err(|e| anyhow!("invalid invoice: {:?}", e))?;
481
482 invoice.validate_issuance(&offer)
483 .context("invalid BOLT12 invoice received from offer")?;
484
485 let ret = self.make_lightning_payment(&invoice.into(), offer.into(), None).await
486 .context("bolt12 payment error")?;
487 info!("Paid invoice: {}", ret.invoice.to_string());
488
489 Ok(ret)
490 }
491
492 pub async fn make_lightning_payment(
529 &self,
530 invoice: &Invoice,
531 original_payment_method: PaymentMethod,
532 user_amount: Option<Amount>,
533 ) -> anyhow::Result<LightningSend> {
534 if !original_payment_method.is_lightning() && !original_payment_method.is_custom() {
535 bail!("Invalid original payment method for lightning payment");
536 }
537
538 let payment_hash = invoice.payment_hash();
539
540 let key = format!("{}.{}", LIGHTNING_PAY_LOCK_PREFIX, payment_hash);
544 let _guard = self.inner.lock_manager.try_lock(&key).await
545 .context("Payment operation already in progress for this invoice")?;
546
547 self.make_lightning_payment_inner(invoice, original_payment_method, user_amount, payment_hash).await
549 }
550
551 async fn make_lightning_payment_inner(
553 &self,
554 invoice: &Invoice,
555 original_payment_method: PaymentMethod,
556 user_amount: Option<Amount>,
557 payment_hash: PaymentHash,
558 ) -> anyhow::Result<LightningSend> {
559 let (mut srv, ark_info) = self.require_server().await?;
560
561 let tip = self.inner.chain.tip().await?;
562
563 let properties = self.inner.db.read_properties().await?.context("Missing config")?;
564 if invoice.network() != properties.network {
565 bail!("Invoice is for wrong network: {}", invoice.network());
566 }
567
568 let lightning_send = self.inner.db.get_lightning_send(payment_hash).await?;
569 if lightning_send.is_some() {
570 bail!("Invoice has already been paid");
571 }
572
573 invoice.check_signature()?;
574
575 let payment_amount = invoice.get_payment_amount(user_amount)?;
576 if payment_amount == Amount::ZERO {
577 bail!("Cannot pay invoice for 0 sats (0 sat invoices are not any-amount invoices)");
578 }
579
580 let (change_keypair, _) = self.derive_store_next_keypair().await?;
581
582 let (inputs, fee) = self.select_vtxos_to_cover_with_fee(
583 payment_amount, |a, v| ark_info.fees.lightning_send.calculate(a, v)
584 .context("fee overflowed"),
585 ).await.context("Could not find enough suitable VTXOs to cover lightning payment")?;
586 let total_amount = payment_amount + fee;
587
588 let input_ids = inputs.iter().map(|v| v.id()).collect::<Vec<_>>();
592 let full_inputs = self.inner.db.get_full_vtxos(&input_ids).await
593 .context("failed to hydrate lightning-send input vtxos")?;
594
595 self.register_vtxo_transactions_with_server(&full_inputs).await
598 .context("failed to register lightning-send input vtxo transactions with server")?;
599
600 let mut secs = Vec::with_capacity(inputs.len());
601 let mut pubs = Vec::with_capacity(inputs.len());
602 let mut input_keypairs = Vec::with_capacity(inputs.len());
603 for input in inputs.iter() {
604 let keypair = self.get_vtxo_key(input).await?;
605 let (s, p) = musig::nonce_pair(&keypair);
606 secs.push(s);
607 pubs.push(p);
608 input_keypairs.push(keypair);
609 }
610
611 let expiry = tip + ark_info.htlc_send_expiry_delta as BlockHeight;
612 let policy = VtxoPolicy::new_server_htlc_send(
613 change_keypair.public_key(), invoice.payment_hash(), expiry,
614 );
615
616 let input_amount = inputs.iter().map(|v| v.amount()).sum::<Amount>();
617 let pay_dest = ArkoorDestination { total_amount, policy };
618 let outputs = if input_amount == total_amount {
619 vec![pay_dest]
620 } else {
621 let change_dest = ArkoorDestination {
622 total_amount: input_amount - total_amount,
623 policy: VtxoPolicy::new_pubkey(change_keypair.public_key()),
624 };
625 vec![pay_dest, change_dest]
626 };
627 let builder = ArkoorPackageBuilder::new_with_checkpoints(
628 full_inputs,
629 outputs,
630 )
631 .context("Failed to construct arkoor package")?
632 .generate_user_nonces(&input_keypairs)
633 .context("invalid nb of keypairs")?;
634
635 let package_cosign_request = protos::ArkoorPackageCosignRequest::from(
636 builder.cosign_request(),
637 );
638 let cosign_request = protos::LightningPayHtlcCosignRequest {
639 parts: package_cosign_request.parts,
640 };
641
642 let response = srv.client.request_lightning_pay_htlc_cosign(cosign_request).await
643 .context("htlc request failed")?.into_inner();
644
645 let cosign_responses = ArkoorPackageCosignResponse::try_from(response)
646 .context("Failed to parse cosign response from server")?;
647
648 let vtxos = builder
649 .user_cosign(&input_keypairs, cosign_responses)
650 .context("Failed to cosign vtxos")?
651 .build_signed_vtxos();
652
653 self.register_vtxo_transactions_with_server(&vtxos).await?;
656
657 let (htlc_vtxos, change_vtxos) = vtxos.into_iter()
658 .partition::<Vec<_>, _>(|v| matches!(v.policy(), VtxoPolicy::ServerHtlcSend(_)));
659
660 let mut effective_balance = Amount::ZERO;
662 for vtxo in &htlc_vtxos {
663 self.validate_vtxo(vtxo).await?;
664 effective_balance += vtxo.amount();
665 }
666
667 let movement_id = self.inner.movements.new_movement_with_update(
668 Subsystem::LIGHTNING_SEND,
669 LightningSendMovement::Send.to_string(),
670 MovementUpdate::new()
671 .intended_balance(-payment_amount.to_signed()?)
672 .effective_balance(-effective_balance.to_signed()?)
673 .fee(fee)
674 .consumed_vtxos(&inputs)
675 .sent_to([MovementDestination::new(original_payment_method, payment_amount)])
676 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
677 ).await?;
678
679 let holder = VtxoLockHolder::Movement { id: movement_id };
680 self.store_locked_vtxos(&htlc_vtxos, Some(holder)).await?;
681 self.mark_vtxos_as_spent(&input_ids).await?;
682
683 for change in &change_vtxos {
685 let last_input = inputs.last().context("no inputs provided")?;
686 let tx = self.inner.chain.get_tx(&last_input.chain_anchor().txid).await?;
687 let tx = tx.with_context(|| {
688 format!("input vtxo chain anchor not found for lightning change vtxo: {}", last_input.chain_anchor().txid)
689 })?;
690 change.validate(&tx).context("invalid lightning change vtxo")?;
691 self.store_spendable_vtxos([change]).await?;
692 }
693
694 self.inner.movements.update_movement(
695 movement_id,
696 MovementUpdate::new()
697 .produced_vtxos(change_vtxos)
698 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
699 ).await?;
700
701 let lightning_send = self.inner.db.store_new_pending_lightning_send(
702 &invoice,
703 payment_amount,
704 fee,
705 &htlc_vtxos.iter().map(|v| v.id()).collect::<Vec<_>>(),
706 movement_id,
707 ).await?;
708
709 let mailbox_id = self.mailbox_identifier();
710 let req = protos::InitiateLightningPaymentRequest {
711 invoice: invoice.to_string(),
712 htlc_vtxo_ids: htlc_vtxos.iter().map(|v| v.id().to_bytes().to_vec()).collect(),
713 payment_amount_sat: payment_amount.to_sat(),
714 mailbox_id: Some(mailbox_id.serialize()),
715 };
716
717 srv.client.initiate_lightning_payment(req).await?;
718
719 Ok(lightning_send)
720 }
721}