bark/lightning/
receive.rs1use anyhow::Context;
2use bitcoin::Amount;
3use futures::StreamExt;
4use lightning_invoice::Bolt11Invoice;
5use log::{error, info, warn};
6use server_rpc::protos;
7
8use ark::lightning::{Bolt11InvoiceExt, Preimage, PaymentHash};
9
10use crate::Wallet;
11use crate::actions::DriveMode;
12use crate::actions::lightning::receive::{
13 Htlcs, LightningReceive, LightningReceiveState, Progress,
14 ln_recv_action_id, start_lightning_receive
15};
16use crate::movement::MovementStatus;
17use crate::movement::update::MovementUpdate;
18
19impl Wallet {
20 pub async fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
22 let mut result = Vec::new();
23 for cp in self.inner.db.get_all_wallet_action_checkpoints().await? {
24 if let Some(recv) = cp.into_lightning_receive() {
25 result.push(recv);
26 }
27 }
28 Ok(result)
29 }
30
31 pub async fn claimable_lightning_receive_balance(&self) -> anyhow::Result<Amount> {
35 let mut total = Amount::ZERO;
36 for recv in self.pending_lightning_receives().await? {
37 let vtxo_ids = match &recv.progress {
38 Progress::AwaitingPayment => continue,
39 Progress::HtlcsReady(htlcs) => &htlcs.vtxo_ids,
40 Progress::PreimageRevealed(htlcs) => &htlcs.vtxo_ids,
41 Progress::Delivering(_) => continue,
43 };
44 for id in vtxo_ids {
45 total += self.get_vtxo_by_id(*id).await?.vtxo.amount();
46 }
47 }
48 Ok(total)
49 }
50
51 pub async fn sync_pending_lightning_receives(&self) -> anyhow::Result<()> {
55 let pending = self.pending_lightning_receives().await?;
56 if pending.is_empty() {
57 return Ok(());
58 }
59 info!("Syncing {} pending lightning receives", pending.len());
60 for recv in pending {
61 let id = recv.id();
62 if let Err(e) = self.drive_action(recv, DriveMode::UntilParkOrDone).await {
63 warn!("Failed to sync lightning receive {}: {:#}", id, e);
64 }
65 }
66 Ok(())
67 }
68
69 pub async fn lightning_receive_checkpoint(&self, hash: PaymentHash)
71 -> anyhow::Result<Option<LightningReceive>>
72 {
73 Ok(self.inner.db.get_wallet_action_checkpoint(&ln_recv_action_id(hash)).await?
74 .and_then(|cp| cp.into_lightning_receive()))
75 }
76
77 pub(crate) async fn lightning_receive_preimage(&self, hash: PaymentHash)
82 -> anyhow::Result<Option<Preimage>>
83 {
84 if let Some(settled) = self.inner.db.get_settled_lightning_receive(hash).await? {
85 return Ok(Some(settled.preimage));
86 }
87 if let Some(cp) = self.lightning_receive_checkpoint(hash).await? {
88 return Ok(Some(cp.payment_preimage));
89 }
90 Ok(None)
91 }
92
93 pub async fn lightning_receive_state(&self, hash: PaymentHash)
95 -> anyhow::Result<LightningReceiveState>
96 {
97 if let Some(settled) = self.inner.db.get_settled_lightning_receive(hash).await? {
98 return Ok(LightningReceiveState::Settled(settled));
99 }
100 if let Some(cp) = self.lightning_receive_checkpoint(hash).await? {
101 return Ok(LightningReceiveState::InProgress(cp));
102 }
103 bail!("no pending lightning receive found for this payment hash");
104 }
105
106 pub async fn bolt11_invoice(
118 &self,
119 amount: Amount,
120 description: Option<String>,
121 token: Option<String>,
122 ) -> anyhow::Result<Bolt11Invoice> {
123 let start = start_lightning_receive(self, amount, description, token, None).await?;
124 self.inner.db.upsert_wallet_action_checkpoint(&start.id(), &start.clone().into()).await?;
125 Ok(start.invoice.clone())
126 }
127
128 pub async fn bolt11_invoice_for_address(
151 &self,
152 amount: Amount,
153 claim_destination: ark::Address,
154 description: Option<String>,
155 token: Option<String>,
156 ) -> anyhow::Result<Bolt11Invoice> {
157 let start = start_lightning_receive(
158 self, amount, description, token, Some(claim_destination),
159 ).await?;
160 self.inner.db.upsert_wallet_action_checkpoint(&start.id(), &start.clone().into()).await?;
161 Ok(start.invoice.clone())
162 }
163
164 pub async fn cancel_lightning_receive(&self, hash: PaymentHash) -> anyhow::Result<()> {
172 let key = ln_recv_action_id(hash);
173 let _guard = self.inner.lock_manager.try_lock(&key).await
175 .context("receive operation already in progress for this payment")?;
176
177 let recv = self.lightning_receive_checkpoint(hash).await?
178 .context("no pending lightning receive found for this payment hash")?;
179
180 match recv.progress {
181 Progress::AwaitingPayment => {
182 if let Ok((mut srv, _)) = self.require_server().await {
185 if let Err(e) = srv.client.cancel_lightning_receive(
186 protos::CancelLightningReceiveRequest { payment_hash: hash.to_vec() },
187 ).await {
188 warn!("server did not cancel lightning receive {}: {}", hash, e);
189 }
190 }
191 self.stop_wallet_action(&key).await?;
192 Ok(())
193 },
194 Progress::HtlcsReady(_) => {
195 bail!("cannot cancel: HTLCs already granted; the receive will complete \
196 or be abandoned near expiry");
197 },
198 Progress::PreimageRevealed(_) | Progress::Delivering(_) => {
199 bail!("cannot cancel: preimage has already been revealed");
200 },
201 }
202 }
203
204 pub async fn attempt_lightning_receive_exit(
216 &self,
217 payment: impl Into<PaymentHash>,
218 ) -> anyhow::Result<()> {
219 let hash = payment.into();
220 let key = ln_recv_action_id(hash);
221 let _guard = self.inner.lock_manager.try_lock(&key).await
223 .context("receive operation already in progress for this payment")?;
224
225 let recv = self.lightning_receive_checkpoint(hash).await?
226 .context("no pending lightning receive found for this payment hash")?;
227
228 let htlcs = match &recv.progress {
229 Progress::PreimageRevealed(htlcs) => htlcs,
230 _ => bail!("preimage must be revealed before attempting to exit"),
231 };
232
233 self.exit_lightning_receive_htlcs(&recv, htlcs).await?;
234
235 self.stop_wallet_action(&key).await?;
238 Ok(())
239 }
240
241 pub async fn exit_lightning_receive_htlcs(
247 &self,
248 recv: &LightningReceive,
249 htlcs: &Htlcs,
250 ) -> anyhow::Result<()> {
251 warn!("Exiting HTLC VTXOs for lightning receive {}", recv.payment_hash);
252
253 let mut vtxos = Vec::with_capacity(htlcs.vtxo_ids.len());
254 for id in htlcs.vtxo_ids.iter() {
255 vtxos.push(self.get_vtxo_by_id(*id).await?.vtxo);
256 }
257 let vtxo_refs = vtxos.iter().collect::<Vec<_>>();
258 self.inner.exit.start_exit_for_vtxos(&vtxo_refs).await?;
259
260 self.inner.movements.finish_movement_with_update(
261 htlcs.movement_id,
262 MovementStatus::Failed,
263 MovementUpdate::new().exited_vtxos(vtxo_refs),
264 ).await?;
265
266 let amount = recv.invoice.get_payment_amount(None).unwrap_or(Amount::ZERO);
270 self.inner.db.record_settled_lightning_receive(
271 recv.payment_hash, recv.payment_preimage, &recv.invoice, amount,
272 ).await?;
273
274 Ok(())
275 }
276
277 pub async fn try_claim_lightning_receive(&self, hash: PaymentHash, wait: bool)
281 -> anyhow::Result<LightningReceiveState>
282 {
283 if let Some(recv) = self.lightning_receive_checkpoint(hash).await? {
284 let mode = if wait { DriveMode::UntilDone } else { DriveMode::UntilParkOrDone };
285 self.drive_action(recv, mode).await?;
286 }
287 self.lightning_receive_state(hash).await
288 }
289
290 pub async fn try_claim_all_lightning_receives(&self, wait: bool)
294 -> anyhow::Result<Vec<LightningReceiveState>>
295 {
296 let pending = self.pending_lightning_receives().await?;
297 let total = pending.len();
298
299 if total == 0 {
300 return Ok(vec![]);
301 }
302
303 let results: Vec<_> = tokio_stream::iter(pending)
304 .map(|rcv| async move {
305 self.try_claim_lightning_receive(rcv.invoice.into(), wait).await
306 })
307 .buffer_unordered(3)
308 .collect()
309 .await;
310
311 let mut claimed = vec![];
312 let mut failed = 0;
313
314 for result in results {
315 match result {
316 Ok(receive) => claimed.push(receive),
317 Err(e) => {
318 error!("Error claiming lightning receive: {:#}", e);
319 failed += 1;
320 }
321 }
322 }
323
324 if failed > 0 {
325 info!(
326 "Lightning receive claims: {} succeeded, {} failed out of {} pending",
327 claimed.len(), failed, total
328 );
329 }
330
331 if claimed.is_empty() {
332 anyhow::bail!("All {} lightning receive claim(s) failed", failed);
333 }
334
335 Ok(claimed)
336 }
337}