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(),
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)
233 -> anyhow::Result<Option<LightningSend>>
234 {
235 trace!("Checking lightning payment status for payment hash: {}", payment_hash);
236
237 {
241 let mut inflight = self.inflight_lightning_payments.lock().await;
242 if !inflight.insert(payment_hash) {
243 bail!("Payment operation already in progress for this invoice");
244 }
245 }
246
247 let result = self.check_lightning_payment_inner(payment_hash, wait).await;
248
249 {
251 let mut inflight = self.inflight_lightning_payments.lock().await;
252 inflight.remove(&payment_hash);
253 }
254
255 result
256 }
257
258 async fn check_lightning_payment_inner(&self, payment_hash: PaymentHash, wait: bool)
260 -> anyhow::Result<Option<LightningSend>>
261 {
262 let (mut srv, _) = self.require_server().await?;
263
264 let payment = self.db.get_lightning_send(payment_hash).await?
265 .context("no lightning send found for payment hash")?;
266
267 if payment.preimage.is_some() {
269 trace!("Payment already completed with preimage");
270 return Ok(Some(payment));
271 }
272
273 if payment.htlc_vtxos.is_empty() {
274 bail!("No HTLC VTXOs found for payment");
275 }
276
277 let policy = payment.htlc_vtxos.iter()
278 .all_same(|v| v.vtxo.policy())
279 .ok_or(anyhow::anyhow!("All lightning htlc should have the same policy"))?;
280
281 let policy = policy.as_server_htlc_send().context("VTXO is not an HTLC send")?;
282 if policy.payment_hash != payment_hash {
283 bail!("Payment hash mismatch");
284 }
285
286 let req = protos::CheckLightningPaymentRequest {
287 hash: payment_hash.to_vec(),
288 wait,
289 };
290 let response = srv.client.check_lightning_payment(req).await
293 .map(|r| r.into_inner().payment_status);
294
295 let tip = self.chain.tip().await?;
296 let min_vtxo_expiry = payment.htlc_vtxos.iter()
297 .map(|v| v.vtxo.expiry_height())
298 .min().context("no HTLC VTXOs for expiry check")?;
299 let expired = tip > policy.htlc_expiry
300 || tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold);
301
302 let should_revoke = match response {
303 Ok(Some(PaymentStatus::Success(status))) => {
304 let preimage_opt = self.process_lightning_send_server_preimage(
305 Some(status.preimage), &payment,
306 ).await?;
307
308 if preimage_opt.is_some() {
309 let updated_payment = self.db.get_lightning_send(payment_hash).await?
311 .context("payment disappeared from database")?;
312 return Ok(Some(updated_payment));
313 } else {
314 trace!("Server said payment is complete, but has no valid preimage: {:?}", preimage_opt);
315 expired
316 }
317 },
318 Ok(Some(PaymentStatus::Failed(_))) => {
319 info!("Payment failed, revoking VTXO");
320 true
321 },
322 Ok(Some(PaymentStatus::Pending(_))) => {
323 trace!("Payment is still pending");
324 expired
325 },
326 Ok(None) | Err(_) => expired,
328 };
329
330 if should_revoke {
331 debug!("Revoking HTLC VTXOs for payment {} (tip: {}, expiry: {})",
332 payment_hash, tip, policy.htlc_expiry);
333
334 if let Err(e) = self.process_lightning_revocation(&payment).await {
335 warn!("Failed to revoke VTXO: {}", e);
336
337 if tip > min_vtxo_expiry.saturating_sub(self.config().vtxo_refresh_expiry_threshold) {
341 warn!("HTLC VTXOs for payment {} are near VTXO expiry, marking to exit", payment_hash);
342
343 let vtxos = payment.htlc_vtxos
344 .iter()
345 .map(|v| v.vtxo.clone())
346 .collect::<Vec<_>>();
347 self.exit.write().await.start_exit_for_vtxos(&vtxos).await?;
348
349 let exited = vtxos.iter().map(|v| v.amount()).sum::<Amount>();
350 let effective = -payment.amount.to_signed()? - payment.fee.to_signed()? + exited.to_signed()?;
351 if effective != SignedAmount::ZERO {
352 warn!("Movement {} should have fee of zero, but got {}: amount = {}, fee = {}, exited = {}",
353 payment.movement_id, effective, payment.amount, payment.fee, exited,
354 );
355 }
356 self.movements.finish_movement_with_update(
357 payment.movement_id,
358 MovementStatus::Failed,
359 MovementUpdate::new()
360 .effective_balance(effective)
361 .fee(effective.unsigned_abs())
362 .exited_vtxos(&vtxos)
363 ).await?;
364 self.db.finish_lightning_send(payment.invoice.payment_hash(), None).await?;
365 }
366
367 return Err(e)
368 }
369 }
370
371 Ok(self.db.get_lightning_send(payment_hash).await?)
373 }
374
375 pub async fn pay_lightning_invoice<T>(
382 &self,
383 invoice: T,
384 user_amount: Option<Amount>,
385 ) -> anyhow::Result<LightningSend>
386 where
387 T: TryInto<Invoice>,
388 T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
389 {
390 let invoice = invoice.try_into().context("failed to parse invoice")?;
391 let amount = invoice.get_final_amount(user_amount)?;
392 info!("Sending bolt11 payment of {} to invoice {}", amount, invoice);
393 self.make_lightning_payment(&invoice, invoice.clone().into(), user_amount).await
394 }
395
396 pub async fn pay_lightning_address(
398 &self,
399 addr: &LightningAddress,
400 amount: Amount,
401 comment: Option<impl AsRef<str>>,
402 ) -> anyhow::Result<LightningSend> {
403 let comment = comment.as_ref();
404 let invoice = lnaddr_invoice(addr, amount, comment).await
405 .context("lightning address error")?;
406 info!("Sending {} to lightning address {}", amount, addr);
407 let ret = self.make_lightning_payment(&invoice.into(), addr.clone().into(), None).await
408 .context("bolt11 payment error")?;
409 info!("Paid invoice {}", ret.invoice);
410 Ok(ret)
411 }
412
413 pub async fn pay_lightning_offer(
415 &self,
416 offer: Offer,
417 user_amount: Option<Amount>,
418 ) -> anyhow::Result<LightningSend> {
419 let (mut srv, _) = self.require_server().await?;
420
421 let offer_bytes = {
422 let mut bytes = Vec::new();
423 offer.write(&mut bytes).context("failed to serialize BOLT12 offer")?;
424 bytes
425 };
426
427 let req = protos::FetchBolt12InvoiceRequest {
428 offer: offer_bytes,
429 amount_sat: user_amount.map(|a| a.to_sat()),
430 };
431
432 if let Some(amt) = user_amount {
433 info!("Sending bolt12 payment of {} (user amount) to offer {}", amt, offer);
434 } else if let Some(amt) = offer.amount() {
435 info!("Sending bolt12 payment of {:?} (invoice amount) to offer {}", amt, offer);
436 } else {
437 warn!("Paying offer without amount nor user amount provided: {}", offer);
438 }
439
440 let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
441 let invoice = Bolt12Invoice::try_from(resp.invoice)
442 .map_err(|e| anyhow!("invalid invoice: {:?}", e))?;
443
444 invoice.validate_issuance(&offer)
445 .context("invalid BOLT12 invoice received from offer")?;
446
447 let ret = self.make_lightning_payment(&invoice.into(), offer.into(), None).await
448 .context("bolt12 payment error")?;
449 info!("Paid invoice: {:?}", ret.invoice);
450
451 Ok(ret)
452 }
453
454 pub async fn make_lightning_payment(
491 &self,
492 invoice: &Invoice,
493 original_payment_method: PaymentMethod,
494 user_amount: Option<Amount>,
495 ) -> anyhow::Result<LightningSend> {
496 if !original_payment_method.is_lightning() && !original_payment_method.is_custom() {
497 bail!("Invalid original payment method for lightning payment");
498 }
499
500 let payment_hash = invoice.payment_hash();
501
502 {
506 let mut inflight = self.inflight_lightning_payments.lock().await;
507 if !inflight.insert(payment_hash) {
508 bail!("Payment already in progress for this invoice");
509 }
510 }
511
512 let result = self.make_lightning_payment_inner(
514 invoice, original_payment_method, user_amount, payment_hash
515 ).await;
516
517 {
519 let mut inflight = self.inflight_lightning_payments.lock().await;
520 inflight.remove(&payment_hash);
521 }
522
523 result
524 }
525
526 async fn make_lightning_payment_inner(
528 &self,
529 invoice: &Invoice,
530 original_payment_method: PaymentMethod,
531 user_amount: Option<Amount>,
532 payment_hash: PaymentHash,
533 ) -> anyhow::Result<LightningSend> {
534 let (mut srv, ark_info) = self.require_server().await?;
535
536 let tip = self.chain.tip().await?;
537
538 let properties = self.db.read_properties().await?.context("Missing config")?;
539 if invoice.network() != properties.network {
540 bail!("Invoice is for wrong network: {}", invoice.network());
541 }
542
543 let lightning_send = self.db.get_lightning_send(payment_hash).await?;
544 if lightning_send.is_some() {
545 bail!("Invoice has already been paid");
546 }
547
548 invoice.check_signature()?;
549
550 let amount = invoice.get_final_amount(user_amount)?;
551 if amount == Amount::ZERO {
552 bail!("Cannot pay invoice for 0 sats (0 sat invoices are not any-amount invoices)");
553 }
554
555 let (change_keypair, _) = self.derive_store_next_keypair().await?;
556
557 let (inputs, fee) = self.select_vtxos_to_cover_with_fee(
558 amount, |a, v| ark_info.fees.lightning_send.calculate(a, v).context("fee overflowed"),
559 ).await.context("Could not find enough suitable VTXOs to cover lightning payment")?;
560 let total_amount = amount + fee;
561
562 let mut secs = Vec::with_capacity(inputs.len());
563 let mut pubs = Vec::with_capacity(inputs.len());
564 let mut input_keypairs = Vec::with_capacity(inputs.len());
565 let mut input_ids = Vec::with_capacity(inputs.len());
566 for input in inputs.iter() {
567 let keypair = self.get_vtxo_key(input).await?;
568 let (s, p) = musig::nonce_pair(&keypair);
569 secs.push(s);
570 pubs.push(p);
571 input_keypairs.push(keypair);
572 input_ids.push(input.id());
573 }
574
575 let expiry = tip + ark_info.htlc_send_expiry_delta as BlockHeight;
576 let policy = VtxoPolicy::new_server_htlc_send(
577 change_keypair.public_key(), invoice.payment_hash(), expiry,
578 );
579
580 let input_amount = inputs.iter().map(|v| v.amount()).sum::<Amount>();
581 let pay_dest = ArkoorDestination { total_amount, policy };
582 let outputs = if input_amount == total_amount {
583 vec![pay_dest]
584 } else {
585 let change_dest = ArkoorDestination {
586 total_amount: input_amount - total_amount,
587 policy: VtxoPolicy::new_pubkey(change_keypair.public_key()),
588 };
589 vec![pay_dest, change_dest]
590 };
591 let builder = ArkoorPackageBuilder::new_with_checkpoints(
592 inputs.iter().map(|v| &v.vtxo).cloned(),
593 outputs,
594 )
595 .context("Failed to construct arkoor package")?
596 .generate_user_nonces(&input_keypairs)
597 .context("invalid nb of keypairs")?;
598
599 let package_cosign_request = protos::ArkoorPackageCosignRequest::from(
600 builder.cosign_request(),
601 );
602 let cosign_request = protos::LightningPayHtlcCosignRequest {
603 parts: package_cosign_request.parts,
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}