diff --git a/CHANGELOG.md b/CHANGELOG.md index 02600095921f..af7dc14ffbaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ - [#5869](https://github.com/ChainSafe/forest/pull/5869) Updated `forest-cli snapshot export` to print average speed. +- [#5969](https://github.com/ChainSafe/forest/pull/5969) Updated `forest-tool snapshot validate` to print better error message for troubleshooting. + ### Removed ### Fixed diff --git a/src/cli_shared/logger/mod.rs b/src/cli_shared/logger/mod.rs index 5263ef4bac92..6711c5e10b17 100644 --- a/src/cli_shared/logger/mod.rs +++ b/src/cli_shared/logger/mod.rs @@ -163,6 +163,10 @@ fn default_env_filter() -> EnvFilter { fn default_tool_filter() -> EnvFilter { let default_directives = [ "info", + "bellperson::groth16::aggregate::verify=warn", + "storage_proofs_core=warn", + "axum=warn", + "filecoin_proofs=warn", "forest::snapshot=info", "forest::progress=info", "libp2p_bitswap=off", diff --git a/src/ipld/util.rs b/src/ipld/util.rs index f530b3b25a8b..5080dc167420 100644 --- a/src/ipld/util.rs +++ b/src/ipld/util.rs @@ -101,11 +101,16 @@ impl Iterator for DfsIter { } } +enum IterateType { + Message(Cid), + StateRoot(Cid), +} + enum Task { // Yield the block, don't visit it. Emit(Cid, Option>), // Visit all the elements, recursively. - Iterate(VecDeque), + Iterate(ChainEpoch, Cid, IterateType, VecDeque), } pin_project! { @@ -195,12 +200,12 @@ impl, ITER: Iterator + Unpin> Stream return Poll::Ready(Some(Ok(CarBlock { cid, data }))); } else if fail_on_dead_links { return Poll::Ready(Some(Err(anyhow::anyhow!( - "missing key: {cid}" + "[Emit] missing key: {cid}" )))); }; } } - Iterate(cid_vec) => { + Iterate(epoch, block_cid, _type, cid_vec) => { while let Some(cid) = cid_vec.pop_front() { // The link traversal implementation assumes there are three types of encoding: // 1. DAG_CBOR: needs to be reachable, so we add it to the queue and load. @@ -220,8 +225,16 @@ impl, ITER: Iterator + Unpin> Stream } return Poll::Ready(Some(Ok(CarBlock { cid, data }))); } else if fail_on_dead_links { + let type_display = match _type { + IterateType::Message(c) => { + format!("message {c}") + } + IterateType::StateRoot(c) => { + format!("state root {c}") + } + }; return Poll::Ready(Some(Err(anyhow::anyhow!( - "missing key: {cid}" + "[Iterate] missing key: {cid} from {type_display} in block {block_cid} at epoch {epoch}" )))); } } @@ -251,6 +264,9 @@ impl, ITER: Iterator + Unpin> Stream // Process block messages. if block.epoch > stateroot_limit { this.dfs.push_back(Iterate( + block.epoch, + *block.cid(), + IterateType::Message(block.messages), DfsIter::from(block.messages) .filter_map(ipld_to_cid) .collect(), @@ -263,6 +279,9 @@ impl, ITER: Iterator + Unpin> Stream // NOTE: In the original `walk_snapshot` implementation we walk the dag // immediately. Which is what we do here as well, but using a queue. this.dfs.push_back(Iterate( + block.epoch, + *block.cid(), + IterateType::StateRoot(block.state_root), DfsIter::from(block.state_root) .filter_map(ipld_to_cid) .collect(), @@ -429,7 +448,9 @@ impl< // If the receiving end has already quit - just ignore it and // break out of the loop. let _ = block_sender - .send_async(Err(anyhow::anyhow!("missing key: {cid}"))) + .send_async(Err(anyhow::anyhow!( + "[Send] missing key: {cid}" + ))) .await; break 'main; } @@ -499,7 +520,7 @@ impl<'a, DB: Blockstore + Send + Sync + 'static, T: Iterator + Un } else if let Some(data) = self.db.get(&cid)? { return Poll::Ready(Some(Ok(CarBlock { cid, data }))); } else if fail_on_dead_links { - return Poll::Ready(Some(Err(anyhow::anyhow!("missing key: {cid}")))); + return Poll::Ready(Some(Err(anyhow::anyhow!("[Poll] missing key: {cid}")))); } } diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index f781af558592..5ca111b48777 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -1727,7 +1727,7 @@ where NO_CALLBACK, VMTrace::NotTraced, ) - .context("couldn't compute tipset state")?; + .map_err(|e| anyhow::anyhow!("couldn't compute tipset state: {e}"))?; let expected_receipt = child.min_ticket_block().message_receipts; let expected_state = child.parent_state(); match (expected_state, expected_receipt) == (&actual_state, actual_receipt) { diff --git a/src/tool/subcommands/snapshot_cmd.rs b/src/tool/subcommands/snapshot_cmd.rs index fd0e02fe8d78..ffe361230f22 100644 --- a/src/tool/subcommands/snapshot_cmd.rs +++ b/src/tool/subcommands/snapshot_cmd.rs @@ -409,7 +409,10 @@ where ), ); - let last_epoch = ts.epoch() - epochs as i64; + // Fix off-by-1 bug: prevent validating more epochs than available in the snapshot. + // Without +1, specifying --check-stateroots=900 would validate 901 epochs, + // causing out-of-bounds errors when the snapshot contains only 900 recent state roots. + let last_epoch = ts.epoch() - epochs as i64 + 1; // Bundles are required when doing state migrations. load_actor_bundles(&db, &network).await?; @@ -445,7 +448,6 @@ where )?; pb.finish_with_message("✅ verified!"); - drop(pb); Ok(()) }