-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Better logging when backfilling ancient blocks fail #10796
Changes from 18 commits
088cac8
3c13109
020acf1
690b805
c516d1d
9f6d5d4
5e2bfb9
62c65aa
947d1e4
d9ab3b9
72c58b5
f027d4b
2f5a1c6
e019c8e
6bf1ad3
25233d1
12fd856
f577c8f
658f7ef
323edb7
fc220b0
9e17d61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,7 @@ use bytes::Bytes; | |
| use journaldb::Algorithm; | ||
| use kvdb::DBTransaction; | ||
| use snappy; | ||
| use snapshot::error::Error::UnlinkedAncientBlockChain; | ||
|
|
||
| /// Helper for removing directories in case of error. | ||
| struct Guard(bool, PathBuf); | ||
|
|
@@ -110,17 +111,17 @@ impl Restoration { | |
|
|
||
| let secondary = components.rebuilder(chain, raw_db.clone(), &manifest)?; | ||
|
|
||
| let root = manifest.state_root.clone(); | ||
| let final_state_root = manifest.state_root.clone(); | ||
|
|
||
| Ok(Restoration { | ||
| manifest: manifest, | ||
| manifest, | ||
| state_chunks_left: state_chunks, | ||
| block_chunks_left: block_chunks, | ||
| state: StateRebuilder::new(raw_db.key_value().clone(), params.pruning), | ||
| secondary: secondary, | ||
| secondary, | ||
| writer: params.writer, | ||
| snappy_buffer: Vec::new(), | ||
| final_state_root: root, | ||
| final_state_root, | ||
| guard: params.guard, | ||
| db: raw_db, | ||
| }) | ||
|
|
@@ -170,7 +171,7 @@ impl Restoration { | |
| } | ||
|
|
||
| // finish up restoration. | ||
| fn finalize(mut self, engine: &dyn Engine) -> Result<(), Error> { | ||
| fn finalize(mut self) -> Result<(), Error> { | ||
| use trie::TrieError; | ||
|
|
||
| if !self.is_done() { return Ok(()) } | ||
|
|
@@ -186,13 +187,14 @@ impl Restoration { | |
| self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?; | ||
|
|
||
| // connect out-of-order chunks and verify chain integrity. | ||
| self.secondary.finalize(engine)?; | ||
| self.secondary.finalize()?; | ||
|
|
||
| if let Some(writer) = self.writer { | ||
| writer.finish(self.manifest)?; | ||
| } | ||
|
|
||
| self.guard.disarm(); | ||
| trace!(target: "snapshot", "restoration finalised correctly"); | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -337,16 +339,6 @@ impl Service { | |
| dir | ||
| } | ||
|
|
||
| // replace one the client's database with our own. | ||
| fn replace_client_db(&self) -> Result<(), Error> { | ||
| let migrated_blocks = self.migrate_blocks()?; | ||
| info!(target: "snapshot", "Migrated {} ancient blocks", migrated_blocks); | ||
|
|
||
| let rest_db = self.restoration_db(); | ||
| self.client.restore_db(&*rest_db.to_string_lossy())?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| // Migrate the blocks in the current DB into the new chain | ||
| fn migrate_blocks(&self) -> Result<usize, Error> { | ||
| // Count the number of migrated blocks | ||
|
|
@@ -361,11 +353,27 @@ impl Service { | |
|
|
||
| // The old database looks like this: | ||
| // [genesis, best_ancient_block] ... [first_block, best_block] | ||
| // If we are fully synced neither `best_ancient_block` nor `first_block` is set, and we can assume that the whole range from [genesis, best_block] is imported. | ||
| // The new database only contains the tip of the chain ([first_block, best_block]), | ||
| // If we are fully synced neither `best_ancient_block` nor `first_block` is set, and we can | ||
| // assume that the whole range from [genesis, best_block] is imported. | ||
| // The new database only contains the tip of the chain ([new_first_block, new_best_block]), | ||
| // so the useful set of blocks is defined as: | ||
| // [0 ... min(new.first_block, best_ancient_block or best_block)] | ||
| // | ||
| // If, for whatever reason, the old db does not have ancient blocks (i.e. | ||
| // `best_ancient_block` is `None`) AND a non-zero `first_block`, such that the old db looks | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I should move the parens I think. |
||
| // like [old_first_block..old_best_block] (which may or may not partially overlap with | ||
| // [new_first_block..new_best_block]) we do the conservative thing and do not migrate the | ||
| // old blocks. | ||
| let find_range = || -> Option<(H256, H256)> { | ||
| // In theory, if the current best_block is > new first_block (i.e. ranges overlap) | ||
| // we could salvage them but what if there's been a re-org at the boundary and the two | ||
| // chains do not match anymore? We'd have to check the existing blocks carefully. | ||
| if cur_chain_info.ancient_block_number.is_none() && cur_chain_info.first_block_number.unwrap_or(0) > 0 { | ||
| warn!(target: "blockchain", "blocks in the current DB do not stretch back to genesis; can't salvage them into the new DB. In current DB first block : {:?}/#{:?}, best block: {:?}, #{:?}", | ||
|
dvdplm marked this conversation as resolved.
Outdated
|
||
| cur_chain_info.first_block_hash, cur_chain_info.first_block_number, | ||
| cur_chain_info.best_block_number, cur_chain_info.best_block_hash); | ||
| return None; | ||
| } | ||
| let next_available_from = next_chain_info.first_block_number?; | ||
| let cur_available_to = cur_chain_info.ancient_block_number.unwrap_or(cur_chain_info.best_block_number); | ||
|
|
||
|
|
@@ -375,10 +383,11 @@ impl Service { | |
| return None; | ||
| } | ||
|
|
||
| trace!(target: "snapshot", "Trying to import ancient blocks until {}", highest_block_num); | ||
| trace!(target: "snapshot", "Trying to import ancient blocks until {}. First block in new chain=#{}, first block in old chain=#{:?}, best block in old chain=#{}", highest_block_num, | ||
| next_available_from, cur_chain_info.first_block_number, cur_chain_info.best_block_number); | ||
|
|
||
| // Here we start from the highest block number and go backward to 0, | ||
| // thus starting at `highest_block_num` and targetting `0`. | ||
| // thus starting at `highest_block_num` and targeting `0`. | ||
| let target_hash = self.client.block_hash(BlockId::Number(0))?; | ||
| let start_hash = self.client.block_hash(BlockId::Number(highest_block_num))?; | ||
|
|
||
|
|
@@ -398,7 +407,10 @@ impl Service { | |
| return Ok(count); | ||
| } | ||
|
|
||
| let block = self.client.block(BlockId::Hash(parent_hash)).ok_or(::snapshot::error::Error::UnlinkedAncientBlockChain)?; | ||
| let block = self.client.block(BlockId::Hash(parent_hash)).ok_or_else(|| { | ||
| error!(target: "snapshot", "migrate_blocks: did not find block from parent_hash={} (start_hash={})", parent_hash, start_hash); | ||
|
dvdplm marked this conversation as resolved.
Outdated
|
||
| UnlinkedAncientBlockChain(parent_hash) | ||
| })?; | ||
| parent_hash = block.parent_hash(); | ||
|
|
||
| let block_number = block.number(); | ||
|
|
@@ -412,7 +424,11 @@ impl Service { | |
| next_chain.insert_unordered_block(&mut batch, block, block_receipts, Some(parent_total_difficulty), false, true); | ||
| count += 1; | ||
| }, | ||
| _ => break, | ||
| _ => { | ||
| error!(target: "snapshot", "migrate_blocks: failed to find receipts and parent total difficulty. Block #{}, parent_hash={:?}, parent_total_difficulty={:?}", | ||
| block_number, parent_hash, parent_total_difficulty); | ||
| break | ||
|
dvdplm marked this conversation as resolved.
Outdated
|
||
| }, | ||
| } | ||
|
|
||
| // Writing changes to DB and logging every now and then | ||
|
|
@@ -435,7 +451,11 @@ impl Service { | |
|
|
||
| // We couldn't reach the targeted hash | ||
| if parent_hash != target_hash { | ||
| return Err(::snapshot::error::Error::UnlinkedAncientBlockChain.into()); | ||
| error!(target: "snapshot", "migrate_blocks: could not reach the target_hash, parent_hash={:?}, target_hash={:?}, start_hash={:?}, ancient_block_number={:?}, best_block_number={:?}", | ||
| parent_hash, target_hash, start_hash, | ||
| cur_chain_info.ancient_block_number, cur_chain_info.best_block_number, | ||
| ); | ||
| return Err(UnlinkedAncientBlockChain(parent_hash).into()); | ||
| } | ||
|
|
||
| // Update best ancient block in the Next Chain | ||
|
|
@@ -549,6 +569,8 @@ impl Service { | |
|
|
||
| *self.status.lock() = RestorationStatus::Initializing { | ||
| chunks_done: 0, | ||
| state_chunks: manifest.state_hashes.len() as u32, | ||
| block_chunks: manifest.block_hashes.len() as u32, | ||
| }; | ||
|
|
||
| fs::create_dir_all(&rest_dir)?; | ||
|
|
@@ -563,7 +585,7 @@ impl Service { | |
| manifest: manifest.clone(), | ||
| pruning: self.pruning, | ||
| db: self.restoration_db_handler.open(&rest_db)?, | ||
| writer: writer, | ||
| writer, | ||
| genesis: &self.genesis_block, | ||
| guard: Guard::new(rest_db), | ||
| engine: &*self.engine, | ||
|
|
@@ -654,15 +676,20 @@ impl Service { | |
| // lead to deadlock. | ||
| fn finalize_restoration(&self, rest: &mut Option<Restoration>) -> Result<(), Error> { | ||
| trace!(target: "snapshot", "finalizing restoration"); | ||
| *self.status.lock() = RestorationStatus::Finalizing; | ||
|
|
||
| let recover = rest.as_ref().map_or(false, |rest| rest.writer.is_some()); | ||
|
|
||
| // destroy the restoration before replacing databases and snapshot. | ||
| rest.take() | ||
| .map(|r| r.finalize(&*self.engine)) | ||
| .map(|r| r.finalize()) | ||
| .unwrap_or(Ok(()))?; | ||
|
|
||
| self.replace_client_db()?; | ||
| let migrated_blocks = self.migrate_blocks()?; | ||
| info!(target: "snapshot", "Migrated {} ancient blocks", migrated_blocks); | ||
|
|
||
| // replace the Client's database with the new one (restart the Client). | ||
| self.client.restore_db(&*self.restoration_db().to_string_lossy())?; | ||
|
|
||
| if recover { | ||
| let mut reader = self.reader.write(); | ||
|
|
@@ -690,14 +717,20 @@ impl Service { | |
| /// Feed a chunk of either kind (block or state). no-op if no restoration or status is wrong. | ||
| fn feed_chunk(&self, hash: H256, chunk: &[u8], is_state: bool) { | ||
| // TODO: be able to process block chunks and state chunks at same time? | ||
| let mut restoration = self.restoration.lock(); | ||
| match self.feed_chunk_with_restoration(&mut restoration, hash, chunk, is_state) { | ||
| let r = { | ||
| let mut restoration = self.restoration.lock(); | ||
| self.feed_chunk_with_restoration(&mut restoration, hash, chunk, is_state) | ||
| }; | ||
| match r { | ||
| Ok(()) | | ||
| Err(Error::Snapshot(SnapshotError::RestorationAborted)) => (), | ||
| Err(e) => { | ||
| // TODO: after this we're sometimes deadlocked | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should actually always deadlock - because we already have Instead of calling
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Well, for some reason we do not. It's pretty rare, I've seen it twice.
That's what
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To avoid locking twice? As I said the second ption should be
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the reason I removed
The
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since is guaranteed to deadlock, I thought always, but I suspect it might be just random. In the same file I suppose we already had deadlock issues, so the I propose to use exactly the same patter for
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, thank you for explaining. Is this an option?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to docs:
So I'm not sure either why it didn't always deadlock. The proposed fix looks good. |
||
| warn!("Encountered error during snapshot restoration: {}", e); | ||
| *self.restoration.lock() = None; | ||
| *self.status.lock() = RestorationStatus::Failed; | ||
| self.abort_restore(); | ||
| if let Some(mut status) = self.status.try_lock_for(std::time::Duration::from_millis(10)) { | ||
|
ordian marked this conversation as resolved.
|
||
| *status = RestorationStatus::Failed; | ||
| } | ||
| let _ = fs::remove_dir_all(self.restoration_dir()); | ||
| } | ||
| } | ||
|
|
@@ -707,8 +740,8 @@ impl Service { | |
| fn feed_chunk_with_restoration(&self, restoration: &mut Option<Restoration>, hash: H256, chunk: &[u8], is_state: bool) -> Result<(), Error> { | ||
| let (result, db) = { | ||
| match self.status() { | ||
| RestorationStatus::Inactive | RestorationStatus::Failed => { | ||
| trace!(target: "snapshot", "Tried to restore chunk {:x} while inactive or failed", hash); | ||
| RestorationStatus::Inactive | RestorationStatus::Failed | RestorationStatus::Finalizing => { | ||
| trace!(target: "snapshot", "Tried to restore chunk {:x} while inactive, failed or finalizing", hash); | ||
| return Ok(()); | ||
| }, | ||
| RestorationStatus::Ongoing { .. } | RestorationStatus::Initializing { .. } => { | ||
|
|
@@ -803,7 +836,7 @@ impl SnapshotService for Service { | |
| let mut cur_status = self.status.lock(); | ||
|
|
||
| match *cur_status { | ||
| RestorationStatus::Initializing { ref mut chunks_done } => { | ||
| RestorationStatus::Initializing { ref mut chunks_done, .. } => { | ||
| *chunks_done = self.state_chunks.load(Ordering::SeqCst) as u32 + | ||
| self.block_chunks.load(Ordering::SeqCst) as u32; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.