X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;ds=sidebyside;f=lightning-transaction-sync%2Fsrc%2Fesplora.rs;h=538918ada953f74e8170864be1a20f7a6e3bf7ad;hb=6d1111111ccafedec6b2050e76ce91eb59c993b4;hp=3f0d4828355161ac0a807a9004636ad3961814ac;hpb=82f4c10e18f9cbe5359a20433fa7638a66c4f96f;p=rust-lightning diff --git a/lightning-transaction-sync/src/esplora.rs b/lightning-transaction-sync/src/esplora.rs index 3f0d4828..538918ad 100644 --- a/lightning-transaction-sync/src/esplora.rs +++ b/lightning-transaction-sync/src/esplora.rs @@ -14,7 +14,6 @@ use esplora_client::r#async::AsyncClient; #[cfg(not(feature = "async-interface"))] use esplora_client::blocking::BlockingClient; -use std::time::Instant; use std::collections::HashSet; use core::ops::Deref; @@ -91,7 +90,8 @@ where let mut sync_state = self.sync_state.lock().await; log_trace!(self.logger, "Starting transaction sync."); - let start_time = Instant::now(); + #[cfg(feature = "time")] + let start_time = std::time::Instant::now(); let mut num_confirmed = 0; let mut num_unconfirmed = 0; @@ -114,16 +114,32 @@ where Ok(unconfirmed_txs) => { // Double-check the tip hash. If it changed, a reorg happened since // we started syncing and we need to restart last-minute. - let check_tip_hash = maybe_await!(self.client.get_tip_hash())?; - if check_tip_hash != tip_hash { - tip_hash = check_tip_hash; - - log_debug!(self.logger, "Encountered inconsistency during transaction sync, restarting."); - sync_state.pending_sync = true; - continue; + match maybe_await!(self.client.get_tip_hash()) { + Ok(check_tip_hash) => { + if check_tip_hash != tip_hash { + tip_hash = check_tip_hash; + + log_debug!(self.logger, "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + num_unconfirmed += unconfirmed_txs.len(); + sync_state.sync_unconfirmed_transactions( + &confirmables, + unconfirmed_txs + ); + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } } - num_unconfirmed += unconfirmed_txs.len(); - sync_state.sync_unconfirmed_transactions(&confirmables, unconfirmed_txs); }, Err(err) => { // (Semi-)permanent failure, retry later. @@ -137,7 +153,7 @@ where } } - match maybe_await!(self.sync_best_block_updated(&confirmables, &tip_hash)) { + match maybe_await!(self.sync_best_block_updated(&confirmables, &mut sync_state, &tip_hash)) { Ok(()) => {} Err(InternalError::Inconsistency) => { // Immediately restart syncing when we encounter any inconsistencies. @@ -162,17 +178,33 @@ where Ok(confirmed_txs) => { // Double-check the tip hash. If it changed, a reorg happened since // we started syncing and we need to restart last-minute. - let check_tip_hash = maybe_await!(self.client.get_tip_hash())?; - if check_tip_hash != tip_hash { - tip_hash = check_tip_hash; - continue; + match maybe_await!(self.client.get_tip_hash()) { + Ok(check_tip_hash) => { + if check_tip_hash != tip_hash { + tip_hash = check_tip_hash; + + log_debug!(self.logger, + "Encountered inconsistency during transaction sync, restarting."); + sync_state.pending_sync = true; + continue; + } + num_confirmed += confirmed_txs.len(); + sync_state.sync_confirmed_transactions( + &confirmables, + confirmed_txs + ); + } + Err(err) => { + // (Semi-)permanent failure, retry later. + log_error!(self.logger, + "Failed during transaction sync, aborting. Synced so far: {} confirmed, {} unconfirmed.", + num_confirmed, + num_unconfirmed + ); + sync_state.pending_sync = true; + return Err(TxSyncError::from(err)); + } } - - num_confirmed += confirmed_txs.len(); - sync_state.sync_confirmed_transactions( - &confirmables, - confirmed_txs, - ); } Err(InternalError::Inconsistency) => { // Immediately restart syncing when we encounter any inconsistencies. @@ -195,14 +227,18 @@ where sync_state.pending_sync = false; } } + #[cfg(feature = "time")] log_debug!(self.logger, "Finished transaction sync at tip {} in {}ms: {} confirmed, {} unconfirmed.", tip_hash, start_time.elapsed().as_millis(), num_confirmed, num_unconfirmed); + #[cfg(not(feature = "time"))] + log_debug!(self.logger, "Finished transaction sync at tip {}: {} confirmed, {} unconfirmed.", + tip_hash, num_confirmed, num_unconfirmed); Ok(()) } #[maybe_async] fn sync_best_block_updated( - &self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, tip_hash: &BlockHash, + &self, confirmables: &Vec<&(dyn Confirm + Sync + Send)>, sync_state: &mut SyncState, tip_hash: &BlockHash, ) -> Result<(), InternalError> { // Inform the interface of the new block. @@ -213,6 +249,9 @@ where for c in confirmables { c.best_block_updated(&tip_header, tip_height); } + + // Prune any sufficiently confirmed output spends + sync_state.prune_output_spends(tip_height); } } else { return Err(InternalError::Inconsistency); @@ -228,10 +267,13 @@ where // First, check the confirmation status of registered transactions as well as the // status of dependent transactions of registered outputs. - let mut confirmed_txs = Vec::new(); + let mut confirmed_txs: Vec = Vec::new(); for txid in &sync_state.watched_transactions { - if let Some(confirmed_tx) = maybe_await!(self.get_confirmed_tx(&txid, None, None))? { + if confirmed_txs.iter().any(|ctx| ctx.txid == *txid) { + continue; + } + if let Some(confirmed_tx) = maybe_await!(self.get_confirmed_tx(*txid, None, None))? { confirmed_txs.push(confirmed_tx); } } @@ -242,9 +284,19 @@ where { if let Some(spending_txid) = output_status.txid { if let Some(spending_tx_status) = output_status.status { + if confirmed_txs.iter().any(|ctx| ctx.txid == spending_txid) { + if spending_tx_status.confirmed { + // Skip inserting duplicate ConfirmedTx entry + continue; + } else { + log_trace!(self.logger, "Inconsistency: Detected previously-confirmed Tx {} as unconfirmed", spending_txid); + return Err(InternalError::Inconsistency); + } + } + if let Some(confirmed_tx) = maybe_await!(self .get_confirmed_tx( - &spending_txid, + spending_txid, spending_tx_status.block_hash, spending_tx_status.block_height, ))? @@ -267,7 +319,7 @@ where #[maybe_async] fn get_confirmed_tx( - &self, txid: &Txid, expected_block_hash: Option, known_block_height: Option, + &self, txid: Txid, expected_block_hash: Option, known_block_height: Option, ) -> Result, InternalError> { if let Some(merkle_block) = maybe_await!(self.client.get_merkle_block(&txid))? { let block_header = merkle_block.header; @@ -282,7 +334,7 @@ where let mut matches = Vec::new(); let mut indexes = Vec::new(); let _ = merkle_block.txn.extract_matches(&mut matches, &mut indexes); - if indexes.len() != 1 || matches.len() != 1 || matches[0] != *txid { + if indexes.len() != 1 || matches.len() != 1 || matches[0] != txid { log_error!(self.logger, "Retrieved Merkle block for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); return Err(InternalError::Failed); } @@ -290,14 +342,19 @@ where // unwrap() safety: len() > 0 is checked above let pos = *indexes.first().unwrap() as usize; if let Some(tx) = maybe_await!(self.client.get_tx(&txid))? { + if tx.txid() != txid { + log_error!(self.logger, "Retrieved transaction for txid {} doesn't match expectations. This should not happen. Please verify server integrity.", txid); + return Err(InternalError::Failed); + } + if let Some(block_height) = known_block_height { // We can take a shortcut here if a previous call already gave us the height. - return Ok(Some(ConfirmedTx { tx, block_header, pos, block_height })); + return Ok(Some(ConfirmedTx { tx, txid, block_header, pos, block_height })); } let block_status = maybe_await!(self.client.get_block_status(&block_hash))?; if let Some(block_height) = block_status.height { - return Ok(Some(ConfirmedTx { tx, block_header, pos, block_height })); + return Ok(Some(ConfirmedTx { tx, txid, block_header, pos, block_height })); } else { // If any previously-confirmed block suddenly is no longer confirmed, we found // an inconsistency and should start over.