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;
18use crate::vtxo::VtxoStateKind;
19
20impl Wallet {
21 pub async fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
23 let mut result = Vec::new();
24 for cp in self.inner.db.get_all_wallet_action_checkpoints().await? {
25 if let Some(recv) = cp.into_lightning_receive() {
26 result.push(recv);
27 }
28 }
29 Ok(result)
30 }
31
32 pub async fn claimable_lightning_receive_balance(&self) -> anyhow::Result<Amount> {
40 let mut total = Amount::ZERO;
41 for recv in self.pending_lightning_receives().await? {
42 let vtxo_ids = match &recv.progress {
43 Progress::AwaitingPayment => continue,
44 Progress::HtlcsReady(htlcs) => &htlcs.vtxo_ids,
45 Progress::PreimageRevealed(htlcs) => &htlcs.vtxo_ids,
46 Progress::Delivering(_) => continue,
48 };
49 for id in vtxo_ids {
50 let vtxo = self.get_vtxo_by_id(*id).await?;
51 if VtxoStateKind::UNSPENT_STATES.contains(&vtxo.state.kind()) {
52 total += vtxo.vtxo.amount();
53 }
54 }
55 }
56 Ok(total)
57 }
58
59 pub async fn sync_pending_lightning_receives(&self) -> anyhow::Result<()> {
63 let pending = self.pending_lightning_receives().await?;
64 if pending.is_empty() {
65 return Ok(());
66 }
67 info!("Syncing {} pending lightning receives", pending.len());
68 for recv in pending {
69 let id = recv.id();
70 if let Err(e) = self.drive_action(recv, DriveMode::UntilParkOrDone).await {
71 warn!("Failed to sync lightning receive {}: {:#}", id, e);
72 }
73 }
74 Ok(())
75 }
76
77 pub async fn lightning_receive_checkpoint(&self, hash: PaymentHash)
79 -> anyhow::Result<Option<LightningReceive>>
80 {
81 Ok(self.inner.db.get_wallet_action_checkpoint(&ln_recv_action_id(hash)).await?
82 .and_then(|cp| cp.into_lightning_receive()))
83 }
84
85 pub(crate) async fn lightning_receive_preimage(&self, hash: PaymentHash)
90 -> anyhow::Result<Option<Preimage>>
91 {
92 if let Some(settled) = self.inner.db.get_settled_lightning_receive(hash).await? {
93 return Ok(Some(settled.preimage));
94 }
95 if let Some(cp) = self.lightning_receive_checkpoint(hash).await? {
96 return Ok(Some(cp.payment_preimage));
97 }
98 Ok(None)
99 }
100
101 pub async fn lightning_receive_state(&self, hash: PaymentHash)
103 -> anyhow::Result<LightningReceiveState>
104 {
105 if let Some(settled) = self.inner.db.get_settled_lightning_receive(hash).await? {
106 return Ok(LightningReceiveState::Settled(settled));
107 }
108 if let Some(cp) = self.lightning_receive_checkpoint(hash).await? {
109 return Ok(LightningReceiveState::InProgress(cp));
110 }
111 bail!("no pending lightning receive found for this payment hash");
112 }
113
114 pub async fn bolt11_invoice(
126 &self,
127 amount: Amount,
128 description: Option<String>,
129 token: Option<String>,
130 ) -> anyhow::Result<Bolt11Invoice> {
131 let start = start_lightning_receive(self, amount, description, token, None).await?;
132 self.inner.db.upsert_wallet_action_checkpoint(&start.id(), &start.clone().into()).await?;
133 Ok(start.invoice.clone())
134 }
135
136 pub async fn bolt11_invoice_for_address(
159 &self,
160 amount: Amount,
161 claim_destination: ark::Address,
162 description: Option<String>,
163 token: Option<String>,
164 ) -> anyhow::Result<Bolt11Invoice> {
165 let start = start_lightning_receive(
166 self, amount, description, token, Some(claim_destination),
167 ).await?;
168 self.inner.db.upsert_wallet_action_checkpoint(&start.id(), &start.clone().into()).await?;
169 Ok(start.invoice.clone())
170 }
171
172 pub async fn cancel_lightning_receive(&self, hash: PaymentHash) -> anyhow::Result<()> {
180 let key = ln_recv_action_id(hash);
181 let _guard = self.inner.lock_manager.try_lock(&key).await
183 .context("receive operation already in progress for this payment")?;
184
185 let recv = self.lightning_receive_checkpoint(hash).await?
186 .context("no pending lightning receive found for this payment hash")?;
187
188 match recv.progress {
189 Progress::AwaitingPayment => {
190 if let Ok((mut srv, _)) = self.require_server().await {
193 if let Err(e) = srv.client.cancel_lightning_receive(
194 protos::CancelLightningReceiveRequest { payment_hash: hash.to_vec() },
195 ).await {
196 warn!("server did not cancel lightning receive {}: {}", hash, e);
197 }
198 }
199 self.stop_wallet_action(&key).await?;
200 Ok(())
201 },
202 Progress::HtlcsReady(_) => {
203 bail!("cannot cancel: HTLCs already granted; the receive will complete \
204 or be abandoned near expiry");
205 },
206 Progress::PreimageRevealed(_) | Progress::Delivering(_) => {
207 bail!("cannot cancel: preimage has already been revealed");
208 },
209 }
210 }
211
212 pub async fn attempt_lightning_receive_exit(
224 &self,
225 payment: impl Into<PaymentHash>,
226 ) -> anyhow::Result<()> {
227 let hash = payment.into();
228 let key = ln_recv_action_id(hash);
229 let _guard = self.inner.lock_manager.try_lock(&key).await
231 .context("receive operation already in progress for this payment")?;
232
233 let recv = self.lightning_receive_checkpoint(hash).await?
234 .context("no pending lightning receive found for this payment hash")?;
235
236 let htlcs = match &recv.progress {
237 Progress::PreimageRevealed(htlcs) => htlcs,
238 _ => bail!("preimage must be revealed before attempting to exit"),
239 };
240
241 self.exit_lightning_receive_htlcs(&recv, htlcs).await?;
242
243 self.stop_wallet_action(&key).await?;
246 Ok(())
247 }
248
249 pub async fn exit_lightning_receive_htlcs(
255 &self,
256 recv: &LightningReceive,
257 htlcs: &Htlcs,
258 ) -> anyhow::Result<()> {
259 warn!("Exiting HTLC VTXOs for lightning receive {}", recv.payment_hash);
260
261 let mut vtxos = Vec::with_capacity(htlcs.vtxo_ids.len());
262 for id in htlcs.vtxo_ids.iter() {
263 vtxos.push(self.get_vtxo_by_id(*id).await?.vtxo);
264 }
265 let vtxo_refs = vtxos.iter().collect::<Vec<_>>();
266 self.inner.exit.start_exit_for_vtxos(&vtxo_refs).await?;
267
268 self.inner.movements.finish_movement_with_update(
269 htlcs.movement_id,
270 MovementStatus::Failed,
271 MovementUpdate::new().exited_vtxos(vtxo_refs),
272 ).await?;
273
274 let amount = recv.invoice.get_payment_amount(None).unwrap_or(Amount::ZERO);
278 self.inner.db.record_settled_lightning_receive(
279 recv.payment_hash, recv.payment_preimage, &recv.invoice, amount,
280 ).await?;
281
282 Ok(())
283 }
284
285 pub async fn try_claim_lightning_receive(&self, hash: PaymentHash, wait: bool)
289 -> anyhow::Result<LightningReceiveState>
290 {
291 if let Some(recv) = self.lightning_receive_checkpoint(hash).await? {
292 let mode = if wait { DriveMode::UntilDone } else { DriveMode::UntilParkOrDone };
293 self.drive_action(recv, mode).await?;
294 }
295 self.lightning_receive_state(hash).await
296 }
297
298 pub async fn try_claim_all_lightning_receives(&self, wait: bool)
302 -> anyhow::Result<Vec<LightningReceiveState>>
303 {
304 let pending = self.pending_lightning_receives().await?;
305 let total = pending.len();
306
307 if total == 0 {
308 return Ok(vec![]);
309 }
310
311 let results: Vec<_> = tokio_stream::iter(pending)
312 .map(|rcv| async move {
313 self.try_claim_lightning_receive(rcv.invoice.into(), wait).await
314 })
315 .buffer_unordered(3)
316 .collect()
317 .await;
318
319 let mut claimed = vec![];
320 let mut failed = 0;
321
322 for result in results {
323 match result {
324 Ok(receive) => claimed.push(receive),
325 Err(e) => {
326 error!("Error claiming lightning receive: {:#}", e);
327 failed += 1;
328 }
329 }
330 }
331
332 if failed > 0 {
333 info!(
334 "Lightning receive claims: {} succeeded, {} failed out of {} pending",
335 claimed.len(), failed, total
336 );
337 }
338
339 if claimed.is_empty() {
340 anyhow::bail!("All {} lightning receive claim(s) failed", failed);
341 }
342
343 Ok(claimed)
344 }
345}