Skip to content

Commit 055bc95

Browse files
committed
Send PaymentSuccess/Fail for spark
1 parent eb6759a commit 055bc95

1 file changed

Lines changed: 141 additions & 36 deletions

File tree

orange-sdk/src/trusted_wallet/spark.rs

Lines changed: 141 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//! An implementation of `TrustedWalletInterface` using the Spark SDK.
2-
use crate::bitcoin::consensus::encode::deserialize_hex;
32
use crate::bitcoin::hex::FromHex;
43
use crate::bitcoin::{Txid, io};
54
use crate::logging::Logger;
@@ -21,8 +20,8 @@ use bitcoin_payment_instructions::PaymentMethod;
2120
use bitcoin_payment_instructions::amount::Amount;
2221

2322
use spark_wallet::{
24-
DefaultSigner, Order, PagingFilter, PayLightningInvoiceResult, Signer, SparkWallet,
25-
SparkWalletConfig, SparkWalletError, SspUserRequest, TransferStatus, WalletEvent,
23+
DefaultSigner, LightningSendStatus, Order, PagingFilter, PayLightningInvoiceResult, Signer,
24+
SparkWallet, SparkWalletConfig, SparkWalletError, SspUserRequest, TransferStatus, WalletEvent,
2625
WalletTransfer,
2726
};
2827

@@ -40,7 +39,9 @@ use uuid::Uuid;
4039
pub(crate) struct Spark {
4140
spark_wallet: Arc<SparkWallet<DefaultSigner>>,
4241
store: Arc<dyn KVStore + Send + Sync>,
42+
event_queue: Arc<EventQueue>,
4343
shutdown_sender: watch::Sender<()>,
44+
shutdown_receiver: watch::Receiver<()>,
4445
logger: Arc<Logger>,
4546
}
4647

@@ -190,7 +191,17 @@ impl TrustedWalletInterface for Spark {
190191
let uuid = Uuid::from_str(id).map_err(|_| {
191192
TrustedError::Other(format!("Failed to parse payment id: {id}"))
192193
})?;
193-
Ok(convert_from_transfer_id(uuid.into_bytes()))
194+
195+
let id = convert_from_transfer_id(uuid.into_bytes());
196+
197+
// Poll the payment status in the background
198+
self.poll_lightning_payment(
199+
pay.id,
200+
id,
201+
PaymentHash(invoice.payment_hash().to_byte_array()),
202+
);
203+
204+
Ok(id)
194205
} else {
195206
log_error!(self.logger, "Invalid payment id format: {}", pay.id);
196207
Err(TrustedError::Other(format!(
@@ -200,7 +211,18 @@ impl TrustedWalletInterface for Spark {
200211
}
201212
},
202213
PayLightningInvoiceResult::Transfer(transfer) => {
203-
Ok(convert_from_transfer_id(transfer.id.to_bytes()))
214+
let id = convert_from_transfer_id(transfer.id.to_bytes());
215+
// transfers just work, no need to poll
216+
self.event_queue
217+
.add_event(Event::PaymentSuccessful {
218+
payment_id: PaymentId::Trusted(id),
219+
payment_hash: PaymentHash(invoice.payment_hash().to_byte_array()),
220+
payment_preimage: PaymentPreimage([0; 32]), // we don't get the preimage here
221+
fee_paid_msat: Some(0),
222+
})
223+
.unwrap();
224+
225+
Ok(id)
204226
},
205227
}
206228
} else {
@@ -261,15 +283,17 @@ impl Spark {
261283
.map_err(|e| InitFailure::TrustedFailure(e.into()))?,
262284
);
263285

264-
let (shutdown_sender, mut shutdown_receiver) = watch::channel::<()>(());
286+
let (shutdown_sender, shutdown_receiver) = watch::channel::<()>(());
265287
let mut events = spark_wallet.subscribe_events();
266288
let l = Arc::clone(&logger);
267289
let w = Arc::clone(&spark_wallet);
268290
let s = Arc::clone(&store);
291+
let eq = Arc::clone(&event_queue);
292+
let mut shutdown_recv = shutdown_receiver.clone();
269293
tokio::spawn(async move {
270294
loop {
271295
tokio::select! {
272-
_ = shutdown_receiver.changed() => {
296+
_ = shutdown_recv.changed() => {
273297
log_info!(l, "Deposit tracking loop shutdown signal received");
274298
return;
275299
}
@@ -284,24 +308,23 @@ impl Spark {
284308
.into_iter()
285309
.find(|t| t.leaves.iter().any(|l| l.leaf.id == node_id))
286310
{
287-
event_queue
288-
.add_event(Event::OnchainPaymentReceived {
289-
payment_id: PaymentId::Trusted(
290-
convert_from_transfer_id(transfer.id.to_bytes()),
291-
),
292-
// todo this is kinda hacky, maybe we should make this optional
293-
txid: transfer
294-
.leaves
295-
.iter()
296-
.find(|t| t.leaf.id == node_id)
297-
.map(|t| t.leaf
298-
.node_tx
299-
.compute_txid())
300-
.unwrap_or(Txid::all_zeros()),
301-
amount_sat: transfer.total_value_sat,
302-
status: ConfirmationStatus::Unconfirmed, // fixme dont have block height
303-
})
304-
.unwrap();
311+
eq.add_event(Event::OnchainPaymentReceived {
312+
payment_id: PaymentId::Trusted(
313+
convert_from_transfer_id(transfer.id.to_bytes()),
314+
),
315+
// todo this is kinda hacky, maybe we should make this optional
316+
txid: transfer
317+
.leaves
318+
.iter()
319+
.find(|t| t.leaf.id == node_id)
320+
.map(|t| t.leaf
321+
.node_tx
322+
.compute_txid())
323+
.unwrap_or(Txid::all_zeros()),
324+
amount_sat: transfer.total_value_sat,
325+
status: ConfirmationStatus::Unconfirmed, // fixme dont have block height
326+
})
327+
.unwrap();
305328
}
306329
}
307330
},
@@ -343,15 +366,14 @@ impl Spark {
343366
},
344367
Some(SspUserRequest::LightningReceiveRequest(req)) => {
345368
if let Ok(hash) = FromHex::from_hex(&req.invoice.payment_hash) {
346-
event_queue
347-
.add_event(Event::PaymentReceived {
348-
payment_id: PaymentId::Trusted(convert_from_transfer_id(transfer.id.to_bytes())),
349-
payment_hash: PaymentHash(hash),
350-
amount_msat: transfer.total_value_sat * 1_000, // convert to msats
351-
custom_records: vec![],
352-
lsp_fee_msats: None,
353-
})
354-
.unwrap();
369+
eq.add_event(Event::PaymentReceived {
370+
payment_id: PaymentId::Trusted(convert_from_transfer_id(transfer.id.to_bytes())),
371+
payment_hash: PaymentHash(hash),
372+
amount_msat: transfer.total_value_sat * 1_000, // convert to msats
373+
custom_records: vec![],
374+
lsp_fee_msats: None,
375+
})
376+
.unwrap();
355377
}
356378
},
357379
Some(req) => {
@@ -370,7 +392,7 @@ impl Spark {
370392
}
371393
});
372394

373-
Ok(Spark { spark_wallet, store, shutdown_sender, logger })
395+
Ok(Spark { spark_wallet, store, event_queue, shutdown_sender, shutdown_receiver, logger })
374396
}
375397

376398
/// Synchronizes payments from transfers to persistent storage
@@ -462,6 +484,69 @@ impl Spark {
462484

463485
Ok(())
464486
}
487+
488+
/// Pools the lightning payment until it is in completed state.
489+
fn poll_lightning_payment(
490+
&self, spark_id: String, payment_id: [u8; 32], payment_hash: PaymentHash,
491+
) {
492+
const MAX_POLL_ATTEMPTS: u64 = 10;
493+
log_info!(self.logger, "Polling lightning send payment {spark_id}");
494+
495+
let mut shutdown = self.shutdown_receiver.clone();
496+
let spark_wallet = Arc::clone(&self.spark_wallet);
497+
let event_queue = Arc::clone(&self.event_queue);
498+
let logger = Arc::clone(&self.logger);
499+
tokio::spawn(async move {
500+
for i in 0..MAX_POLL_ATTEMPTS {
501+
log_info!(logger, "Polling lightning send payment {spark_id} attempt {i}",);
502+
tokio::select! {
503+
_ = shutdown.changed() => {
504+
log_info!(logger, "Shutdown signal received");
505+
return;
506+
},
507+
p = spark_wallet.fetch_lightning_send_payment(&spark_id) => {
508+
if let Ok(Some(p)) = p {
509+
let status: TxStatus = p.status.into();
510+
match status {
511+
TxStatus::Pending => {} // do nothing / wait
512+
TxStatus::Completed => {
513+
// wait for preimage
514+
if p.payment_preimage.is_some() {
515+
log_info!(logger, "Polling payment preimage found");
516+
let preimage: [u8; 32] = FromHex::from_hex(&p.payment_preimage.unwrap()).unwrap();
517+
event_queue
518+
.add_event(Event::PaymentSuccessful {
519+
payment_id: PaymentId::Trusted(payment_id),
520+
payment_hash,
521+
payment_preimage: PaymentPreimage(preimage),
522+
fee_paid_msat: Some(p.fee_sat * 1_000), // convert to msats
523+
})
524+
.unwrap();
525+
return;
526+
}
527+
}
528+
TxStatus::Failed => {
529+
log_info!(logger, "Polling payment failed");
530+
event_queue
531+
.add_event(Event::PaymentFailed {
532+
payment_id: PaymentId::Trusted(payment_id),
533+
payment_hash: Some(payment_hash),
534+
reason: None,
535+
})
536+
.unwrap();
537+
return;
538+
}
539+
}
540+
}
541+
let sleep_time = if i < 5 { Duration::from_secs(1) } else { Duration::from_secs(i) };
542+
tokio::time::sleep(sleep_time).await;
543+
}
544+
}
545+
}
546+
// todo what if we never get a final state?
547+
log_info!(logger, "Polling payment timed out");
548+
});
549+
}
465550
}
466551

467552
impl TryFrom<&WalletTransfer> for StoreTransaction {
@@ -523,7 +608,7 @@ impl TryFrom<&SspUserRequest> for PaymentType {
523608
.lightning_send_payment_preimage
524609
.as_deref()
525610
.map(|t| {
526-
deserialize_hex(t).map_err(|e| {
611+
FromHex::from_hex(t).map_err(|e| {
527612
SparkWalletError::Generic(format!(
528613
"Invalid LightningSendRequest preimage: {e}"
529614
))
@@ -561,6 +646,26 @@ impl From<TransferStatus> for TxStatus {
561646
}
562647
}
563648

649+
impl From<LightningSendStatus> for TxStatus {
650+
fn from(o: LightningSendStatus) -> TxStatus {
651+
match o {
652+
LightningSendStatus::LightningPaymentSucceeded => TxStatus::Completed,
653+
LightningSendStatus::TransferFailed
654+
| LightningSendStatus::LightningPaymentFailed
655+
| LightningSendStatus::UserSwapReturnFailed
656+
| LightningSendStatus::PreimageProvidingFailed => TxStatus::Failed,
657+
LightningSendStatus::Unknown
658+
| LightningSendStatus::UserSwapReturned
659+
| LightningSendStatus::PendingUserSwapReturn
660+
| LightningSendStatus::TransferCompleted
661+
| LightningSendStatus::Created
662+
| LightningSendStatus::RequestValidated
663+
| LightningSendStatus::LightningPaymentInitiated
664+
| LightningSendStatus::PreimageProvided => TxStatus::Pending,
665+
}
666+
}
667+
}
668+
564669
impl From<SparkWalletError> for TrustedError {
565670
fn from(e: SparkWalletError) -> Self {
566671
match e {

0 commit comments

Comments
 (0)