@@ -535,6 +535,7 @@ where
535535 self . check_signed_contracts ( & signed_contracts) . await ?;
536536 self . check_confirmed_contracts ( ) . await ?;
537537 self . check_preclosed_contracts ( ) . await ?;
538+ self . recover_incorrectly_closed_contracts ( ) . await ?;
538539
539540 if check_channels {
540541 self . channel_checks ( ) . await ?;
@@ -890,6 +891,30 @@ where
890891
891892 let contract_id = funding_input. dlc_input . as_ref ( ) . unwrap ( ) . contract_id ;
892893
894+ // Skip pre-close if the splice fund tx was dropped from the
895+ // mempool (get_transaction_confirmations returns Err for
896+ // dropped txs after the esplora fix). Mempool txs (Ok(0))
897+ // and confirmed txs (Ok(n)) proceed normally.
898+ let splice_fund_txid = contract
899+ . accepted_contract
900+ . dlc_transactions
901+ . fund
902+ . compute_txid ( ) ;
903+ if self
904+ . blockchain
905+ . get_transaction_confirmations ( & splice_fund_txid)
906+ . await
907+ . is_err ( )
908+ {
909+ log_debug ! (
910+ self . logger,
911+ "Splice fund tx not found, skipping pre-close of previous contract. splice_fund_txid={} contract_id={}" ,
912+ splice_fund_txid,
913+ contract_id. to_lower_hex_string( ) ,
914+ ) ;
915+ continue ;
916+ }
917+
893918 let confirmed_contract_in_splice = match get_contract_in_state ! (
894919 self ,
895920 & contract_id,
@@ -899,14 +924,14 @@ where
899924 Ok ( c) => Ok ( c) ,
900925 Err ( Error :: InvalidState ( e) ) => {
901926 log_trace ! ( self . logger,
902- "The previouse contract referenced in a splice transaction is in an unexpected state. contract_id={} error={}" ,
927+ "The previouse contract referenced in a splice transaction is in an unexpected state. contract_id={} error={}" ,
903928 contract_id. to_lower_hex_string( ) , e. to_string( ) ,
904929 ) ;
905930 continue ;
906931 }
907932 Err ( e) => {
908933 log_debug ! ( self . logger,
909- "The previouse contract referenced in a splice transaction failed to retrieve. contract_id={} error={}" ,
934+ "The previouse contract referenced in a splice transaction failed to retrieve. contract_id={} error={}" ,
910935 contract_id. to_lower_hex_string( ) , e. to_string( ) ,
911936 ) ;
912937 Err ( e)
@@ -920,8 +945,9 @@ where
920945 } ;
921946
922947 log_debug ! ( self . logger,
923- "The previous contract that was spliced is now closed because the splice contract is confirmed. contract_id={}" ,
948+ "The previous contract that was spliced is now closed because the splice contract is confirmed. contract_id={} splice_fund_txid={}" ,
924949 contract_id. to_lower_hex_string( ) ,
950+ splice_fund_txid,
925951 ) ;
926952
927953 self . store
@@ -1118,10 +1144,14 @@ where
11181144 . dlc_transactions
11191145 . pending_close_txs
11201146 {
1121- let confirmations = self
1147+ let confirmations = match self
11221148 . blockchain
11231149 . get_transaction_confirmations ( & pending_close_tx. compute_txid ( ) )
1124- . await ?;
1150+ . await
1151+ {
1152+ Ok ( c) => c,
1153+ Err ( _) => continue ,
1154+ } ;
11251155
11261156 log_debug ! (
11271157 self . logger,
@@ -1300,10 +1330,32 @@ where
13001330 #[ tracing:: instrument( skip_all, level = "debug" ) ]
13011331 async fn check_preclosed_contract ( & self , contract : & PreClosedContract ) -> Result < ( ) , Error > {
13021332 let broadcasted_txid = contract. signed_cet . compute_txid ( ) ;
1303- let confirmations = self
1333+ let confirmations = match self
13041334 . blockchain
13051335 . get_transaction_confirmations ( & broadcasted_txid)
1306- . await ?;
1336+ . await
1337+ {
1338+ Ok ( c) => c,
1339+ Err ( e) => {
1340+ // For splice pre-closes (no attestations), a tx-not-found
1341+ // error means the splice funding tx was dropped from the
1342+ // mempool. Revert back to Confirmed.
1343+ if contract. attestations . is_none ( ) {
1344+ log_info ! (
1345+ self . logger,
1346+ "Pre-closed splice contract closing tx not found, reverting to confirmed. broadcasted_txid={} contract_id={} error={}" ,
1347+ broadcasted_txid,
1348+ contract. signed_contract. accepted_contract. get_contract_id_string( ) ,
1349+ e
1350+ ) ;
1351+ self . store
1352+ . update_contract ( & Contract :: Confirmed ( contract. signed_contract . clone ( ) ) )
1353+ . await ?;
1354+ return Ok ( ( ) ) ;
1355+ }
1356+ return Err ( e) ;
1357+ }
1358+ } ;
13071359 log_debug ! (
13081360 self . logger,
13091361 "Checking pre-closed contract. broadcasted_txid={} contract_id={} confirmations={}" ,
@@ -1314,9 +1366,10 @@ where
13141366 . get_contract_id_string( ) ,
13151367 confirmations
13161368 ) ;
1369+
13171370 if confirmations >= * NB_CONFIRMATIONS {
13181371 log_debug ! ( self . logger,
1319- "Pre-closed contract is fully confirmed. Moving to closed. broadcasted_txid={} contract_id={}" ,
1372+ "Pre-closed contract is fully confirmed. Moving to closed. broadcasted_txid={} contract_id={}" ,
13201373 broadcasted_txid. to_string( ) ,
13211374 contract. signed_contract. accepted_contract. get_contract_id_string( )
13221375 ) ;
@@ -1356,6 +1409,39 @@ where
13561409 Ok ( ( ) )
13571410 }
13581411
1412+ /// Recover splice-closed contracts whose closing tx was dropped from
1413+ /// the mempool. Only reverts contracts closed via splice (attestations
1414+ /// is None). Uses get_transaction to verify the tx is truly gone — not
1415+ /// just unconfirmed in the mempool.
1416+ #[ tracing:: instrument( skip_all, level = "debug" ) ]
1417+ async fn recover_incorrectly_closed_contracts ( & self ) -> Result < ( ) , Error > {
1418+ let contracts = self . store . get_contracts ( ) . await ?;
1419+ for contract in contracts {
1420+ let Contract :: Closed ( ref closed) = contract else {
1421+ continue ;
1422+ } ;
1423+ if closed. attestations . is_some ( ) {
1424+ continue ;
1425+ }
1426+ let Some ( ref cet) = closed. signed_cet else {
1427+ continue ;
1428+ } ;
1429+ let txid = cet. compute_txid ( ) ;
1430+ if self . blockchain . get_transaction ( & txid) . await . is_err ( ) {
1431+ log_info ! (
1432+ self . logger,
1433+ "Splice-closed contract closing tx not found, reverting to confirmed. closing_txid={} contract_id={}" ,
1434+ txid,
1435+ closed. contract_id. to_lower_hex_string( ) ,
1436+ ) ;
1437+ self . store
1438+ . update_contract ( & Contract :: Confirmed ( closed. signed_contract . clone ( ) ) )
1439+ . await ?;
1440+ }
1441+ }
1442+ Ok ( ( ) )
1443+ }
1444+
13591445 #[ tracing:: instrument( skip_all, level = "debug" ) ]
13601446 async fn close_contract (
13611447 & self ,
@@ -1366,7 +1452,8 @@ where
13661452 let confirmations = self
13671453 . blockchain
13681454 . get_transaction_confirmations ( & signed_cet. compute_txid ( ) )
1369- . await ?;
1455+ . await
1456+ . unwrap_or ( 0 ) ;
13701457
13711458 if confirmations < 1 {
13721459 log_info ! (
@@ -1448,7 +1535,8 @@ where
14481535 let confirmations = self
14491536 . blockchain
14501537 . get_transaction_confirmations ( & refund. compute_txid ( ) )
1451- . await ?;
1538+ . await
1539+ . unwrap_or ( 0 ) ;
14521540 if confirmations == 0 {
14531541 log_debug ! ( self . logger,
14541542 "Refund transaction has not been broadcast yet. Sending transaction txid={} contract_id={}" ,
@@ -1494,7 +1582,8 @@ where
14941582 let confirmations = self
14951583 . blockchain
14961584 . get_transaction_confirmations ( & refund_txid)
1497- . await ?;
1585+ . await
1586+ . unwrap_or ( 0 ) ;
14981587
14991588 if confirmations > 0 {
15001589 // Counterparty (or we) already broadcast the refund tx. Update state.
0 commit comments