11//! An implementation of `TrustedWalletInterface` using the Spark SDK.
2- use crate :: bitcoin:: consensus:: encode:: deserialize_hex;
32use crate :: bitcoin:: hex:: FromHex ;
43use crate :: bitcoin:: { Txid , io} ;
54use crate :: logging:: Logger ;
@@ -21,8 +20,8 @@ use bitcoin_payment_instructions::PaymentMethod;
2120use bitcoin_payment_instructions:: amount:: Amount ;
2221
2322use 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;
4039pub ( 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
467552impl 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+
564669impl From < SparkWalletError > for TrustedError {
565670 fn from ( e : SparkWalletError ) -> Self {
566671 match e {
0 commit comments