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_payment_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.to_string());
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 payment_amount = invoice.get_payment_amount(user_amount)?;
551 if payment_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 payment_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 = payment_amount + fee;
561
562 self.register_vtxo_transactions_with_server(&inputs).await
565 .context("failed to register lightning-send input vtxo transactions with server")?;
566
567 let mut secs = Vec::with_capacity(inputs.len());
568 let mut pubs = Vec::with_capacity(inputs.len());
569 let mut input_keypairs = Vec::with_capacity(inputs.len());
570 let mut input_ids = Vec::with_capacity(inputs.len());
571 for input in inputs.iter() {
572 let keypair = self.get_vtxo_key(input).await?;
573 let (s, p) = musig::nonce_pair(&keypair);
574 secs.push(s);
575 pubs.push(p);
576 input_keypairs.push(keypair);
577 input_ids.push(input.id());
578 }
579
580 let expiry = tip + ark_info.htlc_send_expiry_delta as BlockHeight;
581 let policy = VtxoPolicy::new_server_htlc_send(
582 change_keypair.public_key(), invoice.payment_hash(), expiry,
583 );
584
585 let input_amount = inputs.iter().map(|v| v.amount()).sum::<Amount>();
586 let pay_dest = ArkoorDestination { total_amount, policy };
587 let outputs = if input_amount == total_amount {
588 vec![pay_dest]
589 } else {
590 let change_dest = ArkoorDestination {
591 total_amount: input_amount - total_amount,
592 policy: VtxoPolicy::new_pubkey(change_keypair.public_key()),
593 };
594 vec![pay_dest, change_dest]
595 };
596 let builder = ArkoorPackageBuilder::new_with_checkpoints(
597 inputs.iter().map(|v| &v.vtxo).cloned(),
598 outputs,
599 )
600 .context("Failed to construct arkoor package")?
601 .generate_user_nonces(&input_keypairs)
602 .context("invalid nb of keypairs")?;
603
604 let package_cosign_request = protos::ArkoorPackageCosignRequest::from(
605 builder.cosign_request(),
606 );
607 let cosign_request = protos::LightningPayHtlcCosignRequest {
608 parts: package_cosign_request.parts,
609 };
610
611 let response = srv.client.request_lightning_pay_htlc_cosign(cosign_request).await
612 .context("htlc request failed")?.into_inner();
613
614 let cosign_responses = ArkoorPackageCosignResponse::try_from(response)
615 .context("Failed to parse cosign response from server")?;
616
617 let vtxos = builder
618 .user_cosign(&input_keypairs, cosign_responses)
619 .context("Failed to cosign vtxos")?
620 .build_signed_vtxos();
621
622 self.register_vtxo_transactions_with_server(&vtxos).await?;
625
626 let (htlc_vtxos, change_vtxos) = vtxos.into_iter()
627 .partition::<Vec<_>, _>(|v| matches!(v.policy(), VtxoPolicy::ServerHtlcSend(_)));
628
629 let mut effective_balance = Amount::ZERO;
631 for vtxo in &htlc_vtxos {
632 self.validate_vtxo(vtxo).await?;
633 effective_balance += vtxo.amount();
634 }
635
636 let movement_id = self.movements.new_movement_with_update(
637 Subsystem::LIGHTNING_SEND,
638 LightningSendMovement::Send.to_string(),
639 MovementUpdate::new()
640 .intended_balance(-payment_amount.to_signed()?)
641 .effective_balance(-effective_balance.to_signed()?)
642 .fee(fee)
643 .consumed_vtxos(&inputs)
644 .sent_to([MovementDestination::new(original_payment_method, payment_amount)])
645 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
646 ).await?;
647 self.store_locked_vtxos(&htlc_vtxos, Some(movement_id)).await?;
648 self.mark_vtxos_as_spent(&input_ids).await?;
649
650 for change in &change_vtxos {
652 let last_input = inputs.last().context("no inputs provided")?;
653 let tx = self.chain.get_tx(&last_input.chain_anchor().txid).await?;
654 let tx = tx.with_context(|| {
655 format!("input vtxo chain anchor not found for lightning change vtxo: {}", last_input.chain_anchor().txid)
656 })?;
657 change.validate(&tx).context("invalid lightning change vtxo")?;
658 self.store_spendable_vtxos([change]).await?;
659 }
660
661 self.movements.update_movement(
662 movement_id,
663 MovementUpdate::new()
664 .produced_vtxos(change_vtxos)
665 .metadata(LightningMovement::metadata(invoice.payment_hash(), &htlc_vtxos, None))
666 ).await?;
667
668 let lightning_send = self.db.store_new_pending_lightning_send(
669 &invoice,
670 payment_amount,
671 fee,
672 &htlc_vtxos.iter().map(|v| v.id()).collect::<Vec<_>>(),
673 movement_id,
674 ).await?;
675
676 let req = protos::InitiateLightningPaymentRequest {
677 invoice: invoice.to_string(),
678 htlc_vtxo_ids: htlc_vtxos.iter().map(|v| v.id().to_bytes().to_vec()).collect(),
679 payment_amount_sat: payment_amount.to_sat(),
680 };
681
682 srv.client.initiate_lightning_payment(req).await?;
683
684 Ok(lightning_send)
685 }
686}