@@ -15,7 +15,7 @@ use bdk_chain::bdk_core::spk_client::{
1515} ;
1616use bdk_electrum:: BdkElectrumClient ;
1717use bdk_wallet:: { KeychainKind as BdkKeyChainKind , Update as BdkUpdate } ;
18- use bitcoin:: { FeeRate , Network , Script , ScriptBuf , Transaction , Txid } ;
18+ use bitcoin:: { FeeRate , Network , OutPoint , Script , ScriptBuf , Transaction , Txid } ;
1919use electrum_client:: {
2020 Batch , Client as ElectrumClient , ConfigBuilder as ElectrumConfigBuilder , ElectrumApi ,
2121} ;
@@ -288,6 +288,21 @@ impl ElectrumChainSource {
288288 electrum_client. broadcast ( tx) . await ;
289289 }
290290 }
291+
292+ pub ( crate ) async fn get_transaction ( & self , txid : & Txid ) -> Result < Option < Transaction > , Error > {
293+ let electrum_client: Arc < ElectrumRuntimeClient > =
294+ if let Some ( client) = self . electrum_runtime_status . read ( ) . unwrap ( ) . client ( ) . as_ref ( ) {
295+ Arc :: clone ( client)
296+ } else {
297+ debug_assert ! (
298+ false ,
299+ "We should have started the chain source before getting transactions"
300+ ) ;
301+ return Err ( Error :: TxSyncFailed ) ;
302+ } ;
303+
304+ electrum_client. get_transaction ( txid) . await
305+ }
291306}
292307
293308impl Filter for ElectrumChainSource {
@@ -652,6 +667,125 @@ impl ElectrumRuntimeClient {
652667
653668 Ok ( new_fee_rate_cache)
654669 }
670+
671+ async fn get_transaction ( & self , txid : & Txid ) -> Result < Option < Transaction > , Error > {
672+ let electrum_client = Arc :: clone ( & self . electrum_client ) ;
673+ let txid_copy = * txid;
674+
675+ let spawn_fut =
676+ self . runtime . spawn_blocking ( move || electrum_client. transaction_get ( & txid_copy) ) ;
677+ let timeout_fut = tokio:: time:: timeout (
678+ Duration :: from_secs (
679+ self . sync_config . timeouts_config . lightning_wallet_sync_timeout_secs ,
680+ ) ,
681+ spawn_fut,
682+ ) ;
683+
684+ match timeout_fut. await {
685+ Ok ( res) => match res {
686+ Ok ( inner_res) => match inner_res {
687+ Ok ( tx) => Ok ( Some ( tx) ) ,
688+ Err ( e) => {
689+ // Check if it's a "not found" error
690+ let error_str = e. to_string ( ) ;
691+ if error_str. contains ( "No such mempool or blockchain transaction" )
692+ || error_str. contains ( "not found" )
693+ {
694+ Ok ( None )
695+ } else {
696+ log_error ! ( self . logger, "Failed to get transaction {}: {}" , txid, e) ;
697+ Err ( Error :: TxSyncFailed )
698+ }
699+ } ,
700+ } ,
701+ Err ( e) => {
702+ log_error ! ( self . logger, "Failed to get transaction {}: {}" , txid, e) ;
703+ Err ( Error :: TxSyncFailed )
704+ } ,
705+ } ,
706+ Err ( e) => {
707+ log_error ! ( self . logger, "Failed to get transaction {} due to timeout: {}" , txid, e) ;
708+ Err ( Error :: TxSyncTimeout )
709+ } ,
710+ }
711+ }
712+
713+ async fn is_outpoint_spent ( & self , outpoint : & OutPoint ) -> Result < bool , Error > {
714+ // First get the transaction to find the scriptPubKey of the output
715+ let tx = match self . get_transaction ( & outpoint. txid ) . await ? {
716+ Some ( tx) => tx,
717+ None => {
718+ // Transaction doesn't exist, so outpoint can't be spent
719+ // (or never existed)
720+ return Ok ( false ) ;
721+ } ,
722+ } ;
723+
724+ // Check if the output index is valid
725+ let vout = outpoint. vout as usize ;
726+ if vout >= tx. output . len ( ) {
727+ // Invalid output index
728+ return Ok ( false ) ;
729+ }
730+
731+ let script_pubkey = & tx. output [ vout] . script_pubkey ;
732+ let electrum_client = Arc :: clone ( & self . electrum_client ) ;
733+ let script_pubkey_clone = script_pubkey. clone ( ) ;
734+ let outpoint_txid = outpoint. txid ;
735+ let outpoint_vout = outpoint. vout ;
736+
737+ let spawn_fut = self
738+ . runtime
739+ . spawn_blocking ( move || electrum_client. script_list_unspent ( & script_pubkey_clone) ) ;
740+ let timeout_fut = tokio:: time:: timeout (
741+ Duration :: from_secs (
742+ self . sync_config . timeouts_config . lightning_wallet_sync_timeout_secs ,
743+ ) ,
744+ spawn_fut,
745+ ) ;
746+
747+ match timeout_fut. await {
748+ Ok ( res) => match res {
749+ Ok ( inner_res) => match inner_res {
750+ Ok ( unspent_list) => {
751+ // Check if our outpoint is in the unspent list
752+ let is_unspent = unspent_list. iter ( ) . any ( |u| {
753+ u. tx_hash == outpoint_txid && u. tx_pos == outpoint_vout as usize
754+ } ) ;
755+ // Return true if spent (not in unspent list)
756+ Ok ( !is_unspent)
757+ } ,
758+ Err ( e) => {
759+ log_error ! (
760+ self . logger,
761+ "Failed to check if outpoint {} is spent: {}" ,
762+ outpoint,
763+ e
764+ ) ;
765+ Err ( Error :: TxSyncFailed )
766+ } ,
767+ } ,
768+ Err ( e) => {
769+ log_error ! (
770+ self . logger,
771+ "Failed to check if outpoint {} is spent: {}" ,
772+ outpoint,
773+ e
774+ ) ;
775+ Err ( Error :: TxSyncFailed )
776+ } ,
777+ } ,
778+ Err ( e) => {
779+ log_error ! (
780+ self . logger,
781+ "Failed to check if outpoint {} is spent due to timeout: {}" ,
782+ outpoint,
783+ e
784+ ) ;
785+ Err ( Error :: TxSyncTimeout )
786+ } ,
787+ }
788+ }
655789}
656790
657791impl Filter for ElectrumRuntimeClient {
0 commit comments