@@ -1808,86 +1808,123 @@ impl Taker {
18081808 outgoing_swapcoins. len( )
18091809 ) ;
18101810
1811- for outgoing in & outgoing_swapcoins {
1812- let contract_txid = outgoing. contract_tx . compute_txid ( ) ;
1813- log:: info!(
1814- "Checking recovery options for outgoing contract: {}" ,
1815- contract_txid
1816- ) ;
1811+ let mut pending_recovery: Vec < _ > = outgoing_swapcoins. iter ( ) . collect ( ) ;
18171812
1818- // Check if contract has been spent
1819- let outpoint = bitcoin:: OutPoint {
1820- txid : contract_txid,
1821- vout : 0 ,
1822- } ;
1823- self . watch_service . watch_request ( outpoint) ;
1813+ // loop until all contracts are recovered
1814+ while !pending_recovery. is_empty ( ) {
1815+ let mut recovered_indices = Vec :: new ( ) ;
18241816
1825- let spending_result = if let Some ( event) = self . watch_service . wait_for_event ( ) {
1826- match event {
1827- WatcherEvent :: UtxoSpent { spending_tx, .. } => spending_tx,
1828- _ => None ,
1829- }
1830- } else {
1831- None
1832- } ;
1817+ for ( idx, outgoing) in pending_recovery. iter ( ) . enumerate ( ) {
1818+ let contract_txid = outgoing. contract_tx . compute_txid ( ) ;
1819+ log:: info!(
1820+ "Checking recovery options for outgoing contract: {}" ,
1821+ contract_txid
1822+ ) ;
18331823
1834- match spending_result {
1835- Some ( spending_tx) => {
1836- log:: info!( "Outgoing contract {} already spent" , contract_txid) ;
1824+ // Check if contract has been spent
1825+ let outpoint = bitcoin:: OutPoint {
1826+ txid : contract_txid,
1827+ vout : 0 ,
1828+ } ;
1829+ self . watch_service . watch_request ( outpoint) ;
18371830
1838- match crate :: protocol:: contract2:: detect_taproot_spending_path (
1839- & spending_tx,
1840- outpoint,
1841- ) ? {
1842- TaprootSpendingPath :: KeyPath => {
1843- log:: info!( "Contract spent cooperatively - no recovery needed" ) ;
1844- continue ;
1845- }
1846- TaprootSpendingPath :: Hashlock { .. } => {
1847- log:: info!( "Contract spent by receiver via hashlock - swap partially completed" ) ;
1848- continue ;
1849- }
1850- TaprootSpendingPath :: Timelock => {
1851- log:: warn!( "We already recovered this via timelock" ) ;
1852- continue ;
1853- }
1831+ let spending_result = if let Some ( event) = self . watch_service . wait_for_event ( ) {
1832+ match event {
1833+ WatcherEvent :: UtxoSpent { spending_tx, .. } => spending_tx,
1834+ _ => None ,
18541835 }
1855- }
1856- None => {
1857- // Contract not spent - check if timelock matured
1858- log:: info!(
1859- "Outgoing contract {} not spent - checking timelock maturity" ,
1860- contract_txid
1861- ) ;
1836+ } else {
1837+ None
1838+ } ;
18621839
1863- if let Some ( timelock) = outgoing. get_timelock ( ) {
1864- if crate :: protocol:: contract2:: is_timelock_mature (
1865- & self . wallet . rpc ,
1866- & contract_txid,
1867- timelock,
1868- ) ? {
1869- log:: info!( "Timelock matured, attempting recovery" ) ;
1840+ match spending_result {
1841+ Some ( spending_tx) => {
1842+ log:: info!( "Outgoing contract {} already spent" , contract_txid) ;
18701843
1871- match self
1844+ match crate :: protocol:: contract2:: detect_taproot_spending_path (
1845+ & spending_tx,
1846+ outpoint,
1847+ ) ? {
1848+ TaprootSpendingPath :: KeyPath => {
1849+ log:: info!( "Contract spent cooperatively - no recovery needed" ) ;
1850+ recovered_indices. push ( idx) ;
1851+ }
1852+ TaprootSpendingPath :: Hashlock { .. } => {
1853+ log:: info!( "Contract spent by receiver via hashlock - swap partially completed" ) ;
1854+ recovered_indices. push ( idx) ;
1855+ }
1856+ TaprootSpendingPath :: Timelock => {
1857+ log:: warn!( "We already recovered this via timelock" ) ;
1858+ recovered_indices. push ( idx) ;
1859+ }
1860+ }
1861+ }
1862+ None => {
1863+ // Contract not spent - check if timelock matured
1864+ if let Some ( timelock) = outgoing. get_timelock ( ) {
1865+ // get current confirmations to calculate remaining blocks
1866+ let confirmations: u32 = self
18721867 . wallet
1873- . spend_via_timelock_v2 ( outgoing, & self . watch_service )
1874- {
1875- Ok ( txid) => {
1876- log:: info!( "Successfully recovered outgoing contract via timelock: {}" , txid) ;
1877- }
1878- Err ( e) => {
1879- log:: error!( "Failed to spend via timelock: {:?}" , e) ;
1868+ . rpc
1869+ . get_raw_transaction_info ( & contract_txid, None )
1870+ . ok ( )
1871+ . and_then ( |info| info. confirmations )
1872+ . unwrap_or ( 0 ) ;
1873+
1874+ let remaining_blocks = if confirmations > timelock {
1875+ 0
1876+ } else {
1877+ timelock - confirmations + 1
1878+ } ;
1879+
1880+ if remaining_blocks == 0 {
1881+ log:: info!( "Timelock matured, attempting recovery" ) ;
1882+
1883+ match self
1884+ . wallet
1885+ . spend_via_timelock_v2 ( outgoing, & self . watch_service )
1886+ {
1887+ Ok ( txid) => {
1888+ log:: info!( "Successfully recovered outgoing contract via timelock: {}" , txid) ;
1889+ recovered_indices. push ( idx) ;
1890+ }
1891+ Err ( e) => {
1892+ log:: error!( "Failed to spend via timelock: {:?}" , e) ;
1893+ }
18801894 }
1895+ } else {
1896+ log:: info!(
1897+ "Timelock not yet mature for contract {} - {} blocks remaining (confirmations: {}, required: {})" ,
1898+ contract_txid,
1899+ remaining_blocks,
1900+ confirmations,
1901+ timelock + 1
1902+ ) ;
18811903 }
1882- } else {
1883- log:: info!(
1884- "Timelock not yet mature for contract {}" ,
1885- contract_txid
1886- ) ;
18871904 }
18881905 }
18891906 }
18901907 }
1908+
1909+ // remove recovered contracts
1910+ for idx in recovered_indices. into_iter ( ) . rev ( ) {
1911+ pending_recovery. remove ( idx) ;
1912+ }
1913+
1914+ // If there are still pending contracts, wait before checking again
1915+ if !pending_recovery. is_empty ( ) {
1916+ let wait_time = if cfg ! ( feature = "integration-test" ) {
1917+ std:: time:: Duration :: from_secs ( 10 )
1918+ } else {
1919+ std:: time:: Duration :: from_secs ( 10 * 60 )
1920+ } ;
1921+ log:: info!(
1922+ "Waiting {:?} before checking {} remaining contracts..." ,
1923+ wait_time,
1924+ pending_recovery. len( )
1925+ ) ;
1926+ std:: thread:: sleep ( wait_time) ;
1927+ }
18911928 }
18921929 }
18931930
0 commit comments