Skip to content

feat: add migration checker cmd #1114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open

Conversation

omerfirmak
Copy link

@omerfirmak omerfirmak commented Feb 10, 2025

1. Purpose or design rationale of this PR

...

2. PR title

Your PR title must follow conventional commits (as we are doing squash merge for each PR), so it must start with one of the following types:

  • build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
  • ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
  • docs: Documentation-only changes
  • feat: A new feature
  • fix: A bug fix
  • perf: A code change that improves performance
  • refactor: A code change that doesn't fix a bug, or add a feature, or improves performance
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • test: Adding missing tests or correcting existing tests

3. Deployment tag versioning

Has the version in params/version.go been updated?

  • This PR doesn't involve a new deployment, git tag, docker image tag, and it doesn't affect traces
  • Yes

4. Breaking change label

Does this PR have the breaking-change label?

  • This PR is not a breaking change
  • Yes

Summary by CodeRabbit

  • New Features

    • Introduced a method to initialize a SecureTrie instance without tracing.
    • Added functionality to count leaf nodes in ZkTrie with optional hash verification and parallel execution support.
    • Implemented logging and profiling capabilities for monitoring application performance.
    • Added a comprehensive implementation for checking the equality of two database structures.
  • Bug Fixes

    • Improved error handling to prevent runtime exceptions during data comparisons and processing.
    • Added checks to prevent operations on uninitialized components, enhancing overall robustness.

Copy link

coderabbitai bot commented Feb 10, 2025

Walkthrough

The changes introduce new functionalities to the migration-checker tool and enhance the trie structures. The main.go file implements an HTTP server for logging and profiling database structure comparisons, along with methods for checking equality between different database structures. The secure_trie.go file adds a method for creating a SecureTrie instance without tracing. The tracer.go file includes nil checks in various methods to improve error handling. Lastly, zk_trie.go introduces methods for counting leaf nodes, allowing for parallel execution and optional hash verification.

Changes

File(s) Change Summary
cmd/migration-checker/main.go Introduced methods for database structure equality checks, error handling, and an HTTP server for profiling.
trie/secure_trie.go Added NewSecureNoTracer method to create a SecureTrie instance without tracing.
trie/tracer.go Introduced nil checks in methods (onRead, onInsert, onDelete, etc.) to prevent runtime panics.
trie/zk_trie.go Added CountLeaves and countLeaves methods for counting leaf nodes, supporting optional parallel execution and hash verification.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Main
    participant HTTP_Server
    participant SecureTrie

    User->>Main: Run migration-checker
    Main->>HTTP_Server: Start on 0.0.0.0:6060
    HTTP_Server-->>Main: Log any errors
    Main->>SecureTrie: Create SecureTrie instance
    SecureTrie-->>Main: Return instance
Loading

Poem

I'm a hopping rabbit with code in my soul,
Leaping through changes with a concurrent goal,
Logging and profiling, oh what a delight,
Counting leaves swiftly, from morning to night,
With each new method, I dance and I cheer,
Celebrating code that's robust and clear!
🐰 Happy coding with each new idea so dear!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch 17 times, most recently from a9d9968 to 1651970 Compare February 12, 2025 14:19
@omerfirmak omerfirmak marked this pull request as ready for review February 17, 2025 11:34
@omerfirmak omerfirmak requested a review from Thegaram February 17, 2025 11:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
cmd/migration-checker/main.go (7)

21-22: Consider grouping related global variables into a config struct.
Storing global variables like accountsDone and trieCheckers is convenient, but it can introduce shared state across the entire application. Wrapping them in a configuration or context structure may improve clarity, testability, and modularity.


29-58: Evaluate replacing panics with error returns or exit codes.
While using panicOnError is acceptable in a development tool, panics might complicate automated scripts or higher-level orchestration. Converting these to user-facing error messages with proper exit codes could yield a cleaner CLI experience.


46-49: Check concurrency capacity alignment with real-world usage.
Using runtime.GOMAXPROCS(0)*4 for channel capacity is a good start, but it may need tuning for very large databases. Monitoring resource utilization and adjusting this factor might improve throughput or prevent overconsumption of system resources.


81-83: Add clarity before panicking on leaf count mismatch.
While it’s correct to panic if leaf counts differ, consider logging the specific roots or DB paths for easier troubleshooting.


105-107: Remove unnecessary byte slice conversion.
Static analysis indicates an unnecessary conversion to []byte(...) for mptKey and preimageKey, which are already []byte.

- panic(fmt.Sprintf("%s key %s (preimage %s) not found in mpt", label, hex.EncodeToString([]byte(mptKey)), hex.EncodeToString([]byte(preimageKey))))
+ panic(fmt.Sprintf("%s key %s (preimage %s) not found in mpt", label, hex.EncodeToString(mptKey), hex.EncodeToString(preimageKey)))
🧰 Tools
🪛 GitHub Check: check

[failure] 107-107:
unnecessary conversion (unconvert)


134-146: Handle goroutine panics more gracefully.
Capturing panics in the goroutine and then calling os.Exit(1) is valid, but consider using a more structured error-reporting approach. This might help gather partial results or trigger retries.


217-245: Ensure consistent error handling in loadZkTrie.
panic is used when a preimage is missing. If missing preimages are a frequent partial data scenario, consider logging or returning an error to allow partial inspections.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b56dd9f and a87c094.

📒 Files selected for processing (4)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/secure_trie.go (1 hunks)
  • trie/tracer.go (4 hunks)
  • trie/zk_trie.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 107-107:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (4)
cmd/migration-checker/main.go (2)

69-112: Verify potential mismatch in leaf counts.
The code darts right into a panic if there is a mismatch between MPT leaves and ZK leaves. This strict approach is valid for a verification tool, but confirm whether partial or incremental mismatch logs might be more appropriate for diagnosing large data sets.

🧰 Tools
🪛 GitHub Check: check

[failure] 107-107:
unnecessary conversion (unconvert)


167-215: Watch for race conditions on the shared map.
This code uses a mutex for the shared mptLeafMap. However, the brief locking around if _, ok := mptLeafMap[...] and mptLeafMap[...] = ... might leave a small gap if multiple goroutines detect the same key concurrently before any break/return. Confirm whether this is acceptable or if you require a more robust check-and-insert process.

trie/tracer.go (1)

63-66: Nil receiver checks look correct.
These added if t == nil { return } conditions prevent panics when tracer is intentionally set to nil (e.g., via NewSecureNoTracer). This change maintains robust handling if the tracer is disabled.

Also applies to: 74-77, 89-92, 102-105, 114-116, 130-133

trie/secure_trie.go (1)

68-76: NewSecureNoTracer function is well-structured.
The approach of calling NewSecure and then setting trie.tracer = nil is straightforward and aligns with the new nil-check logic in tracer.go. Consider documenting any limitations when tracing is disabled (e.g., no record of deleted nodes).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
trie/zk_trie.go (1)

242-248: 🛠️ Refactor suggestion

Improve error handling and add documentation.

The method needs better error handling and documentation.

Additionally, fix the pipeline failures by adding the missing verifyNodeHashes argument:

-func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) uint64 {
+func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) (uint64, error) {
 	root, err := t.ZkTrie.Tree().Root()
 	if err != nil {
-		panic("CountLeaves cannot get root")
+		return 0, fmt.Errorf("failed to get root: %w", err)
 	}
-	return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
+	return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes), nil
 }
🧹 Nitpick comments (1)
cmd/migration-checker/main.go (1)

39-42: Consider making levelDB buffer sizes configurable.

The hardcoded buffer sizes (1024, 128) for levelDB might not be optimal for all scenarios. Consider making these configurable via command-line flags.

 var (
     mptDbPath = flag.String("mpt-db", "", "path to the MPT node DB")
     zkDbPath  = flag.String("zk-db", "", "path to the ZK node DB")
     mptRoot   = flag.String("mpt-root", "", "root hash of the MPT node")
     zkRoot    = flag.String("zk-root", "", "root hash of the ZK node")
     paranoid  = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
+    cacheSize = flag.Int("cache-size", 1024, "size of the data cache in MB")
+    handles   = flag.Int("handles", 128, "number of file handles")
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a87c094 and 81ab44f.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: test
trie/zk_trie.go

[failure] 279-279:
not enough arguments in call to leftT.countLeaves


[failure] 282-282:
not enough arguments in call to rightT.countLeaves


[failure] 286-286:
not enough arguments in call to t.countLeaves

🪛 GitHub Check: check
trie/zk_trie.go

[failure] 279-279:
not enough arguments in call to leftT.countLeaves


[failure] 282-282:
not enough arguments in call to rightT.countLeaves


[failure] 286-286:
not enough arguments in call to t.countLeaves


[failure] 279-279:
not enough arguments in call to leftT.countLeaves


[failure] 282-282:
not enough arguments in call to rightT.countLeaves


[failure] 286-286:
not enough arguments in call to t.countLeaves


[failure] 279-279:
not enough arguments in call to leftT.countLeaves

cmd/migration-checker/main.go

[failure] 18-18:
could not import github.com/scroll-tech/go-ethereum/trie (-: # github.com/scroll-tech/go-ethereum/trie

🪛 GitHub Check: build-mock-ccc-geth
trie/zk_trie.go

[failure] 279-279:
not enough arguments in call to leftT.countLeaves


[failure] 282-282:
not enough arguments in call to rightT.countLeaves


[failure] 286-286:
not enough arguments in call to t.countLeaves

🪛 GitHub Actions: CI
trie/zk_trie.go

[error] 279-279: not enough arguments in call to leftT.countLeaves

🪛 golangci-lint (1.62.2)
cmd/migration-checker/main.go

18-18: could not import github.com/scroll-tech/go-ethereum/trie (-: # github.com/scroll-tech/go-ethereum/trie
trie/zk_trie.go:279:62: not enough arguments in call to leftT.countLeaves
have (*zktrie.Hash, func(key []byte, value []byte), int, bool)
want (*zktrie.Hash, func(key []byte, value []byte), int, bool, bool)
trie/zk_trie.go:282:63: not enough arguments in call to rightT.countLeaves
have (*zktrie.Hash, func(key []byte, value []byte), int, bool)
want (*zktrie.Hash, func(key []byte, value []byte), int, bool, bool)
trie/zk_trie.go:286:55: not enough arguments in call to t.countLeaves
have (*zktrie.Hash, func(key []byte, value []byte), int, bool)
want (*zktrie.Hash, func(key []byte, value []byte), int, bool, bool))

(typecheck)

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch from 81ab44f to 12ec3a6 Compare February 21, 2025 14:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
cmd/migration-checker/main.go (2)

61-65: 🛠️ Refactor suggestion

Consider returning errors instead of panicking.

Using panic for error handling is not a best practice. Consider returning errors and letting the caller decide how to handle them.


70-113: 🛠️ Refactor suggestion

Refactor for better modularity and readability.

The function is doing too many things: creating tries, loading data, comparing data, and handling preimage keys. Consider splitting it into smaller, more focused functions.

🧰 Tools
🪛 GitHub Check: check

[failure] 108-108:
unnecessary conversion (unconvert)

trie/zk_trie.go (2)

242-248: 🛠️ Refactor suggestion

Improve error handling and add documentation.

The method needs the following improvements:

  1. Return an error instead of panicking
  2. Add nil check for the callback function
  3. Add documentation explaining the purpose and parameters

250-289: ⚠️ Potential issue

Fix missing arguments and improve error handling.

The method has several issues:

  1. Missing verifyNodeHashes argument in recursive calls
  2. Using panic for error handling
  3. No error handling in goroutines
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81ab44f and 12ec3a6.

📒 Files selected for processing (4)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/secure_trie.go (1 hunks)
  • trie/tracer.go (4 hunks)
  • trie/zk_trie.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • trie/tracer.go
  • trie/secure_trie.go
🧰 Additional context used
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 108-108:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (3)
cmd/migration-checker/main.go (3)

67-69: LGTM!

The function correctly creates a copy of the input byte slice.


158-166: LGTM!

The function correctly compares storage values between ZK and MPT tries.


218-246: LGTM!

The function correctly loads ZK trie data with proper synchronization for parallel processing.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
cmd/migration-checker/main.go (2)

21-22: Consider encapsulating global variables within a struct.

Global variables make testing difficult and can introduce subtle bugs. Consider encapsulating these within a struct:

-var accountsDone atomic.Uint64
-var trieCheckers = make(chan struct{}, runtime.GOMAXPROCS(0)*4)

+type migrationChecker struct {
+    accountsDone atomic.Uint64
+    trieCheckers chan struct{}
+}
+
+func newMigrationChecker() *migrationChecker {
+    maxWorkers := runtime.GOMAXPROCS(0) * 4
+    if maxWorkers <= 0 {
+        maxWorkers = 1
+    }
+    return &migrationChecker{
+        trieCheckers: make(chan struct{}, maxWorkers),
+    }
+}

70-113: Optimize memory usage in trie comparison.

The function loads all leaves into memory before comparison. For large tries, this could cause out-of-memory issues.

Consider implementing a streaming comparison that processes leaves in batches:

 func checkTrieEquality(...) {
+    const batchSize = 1000
     // ... existing setup code ...
-    mptLeafMap := <-mptLeafCh
-    zkLeafMap := <-zkLeafCh
+    for {
+        mptBatch := make(map[string][]byte, batchSize)
+        zkBatch := make(map[string][]byte, batchSize)
+        // ... load and compare batches ...
+        if done {
+            break
+        }
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12ec3a6 and 642c35f.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (1)
cmd/migration-checker/main.go (1)

61-65: Replace panic with error returns for better error handling.

Using panic for error handling is not a best practice.

See previous suggestion about returning errors instead of panicking.

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch 2 times, most recently from bb6c2d4 to 0eb1fb4 Compare March 17, 2025 13:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
trie/zk_trie.go (1)

250-289: 🛠️ Refactor suggestion

Avoid panics in countLeaves and handle concurrency errors gracefully.

The function panics in several places if node retrieval or hash verification fails. This approach prevents any controlled recovery at higher levels and can disrupt in-progress goroutines. Returning an error or feeding the error into a channel would allow callers to respond appropriately.

Example refactor:

 func (t *ZkTrie) countLeaves(depth int, root *zkt.Hash, cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
 	if root == nil {
 		return 0, nil
 	}

 	rootNode, err := t.ZkTrie.Tree().GetNode(root)
 	if err != nil {
-		panic("countLeaves cannot get rootNode")
+		return 0, fmt.Errorf("failed to retrieve node: %w", err)
 	}

 	if rootNode.Type == zktrie.NodeTypeLeaf_New {
 		if verifyNodeHashes {
 			calculatedNodeHash, err := rootNode.NodeHash()
 			if err != nil {
-				panic("countLeaves cannot get calculatedNodeHash")
+				return 0, fmt.Errorf("failed to calculate node hash: %w", err)
 			}
 			if *calculatedNodeHash != *root {
-				panic("countLeaves node hash mismatch")
+				return 0, fmt.Errorf("node hash mismatch")
 			}
 		}
 		cb(rootNode.NodeKey.Bytes(), rootNode.Data())
-		return 1
+		return 1, nil
 	} else {
 		if depth < parallelismCutoffDepth {
 			countCh := make(chan uint64, 1)
 			errCh := make(chan error, 1)

 			leftT := t.Copy()
 			rightT := t.Copy()

 			go func() {
 				spawnWorker(func() {
-					countCh <- leftT.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+					ln, lerr := leftT.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+					countCh <- ln
+					errCh <- lerr
 				})
 			}()

-			rn := rightT.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
-			return rn + <-countCh
+			rn, rerr := rightT.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			ln := <-countCh
+			lerr := <-errCh

+			if rerr != nil {
+				return 0, rerr
+			}
+			if lerr != nil {
+				return 0, lerr
+			}
+			return rn + ln, nil
 		} else {
-			return t.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes) +
-				t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			leftCount, leftErr := t.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			if leftErr != nil {
+				return 0, leftErr
+			}
+			rightCount, rightErr := t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			if rightErr != nil {
+				return 0, rightErr
+			}
+			return leftCount + rightCount, nil
 		}
 	}
 }
🧹 Nitpick comments (4)
cmd/migration-checker/main.go (4)

35-72: Use structured error handling instead of panics in main().

Panic-based error handling within main() abruptly stops execution. Returning or logging errors could give operators more control and visibility into failures. Also consider logging the HTTP server startup error more robustly.


74-78: panicOnError bypasses controlled recovery.

By using panicOnError, non-critical issues can terminate the process. Converting this helper to return or log errors instead would allow callers to decide how to proceed.


144-185: Graceful error handling in checkAccountEquality.

This function forcibly exits the process if recover() catches a panic in the goroutine. Consider returning errors or sending them to a result channel, allowing the main flow to handle them without abruptly stopping the entire program.


187-195: checkStorageEquality relies on panics.

Consider returning an error if the storage data mismatch, rather than panicking. This change can provide better clarity on partial successes/failures and allow for retry logic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af8c904 and 0469b29.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
cmd/migration-checker/main.go (2)

80-82: Utility function dup looks good.

This function is straightforward and correctly duplicates the slice. No concerns here.


197-236: Verify partial iteration in loadMPT.

When a duplicate key is found, the code breaks from iteration immediately. Confirm that skipping subsequent entries is acceptable. Also, consider handling any iteration errors from trie.NewIterator gracefully (e.g., returning them), rather than ignoring them.

trie/zk_trie.go Outdated
Comment on lines 241 to 248

func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace panics with error returns in CountLeaves.

Currently, the function panics if ZkTrie.Tree().Root() fails to load. Using panic can abruptly terminate the application without giving callers a chance to recover. Consider returning an error (or zero count plus an error) to allow graceful handling upstream.

Example refactor:

 func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
 	root, err := t.ZkTrie.Tree().Root()
 	if err != nil {
-		panic("CountLeaves cannot get root")
+		return 0, fmt.Errorf("failed to get root: %w", err)
 	}
-	return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+	count := t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+	return count, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
}
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
return 0, fmt.Errorf("failed to get root: %w", err)
}
count := t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
return count, nil
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
cmd/migration-checker/main.go (1)

35-72: 🛠️ Refactor suggestion

Validate user inputs and handle errors more gracefully
Within the main function, flag values (mpt-db, zk-db, mpt-root, and zk-root) are assumed valid. If these flags or paths are incorrect, the program panics only after the DB open call fails. Validating flags immediately after parsing can surface errors sooner and avoid partial setup.

 flag.Parse()

+if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+    fmt.Println("Error: required flags --mpt-db, --zk-db, --mpt-root, --zk-root must be provided.")
+    os.Exit(1)
+}

 go func() {
    log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
 }()
🧹 Nitpick comments (2)
cmd/migration-checker/main.go (2)

83-142: Consider decomposing this large function for readability and maintainability
checkTrieEquality handles setup, concurrency, progress logging, multiple comparisons, and error checking in one place. Splitting these responsibilities into smaller, dedicated functions or modules can improve clarity and testability.


197-236: Propagate a unified cancellation or completion signal in concurrent MPT loading
When a duplicate key is found, one goroutine breaks, but others continue to iterate. For large data sets, this might waste resources. Consider a cancellation context or a shared done channel to stop other goroutines.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0469b29 and 19683a4.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
trie/zk_trie.go (1)

241-248: Replace panics with proper error handling
As previously suggested in other parts of the code, using panic for error handling can make debugging and recovery difficult. Consider returning an error from CountLeaves instead, providing callers the opportunity to handle failures gracefully.

-func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
-    root, err := t.ZkTrie.Tree().Root()
-    if err != nil {
-        panic("CountLeaves cannot get root")
-    }
-    return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
-}
+func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
+    root, err := t.ZkTrie.Tree().Root()
+    if err != nil {
+        return 0, fmt.Errorf("failed to get root: %w", err)
+    }
+    count := t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+    return count, nil
+}
cmd/migration-checker/main.go (4)

74-78: Avoid using panicOnError for routine errors
This helper enforces a panic whenever an error occurs. That can abruptly stop program execution, leaving no way to recover. Consider returning or logging errors to handle them gracefully.


80-82: No concerns with slice duplication
The dup function is a succinct way to produce a copy of the provided slice. This looks good.


144-185: Replace multiple panics with structured error handling
checkAccountEquality extensively uses panic to handle unexpected conditions. By returning errors or using a more controlled mechanism (logging, sentinel errors, etc.), you ensure the caller can gracefully handle partial failures and keep the rest of the system operational.


238-269: Remove panic when zk preimage is not found
If the code fails to find a key’s preimage (line 247), it panics immediately. Instead, consider returning an error or using an error channel for concurrency. This aligns better with robust error management practices.

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch 2 times, most recently from 9f8af37 to 324fc7a Compare March 18, 2025 10:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
trie/zk_trie.go (1)

250-289: 🛠️ Refactor suggestion

Refactor concurrency and remove panics for safer error handling.

Within countLeaves, goroutines are spawned for parallel leaf counting, but there is no mechanism to propagate errors back to the main thread. Panics in the goroutines cannot be intercepted, leading to abrupt termination. Additionally, there is no buffering or wait group to catch the first error should one goroutine fail. Consider:

  1. Returning (uint64, error) from countLeaves, capturing and merging errors from both subcalls.
  2. Using a buffered channel for counts and errors, or a sync.WaitGroup plus an error channel, to handle concurrency safely.
  3. Removing or replacing all panic calls with proper error returns or logging to allow the caller to handle errors gracefully.

Example approach (minimal illustration):

-func (t *ZkTrie) countLeaves(root *zkt.Hash, cb func(key, value []byte), depth int, parallel, verifyNodeHashes bool) uint64 {
+func (t *ZkTrie) countLeaves(root *zkt.Hash, cb func(key, value []byte), depth int, parallel, verifyNodeHashes bool) (uint64, error) {
     if root == nil {
-        return 0
+        return 0, nil
     }

     rootNode, err := t.ZkTrie.Tree().GetNode(root)
     if err != nil {
-        panic("countLeaves cannot get rootNode")
+        return 0, fmt.Errorf("failed to get node: %w", err)
     }

     // ...
 }
🧹 Nitpick comments (3)
cmd/migration-checker/main.go (3)

35-76: Validate essential command-line flags before proceeding.

The code checks only for errors in opening databases but does not enforce that the user actually provided values for -mpt-db, -zk-db, -mpt-root, and -zk-root. If these flags are required, validating that they are non-empty and returning an error or usage message would improve user experience.


111-170: Separate concurrency logic from business logic in checkTrieEquality.

This function handles concurrency, logs durations, spawns MPT/ZK tries, compares their leaves, and panics on mismatches. Mixing concurrency, I/O, and error handling at this scale complicates nesting and future maintenance. Consider:

  1. Extracting concurrency-related aspects (channels, goroutines) into a helper function.
  2. Substituting panic calls with structured error returns or logging.
  3. Using a more robust concurrency pattern (e.g., wait groups or buffered channels) to prevent partial loads or half-finished comparisons from leaving the system in an unknown state.

172-213: Use consistent error reporting for account mismatches.

Mismatches in nonce, balance, or code hash all trigger a panic. This approach makes it impossible to gather multiple mismatch details in a single run. Consider returning an aggregated list of errors or logging them, especially if the system needs to continue evaluating other accounts.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f8af37 and 324fc7a.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
trie/zk_trie.go (1)

242-248: Consider returning an error instead of panicking in CountLeaves.

The function signature returns only uint64 but may encounter errors (e.g., failing to retrieve the root). Relying on panic precludes graceful error handling for callers and may abruptly terminate the program. Returning (uint64, error) instead of uint64 would make errors explicit and reduce the risk of unintended crashes.

cmd/migration-checker/main.go (2)

102-106: Replace panicOnError with structured error handling.

Using panic can abruptly terminate the process, making it difficult for upstream callers to recover. Promote better error handling by returning errors or logging them and allowing the caller to decide how to handle unexpected situations.


275-303: Return errors rather than panicking in loadZkTrie.

The anonymous goroutine calls panic on missing preimages, which immediately kills the process. Instead, capture this failure using an error channel or wait group approach that signals back to the caller. This avoids forcing the entire process to crash when a single leaf key is missing.

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch from 324fc7a to 6cd77ed Compare March 18, 2025 10:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
cmd/migration-checker/main.go (3)

104-108: 🛠️ Refactor suggestion

Return errors instead of panicking.

Using panic for error handling is not a best practice. Consider returning errors and letting the caller decide how to handle them.

-func panicOnError(err error, label, msg string) {
+func handleError(err error, label, msg string) error {
 	if err != nil {
-		panic(fmt.Sprint(label, " error: ", msg, " ", err))
+		return fmt.Errorf("%s error: %s %w", label, msg, err)
 	}
+	return nil
 }

227-275: ⚠️ Potential issue

Fix race condition in MPT loading.

There's a potential race condition in the break condition. The mutex is unlocked before breaking, which could lead to inconsistent state.

func loadMPT(mptTrie *trie.SecureTrie, parallel bool) chan map[string][]byte {
 	startKey := make([]byte, 32)
 	workers := 1 << 6
 	if !parallel {
 		workers = 1
 	}
 	step := byte(0xFF) / byte(workers)
 
 	mptLeafMap := make(map[string][]byte, 1000)
 	var mptLeafMutex sync.Mutex
 
 	var mptWg sync.WaitGroup
 	for i := 0; i < workers; i++ {
 		startKey[0] = byte(i) * step
 		trieIt := trie.NewIterator(mptTrie.NodeIterator(startKey))
 
 		mptWg.Add(1)
 		go func() {
 			defer mptWg.Done()
 			for trieIt.Next() {
 				if parallel {
 					mptLeafMutex.Lock()
 				}
 
+				var shouldBreak bool
 				if _, ok := mptLeafMap[string(trieIt.Key)]; ok {
-					mptLeafMutex.Unlock()
-					break
+					shouldBreak = true
 				}
 
-				mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)
+				if !shouldBreak {
+					mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)
+				}
 
 				if parallel {
 					mptLeafMutex.Unlock()
 				}
 
+				if shouldBreak {
+					break
+				}
 
 				if parallel && len(mptLeafMap)%10000 == 0 {
 					fmt.Println("MPT Accounts Loaded:", len(mptLeafMap))
 				}
 			}
 		}()
 	}
 
-	respChan := make(chan map[string][]byte)
+	respChan := make(chan map[string][]byte, 1)
 	go func() {
 		mptWg.Wait()
 		respChan <- mptLeafMap
 	}()
 	return respChan
 }

277-305: 🛠️ Refactor suggestion

Fix potential race condition and improve error handling in ZK trie loading.

The current implementation has several issues:

  1. It does not properly handle errors from CountLeaves
  2. There is no proper error propagation from the goroutine
  3. Mutex locking pattern could be improved
func loadZkTrie(zkTrie *trie.ZkTrie, parallel, paranoid bool) chan map[string][]byte {
 	zkLeafMap := make(map[string][]byte, 1000)
 	var zkLeafMutex sync.Mutex
-	zkDone := make(chan map[string][]byte)
+	zkDone := make(chan map[string][]byte, 1)
+	errCh := make(chan error, 1)
 	go func() {
-		zkTrie.CountLeaves(func(key, value []byte) {
+		defer func() {
+			if r := recover(); r != nil {
+				var err error
+				if e, ok := r.(error); ok {
+					err = fmt.Errorf("panic in loadZkTrie: %w", e)
+				} else {
+					err = fmt.Errorf("panic in loadZkTrie: %v", r)
+				}
+				errCh <- err
+			}
+		}()
+		
+		count, err := zkTrie.CountLeaves(func(key, value []byte) {
 			preimageKey := zkTrie.GetKey(key)
 			if len(preimageKey) == 0 {
-				panic(fmt.Sprintf("preimage not found zk trie %s", hex.EncodeToString(key)))
+				err = fmt.Errorf("preimage not found zk trie %s", hex.EncodeToString(key))
+				return
 			}
 
 			if parallel {
 				zkLeafMutex.Lock()
+				defer zkLeafMutex.Unlock()
 			}
 
 			zkLeafMap[string(dup(preimageKey))] = value
 
-			if parallel {
-				zkLeafMutex.Unlock()
-			}
-
 			if parallel && len(zkLeafMap)%10000 == 0 {
 				fmt.Println("ZK Accounts Loaded:", len(zkLeafMap))
 			}
 		}, parallel, paranoid)
+		
+		if err != nil {
+			errCh <- fmt.Errorf("error counting leaves: %w", err)
+			return
+		}
+		
+		if len(zkLeafMap) != int(count) {
+			errCh <- fmt.Errorf("leaf count mismatch: got %d leaves but map has %d entries", count, len(zkLeafMap))
+			return
+		}
+		
 		zkDone <- zkLeafMap
+		errCh <- nil
 	}()
-	return zkDone
+	
+	// Return a new channel that includes error handling
+	resultCh := make(chan map[string][]byte, 1)
+	go func() {
+		select {
+		case result := <-zkDone:
+			if err := <-errCh; err != nil {
+				panic(err) // maintain existing panic behavior for now
+			}
+			resultCh <- result
+		case err := <-errCh:
+			if err != nil {
+				panic(err) // maintain existing panic behavior for now
+			}
+		}
+	}()
+	return resultCh
 }
🧹 Nitpick comments (2)
cmd/migration-checker/main.go (2)

141-143: Improve error message for leaf count mismatch.

The current panic message doesn't give enough information to diagnose the issue. Consider enhancing it with more details.

 	if len(mptLeafMap) != len(zkLeafMap) {
-		panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafMap), len(zkLeafMap)))
+		panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d (diff: %d)", 
+			label, len(mptLeafMap), len(zkLeafMap), len(mptLeafMap) - len(zkLeafMap)))
 	}

46-48: Improve HTTP server handling for profiling.

The HTTP server for profiling is started in a goroutine with no error recovery mechanism, and errors are only logged.

 	go func() {
-		log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
+		err := http.ListenAndServe("0.0.0.0:6060", nil)
+		if err != nil && err != http.ErrServerClosed {
+			log.Printf("Profiling HTTP server error: %v", err)
+			// Consider signaling this error to the main thread
+		}
 	}()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 324fc7a and 6cd77ed.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
cmd/migration-checker/main.go (1)

35-48: Add validation for required command-line flags.

The code should validate that required flags are provided before proceeding.

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 	)
 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		fmt.Println("Error: Required flags missing")
+		flag.Usage()
+		os.Exit(1)
+	}

 	go func() {
 		log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
 	}()

trie/zk_trie.go Outdated
Comment on lines 242 to 248
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace panic with proper error handling.

Using panic in a library method can lead to unexpected application crashes. Return errors instead so callers can handle them appropriately.

-func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) uint64 {
+// CountLeaves counts the number of leaf nodes in the trie.
+// It accepts a callback function that is called for each leaf node with its key and value,
+// a parallel flag to enable concurrent counting, and a verification flag to verify node hashes.
+// Returns the total number of leaves and any error encountered.
+func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) (uint64, error) {
+	if cb == nil {
+		return 0, fmt.Errorf("callback function cannot be nil")
+	}
 	root, err := t.ZkTrie.Tree().Root()
 	if err != nil {
-		panic("CountLeaves cannot get root")
+		return 0, fmt.Errorf("failed to get root: %w", err)
 	}
-	return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
+	return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
}
// CountLeaves counts the number of leaf nodes in the trie.
// It accepts a callback function that is called for each leaf node with its key and value,
// a parallel flag to enable concurrent counting, and a verification flag to verify node hashes.
// Returns the total number of leaves and any error encountered.
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), parallel, verifyNodeHashes bool) (uint64, error) {
if cb == nil {
return 0, fmt.Errorf("callback function cannot be nil")
}
root, err := t.ZkTrie.Tree().Root()
if err != nil {
return 0, fmt.Errorf("failed to get root: %w", err)
}
return t.countLeaves(root, cb, 0, parallel, verifyNodeHashes)
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
trie/zk_trie.go (1)

241-248: 🛠️ Refactor suggestion

Consider returning errors instead of panicking in CountLeaves.
By panicking on root retrieval failure at line 245, higher-level code cannot recover from errors gracefully. Converting the panic to an error return fosters safer error propagation and makes your API more robust.

-func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
-	root, err := t.ZkTrie.Tree().Root()
-	if err != nil {
-		panic("CountLeaves cannot get root")
-	}
-	return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+func (t *ZkTrie) CountLeaves(
+	cb func(key, value []byte),
+	spawnWorker func(func()),
+	parallelismCutoffDepth int,
+	verifyNodeHashes bool,
+) (uint64, error) {
+	root, err := t.ZkTrie.Tree().Root()
+	if err != nil {
+		return 0, fmt.Errorf("failed to retrieve root: %w", err)
+	}
+	return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
}
🧹 Nitpick comments (1)
cmd/migration-checker/main.go (1)

248-285: Implement error propagation in loadZkTrie concurrency.
Lines 254-281 spawn multiple goroutines but rely on panic for any critical errors (e.g., missing preimages). This approach prevents other parts of the system from handling or reporting errors gracefully. A dedicated error channel would give more control over concurrency flows.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cd77ed and 2826ccf.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
cmd/migration-checker/main.go (1)

88-92: Replace or complement panicOnError with error returns to improve reliability.
Panic-based flows abruptly halt execution, making it harder for callers to handle errors gracefully. It is often more maintainable to return an error so that upstream logic can decide how best to recover or log.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
cmd/migration-checker/main.go (4)

88-92: 🛠️ Refactor suggestion

Consider returning errors instead of panicking

Using panic for error handling is not a best practice. Consider returning errors and letting the caller decide how to handle them.

-func panicOnError(err error, label, msg string) {
+func handleError(err error, label, msg string) error {
 	if err != nil {
-		panic(fmt.Sprint(label, " error: ", msg, " ", err))
+		return fmt.Errorf("%s error: %s %w", label, msg, err)
 	}
+	return nil
 }

176-187: 🛠️ Refactor suggestion

Improve goroutine error handling in account trie checking

The current error handling in the goroutine only prints the panic and exits the process. This is a drastic measure that prevents proper error reporting and recovery.

 		zkRoot := common.BytesToHash(zkAccount.Root[:])
 		mptRoot := common.BytesToHash(mptAccount.Root[:])
+		errCh := make(chan error, 1)
 		go func() {
 			defer func() {
 				if p := recover(); p != nil {
-					fmt.Println(p)
-					os.Exit(1)
+					var err error
+					if e, ok := p.(error); ok {
+						err = fmt.Errorf("%s: %w", label, e)
+					} else {
+						err = fmt.Errorf("%s: %v", label, p)
+					}
+					errCh <- err
+					return
 				}
+				errCh <- nil
 			}()

 			checkTrieEquality(zkRoot, mptRoot, label, checkStorageEquality, false, paranoid)
 			accountsDone.Add(1)
 			fmt.Println("Accounts done:", accountsDone.Load())
 		}()
+		// Wait for goroutine to complete or panic
+		if err := <-errCh; err != nil {
+			fmt.Fprintf(os.Stderr, "Fatal error: %v\n", err)
+			os.Exit(1) // Still exit, but with better error reporting
+		}

204-246: 🛠️ Refactor suggestion

Fix potential race condition in break condition

The break condition after checking for duplicate keys has a race condition. The mutex is unlocked before breaking, which could lead to inconsistent state.

 			for trieIt.Next() {
 				mptLeafMutex.Lock()
+				var shouldBreak bool
 				if _, ok := mptLeafMap[string(trieIt.Key)]; ok {
-					mptLeafMutex.Unlock()
-					break
+					shouldBreak = true
 				}
-				mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)
+				if !shouldBreak {
+					mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)
+				}
 				mptLeafMutex.Unlock()
+
+				if shouldBreak {
+					break
+				}

36-86: 🛠️ Refactor suggestion

Add validation for required command-line flags

The code should validate that required flags are provided before proceeding. Without validation, the program may panic later with cryptic errors if any required flag is missing.

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 	)
 	flag.Parse()
+
+	// Validate required flags
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		fmt.Println("Error: required flags -mpt-db, -zk-db, -mpt-root, and -zk-root must be provided")
+		flag.Usage()
+		os.Exit(1)
+	}
🧹 Nitpick comments (3)
cmd/migration-checker/main.go (3)

47-49: Improve HTTP server error handling

The HTTP server is started in a goroutine, but any errors it encounters are only logged and not handled properly. This could lead to silent failures.

 	go func() {
-		log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
+		err := http.ListenAndServe("0.0.0.0:6060", nil)
+		if err != nil {
+			log.Printf("HTTP server error: %v", err)
+		}
 	}()

97-151: Consider refactoring checkTrieEquality for better maintainability

The function is quite long and does multiple things: creating tries, logging progress, comparing data, and handling key formatting. Consider splitting it into smaller, more focused functions for better maintainability.

For example, key handling logic could be extracted:

func normalizePreimageKey(preimageKey string, top bool) string {
    if top {
        if len(preimageKey) > 20 {
            for _, b := range []byte(preimageKey)[20:] {
                if b != 0 {
                    panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", 
                        hex.EncodeToString([]byte(preimageKey))))
                }
            }
            return preimageKey[:20]
        }
    } else if len(preimageKey) != 32 {
        zeroes := make([]byte, 32)
        copy(zeroes, []byte(preimageKey))
        return string(zeroes)
    }
    return preimageKey
}

57-65: Extract database creation into a helper function

The database opening code is repeated for both zkDb and mptDb with identical options. Extract this into a helper function to reduce duplication.

+func createReadOnlyDb(path, name string) (*leveldb.Database, error) {
+	return leveldb.NewCustom(path, name, func(options *opt.Options) {
+		// Set default options
+		options.ReadOnly = true
+		options.BlockCacher = opt.NoCacher
+		options.BlockCacheCapacity = 0
+		options.OpenFilesCacheCapacity = 128
+		options.DisableBlockCache = true
+	})
+}

 // In main()
-		zkDb, err := leveldb.NewCustom(*zkDbPath, "zkDb", func(options *opt.Options) {
-			// Set default options
-			options.ReadOnly = true
-			options.BlockCacher = opt.NoCacher
-			options.BlockCacheCapacity = 0
-			options.OpenFilesCacheCapacity = 128
-			options.DisableBlockCache = true
-		})
+		zkDb, err := createReadOnlyDb(*zkDbPath, "zkDb")
 		panicOnError(err, "", "failed to open zk db")
-		mptDb, err := leveldb.NewCustom(*mptDbPath, "mptDb", func(options *opt.Options) {
-			// Set default options
-			options.ReadOnly = true
-			options.BlockCacher = opt.NoCacher
-			options.BlockCacheCapacity = 0
-			options.OpenFilesCacheCapacity = 128
-			options.DisableBlockCache = true
-		})
+		mptDb, err := createReadOnlyDb(*mptDbPath, "mptDb")
 		panicOnError(err, "", "failed to open mpt db")

Also applies to: 66-74

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2826ccf and 1123fc6.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
cmd/migration-checker/main.go (1)
trie/secure_trie.go (1) (1)
  • NewSecureNoTracer (68:76)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch 2 times, most recently from 07c7893 to 22218a8 Compare March 18, 2025 13:00
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
trie/zk_trie.go (1)

241-248: 🛠️ Refactor suggestion

Consider returning an error instead of panicking in CountLeaves.
Panic-based error handling can abruptly terminate the application. By returning an error, the caller can decide how to react, improving robustness and maintainability.

 func (t *ZkTrie) CountLeaves(cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
 	root, err := t.ZkTrie.Tree().Root()
 	if err != nil {
-		panic("CountLeaves cannot get root")
+		return 0, fmt.Errorf("failed to retrieve trie root: %w", err)
 	}
-	return t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+	count := t.countLeaves(0, root, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+	return count, nil
 }
🧹 Nitpick comments (4)
cmd/migration-checker/main.go (4)

35-72: Validate flags and handle DB closure in main().
Consider validating that all required flags (*mptDbPath, *zkDbPath, etc.) are set before proceeding, and close databases properly before exiting (e.g., using defer zkDb.Close() and defer mptDb.Close()) to release resources.


83-142: Refine concurrency logging in checkTrieEquality.
While the minute-based logging helps track progress, consider a context or cancellation mechanism to stop the ticker when the operation completes. This prevents stray goroutines waiting on timeouts.


187-195: Validate storage data structure in checkStorageEquality.
The logic effectively checks hash mismatches. Consider more descriptive error messages or additional checks if partial data might be encountered.


238-275: Handle potential deadlocks in loadZkTrie.
The 5-minute timeout with a direct call to f() can cause partial concurrency flows if trieLoaders is unavailable. Consider a more robust approach (e.g., returning an error, or using a default case in a select to attempt fallback) for consistent concurrency control.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1123fc6 and 22218a8.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
cmd/migration-checker/main.go (1)

80-82: Function dup looks good.
This function correctly returns a copy of the incoming byte slice, preventing unintended modifications.

trie/zk_trie.go Outdated
Comment on lines 250 to 294
func (t *ZkTrie) countLeaves(depth int, root *zkt.Hash, cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
if root == nil {
return 0
}

rootNode, err := t.ZkTrie.Tree().GetNode(root)
if err != nil {
panic("countLeaves cannot get rootNode")
}

if rootNode.Type == zktrie.NodeTypeLeaf_New {
if verifyNodeHashes {
calculatedNodeHash, err := rootNode.NodeHash()
if err != nil {
panic("countLeaves cannot get calculatedNodeHash")
}
if *calculatedNodeHash != *root {
panic("countLeaves node hash mismatch")
}
}

cb(append([]byte{}, rootNode.NodeKey.Bytes()...), append([]byte{}, rootNode.Data()...))
return 1
} else {
if depth < parallelismCutoffDepth {
count := make(chan uint64, 1)
leftT := t.Copy()
spawnWorker(func() {
count <- leftT.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
})
return t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes) + <-count
} else {
return t.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes) +
t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Propagate errors and refine concurrency logic.
In countLeaves, multiple panic calls prevent graceful error handling and can lead to partial data if any error arises. Replacing these panic statements with returned errors would allow the caller to handle failures. Also, consider collecting errors from spawned workers to ensure consistent concurrency behavior.

-func (t *ZkTrie) countLeaves(depth int, root *zkt.Hash, cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) uint64 {
+func (t *ZkTrie) countLeaves(depth int, root *zkt.Hash, cb func(key, value []byte), spawnWorker func(func()), parallelismCutoffDepth int, verifyNodeHashes bool) (uint64, error) {
 	if root == nil {
-		return 0
+		return 0, nil
 	}

 	rootNode, err := t.ZkTrie.Tree().GetNode(root)
 	if err != nil {
-		panic("countLeaves cannot get rootNode")
+		return 0, fmt.Errorf("failed to get node from the trie: %w", err)
 	}

 	if rootNode.Type == zktrie.NodeTypeLeaf_New {
 		if verifyNodeHashes {
 			calculatedNodeHash, err := rootNode.NodeHash()
 			if err != nil {
-				panic("countLeaves cannot get calculatedNodeHash")
+				return 0, fmt.Errorf("failed to calculate node hash: %w", err)
 			}
 			if *calculatedNodeHash != *root {
-				panic("countLeaves node hash mismatch")
+				return 0, fmt.Errorf("node hash mismatch")
 			}
 		}

 		cb(append([]byte{}, rootNode.NodeKey.Bytes()...), append([]byte{}, rootNode.Data()...))
-		return 1
+		return 1, nil
 	} else {
 		if depth < parallelismCutoffDepth {
 			countCh := make(chan uint64, 1)
+			errCh := make(chan error, 1)
 			leftT := t.Copy()
 			spawnWorker(func() {
-				countCh <- leftT.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+				c, err := leftT.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+				if err != nil {
+					errCh <- err
+					return
+				}
+				countCh <- c
+				errCh <- nil
 			})
-			return t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes) + <-countCh
+			rightCount, rightErr := t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			if rightErr != nil {
+				return 0, rightErr
+			}
+			leftCount := <-countCh
+			if leftErr := <-errCh; leftErr != nil {
+				return 0, leftErr
+			}
+			return leftCount + rightCount, nil
 		} else {
-			return t.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes) +
-				t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			leftCount, err := t.countLeaves(depth+1, rootNode.ChildL, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			if err != nil {
+				return 0, err
+			}
+			rightCount, err := t.countLeaves(depth+1, rootNode.ChildR, cb, spawnWorker, parallelismCutoffDepth, verifyNodeHashes)
+			if err != nil {
+				return 0, err
+			}
+			return leftCount + rightCount, nil
 		}
 	}
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +74 to +83
func panicOnError(err error, label, msg string) {
if err != nil {
panic(fmt.Sprint(label, " error: ", msg, " ", err))
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prefer returning errors instead of calling panicOnError.
This helper function can abruptly terminate the program. For production tools or libraries, returning errors often leads to more flexible handling.

Comment on lines 197 to 246
func loadMPT(mptTrie *trie.SecureTrie, top bool) chan map[string][]byte {
startKey := make([]byte, 32)
mptLeafMap := make(map[string][]byte, 1000)
var mptLeafMutex sync.Mutex

var mptWg sync.WaitGroup
for i := 0; i < 255; i++ {
startKey[0] = byte(i)
trieIt := trie.NewIterator(mptTrie.NodeIterator(startKey))

mptWg.Add(1)
<-trieLoaders
go func() {
defer func() {
mptWg.Done()
trieLoaders <- struct{}{}
}()
for trieIt.Next() {
mptLeafMutex.Lock()
if _, ok := mptLeafMap[string(trieIt.Key)]; ok {
mptLeafMutex.Unlock()
break
}
mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)
mptLeafMutex.Unlock()

if top && len(mptLeafMap)%10000 == 0 {
fmt.Println("MPT Accounts Loaded:", len(mptLeafMap))
}
}
}()
}

respChan := make(chan map[string][]byte)
go func() {
mptWg.Wait()
respChan <- mptLeafMap
}()
return respChan
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure partial iteration break won't lead to inconsistent state in loadMPT.
When a duplicate key is found, the loop breaks out of one goroutine, but other goroutines may still be iterating. Consider adding coordination (e.g., a shared flag or context cancellation) so other goroutines do not continue reading partial data.

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch from 22218a8 to 6ed543b Compare March 19, 2025 10:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
cmd/migration-checker/main.go (3)

30-77: Validate required flags before proceeding.

If any required flag (e.g., --mpt-db, --zk-db, etc.) is missing, consider printing usage and exiting more cleanly. This will help avoid unexpected panics down the line.

 flag.Parse()

+if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+    flag.Usage()
+    os.Exit(1)
+}

192-200: Refine error handling for storage mismatch.

Panicking on storage mismatch can be replaced with structured error returns, allowing advanced error recovery or logging.


248-272: Allow configurable error handling for missing preimages.

Panic at line 256 abruptly ends the process, which might be undesirable in some use cases. Providing an error to the caller could enable more flexible handling or logging.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22218a8 and 6ed543b.

📒 Files selected for processing (2)
  • cmd/migration-checker/main.go (1 hunks)
  • trie/zk_trie.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
trie/zk_trie.go (10)
trie/zktrie_deletionproof.go (5) (5)
  • t (21-29)
  • t (32-52)
  • t (60-103)
  • t (106-121)
  • t (125-168)
trie/secure_trie.go (11) (11)
  • t (80-86)
  • t (91-93)
  • t (97-99)
  • t (103-114)
  • t (122-126)
  • t (136-144)
  • t (147-151)
  • t (155-159)
  • t (163-171)
  • t (178-192)
  • t (196-198)
core/state/state_test.go (1) (1)
  • value (104-104)
core/state/state_object.go (3) (3)
  • value (252-252)
  • err (165-165)
  • err (207-207)
trie/trie_test.go (1) (1)
  • root (652-652)
core/state/state_prove.go (2) (2)
  • err (63-63)
  • err (80-80)
core/blockchain.go (2) (2)
  • err (270-270)
  • err (813-813)
trie/proof.go (2) (2)
  • err (59-59)
  • err (166-166)
core/state/snapshot/snapshot.go (1) (1)
  • Tree (160-169)
trie/zk_trie_test.go (1) (1)
  • count (237-237)
cmd/migration-checker/main.go (2)
trie/zk_trie.go (2) (2)
  • NewZkTrie (53-59)
  • ZkTrie (35-38)
trie/secure_trie.go (2) (2)
  • NewSecureNoTracer (68-76)
  • SecureTrie (38-44)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
trie/zk_trie.go (1)

250-294: Replace panics with proper error handling.

This function spawns goroutines for parallel leaf counting, but any failure triggers a panic without graceful recovery. A robust approach is to return an error and combine the results in the parent function.

cmd/migration-checker/main.go (2)

79-83: Avoid panics in library-like helper functions.

panicOnError may abruptly terminate. Consider returning an error or using structured logging to allow the caller to decide how to handle the failure.


85-87: LGTM for byte slice duplication.

This helper function properly returns a copy without issues.

Comment on lines +241 to +248

func (t *ZkTrie) CountLeaves(cb func(key, value []byte), top, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(root, cb, 0, top, verifyNodeHashes)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider returning an error instead of panicking.

Relying on panic("CountLeaves cannot get root") makes it hard for callers to handle errors gracefully. Converting this to an error return would allow the calling code to recover or log the failure as needed.

-func (t *ZkTrie) CountLeaves(cb func(key, value []byte), top, verifyNodeHashes bool) uint64 {
+// CountLeaves counts the number of leaf nodes in the trie
+// and returns the total count or an error if it fails
+func (t *ZkTrie) CountLeaves(cb func(key, value []byte), top, verifyNodeHashes bool) (uint64, error) {
  root, err := t.ZkTrie.Tree().Root()
  if err != nil {
-    panic("CountLeaves cannot get root")
+    return 0, fmt.Errorf("CountLeaves failed to retrieve root: %w", err)
  }
-  return t.countLeaves(root, cb, 0, top, verifyNodeHashes)
+  return t.countLeaves(root, cb, 0, top, verifyNodeHashes)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), top, verifyNodeHashes bool) uint64 {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
panic("CountLeaves cannot get root")
}
return t.countLeaves(root, cb, 0, top, verifyNodeHashes)
}
// CountLeaves counts the number of leaf nodes in the trie
// and returns the total count or an error if it fails
func (t *ZkTrie) CountLeaves(cb func(key, value []byte), top, verifyNodeHashes bool) (uint64, error) {
root, err := t.ZkTrie.Tree().Root()
if err != nil {
return 0, fmt.Errorf("CountLeaves failed to retrieve root: %w", err)
}
return t.countLeaves(root, cb, 0, top, verifyNodeHashes)
}

Comment on lines 88 to 147
func checkTrieEquality(dbs *dbs, zkRoot, mptRoot common.Hash, label string, leafChecker func(string, *dbs, []byte, []byte, bool), top, paranoid bool) {
done := make(chan struct{})
start := time.Now()
if !top {
go func() {
for {
select {
case <-done:
return
case <-time.After(time.Minute):
fmt.Println("Checking trie", label, "for", time.Since(start))
}
}
}()
}
defer close(done)

zkTrie, err := trie.NewZkTrie(zkRoot, trie.NewZktrieDatabaseFromTriedb(trie.NewDatabaseWithConfig(dbs.zkDb, &trie.Config{Preimages: true})))
panicOnError(err, label, "failed to create zk trie")
mptTrie, err := trie.NewSecureNoTracer(mptRoot, trie.NewDatabaseWithConfig(dbs.mptDb, &trie.Config{Preimages: true}))
panicOnError(err, label, "failed to create mpt trie")

mptLeafCh := loadMPT(mptTrie, top)
zkLeafCh := loadZkTrie(zkTrie, top, paranoid)

mptLeafMap := <-mptLeafCh
zkLeafMap := <-zkLeafCh

if len(mptLeafMap) != len(zkLeafMap) {
panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafMap), len(zkLeafMap)))
}

for preimageKey, zkValue := range zkLeafMap {
if top {
// ZkTrie pads preimages with 0s to make them 32 bytes.
// So we might need to clear those zeroes here since we need 20 byte addresses at top level (ie state trie)
if len(preimageKey) > 20 {
for _, b := range []byte(preimageKey)[20:] {
if b != 0 {
panic(fmt.Sprintf("%s padded byte is not 0 (preimage %s)", label, hex.EncodeToString([]byte(preimageKey))))
}
}
preimageKey = preimageKey[:20]
}
} else if len(preimageKey) != 32 {
// storage leafs should have 32 byte keys, pad them if needed
zeroes := make([]byte, 32)
copy(zeroes, []byte(preimageKey))
preimageKey = string(zeroes)
}

mptKey := crypto.Keccak256([]byte(preimageKey))
mptVal, ok := mptLeafMap[string(mptKey)]
if !ok {
panic(fmt.Sprintf("%s key %s (preimage %s) not found in mpt", label, hex.EncodeToString(mptKey), hex.EncodeToString([]byte(preimageKey))))
}

leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(preimageKey))), dbs, zkValue, mptVal, paranoid)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider returning an error for leaf mismatch.

Currently, the function panics if the trie leaf counts or data differ. Exiting abruptly may not be ideal for larger tools or services. Providing retries or partial recoveries is often more user-friendly.

Comment on lines 202 to 246
func loadMPT(mptTrie *trie.SecureTrie, top bool) chan map[string][]byte {
startKey := make([]byte, 32)
workers := 1 << 5
if !top {
workers = 1 << 3
}
step := byte(0xFF) / byte(workers)

mptLeafMap := make(map[string][]byte, 1000)
var mptLeafMutex sync.Mutex

var mptWg sync.WaitGroup
for i := 0; i < workers; i++ {
startKey[0] = byte(i) * step
trieIt := trie.NewIterator(mptTrie.NodeIterator(startKey))

mptWg.Add(1)
go func() {
defer mptWg.Done()
for trieIt.Next() {
mptLeafMutex.Lock()

if _, ok := mptLeafMap[string(trieIt.Key)]; ok {
mptLeafMutex.Unlock()
break
}

mptLeafMap[string(dup(trieIt.Key))] = dup(trieIt.Value)

mptLeafMutex.Unlock()

if top && len(mptLeafMap)%10000 == 0 {
fmt.Println("MPT Accounts Loaded:", len(mptLeafMap))
}
}
}()
}

respChan := make(chan map[string][]byte)
go func() {
mptWg.Wait()
respChan <- mptLeafMap
}()
return respChan
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prevent partial load after duplicate key detection.

Currently, when one goroutine encounters a duplicate key and breaks, the others continue loading. This can cause partial or inconsistent data. Consider coordinating with a shared flag or context cancellation so all goroutines stop together.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
cmd/migration-checker/main.go (8)

30-39: Validate required command-line flags.

The program doesn't validate that required flags (mpt-db, zk-db, mpt-root, zk-root) are provided before proceeding.

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 	)
 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		fmt.Fprintf(os.Stderr, "Error: required flags missing\n")
+		flag.Usage()
+		os.Exit(1)
+	}

57-66: Add HTTP server for profiling.

The code mentions stats and periodic reporting, but lacks a proper profiling server. Consider adding one for better monitoring capabilities.

 	go func() {
 		for {
 			select {
 			case <-done:
 				return
 			case <-time.After(time.Minute):
 				fmt.Println("Active checkers:", totalCheckers-len(trieCheckers))
 			}
 		}
 	}()
 	defer close(done)
+
+	// Start HTTP server for profiling
+	go func() {
+		http.HandleFunc("/progress", func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintf(w, "Accounts processed: %d\nActive checkers: %d\n", 
+				accountsDone.Load(), totalCheckers-len(trieCheckers))
+		})
+		fmt.Println("Starting profiling server on :6060")
+		if err := http.ListenAndServe(":6060", nil); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to start profiling server: %v\n", err)
+		}
+	}()

Don't forget to add the required import:

 import (
+	"net/http"
 	// existing imports
 )

105-109: Error handling for trie creation is too aggressive.

Creating tries shouldn't panic on error. Instead, report the error and exit gracefully.

-	zkTrie, err := trie.NewZkTrie(zkRoot, trie.NewZktrieDatabaseFromTriedb(trie.NewDatabaseWithConfig(dbs.zkDb, &trie.Config{Preimages: true})))
-	panicOnError(err, label, "failed to create zk trie")
-	mptTrie, err := trie.NewSecureNoTracer(mptRoot, trie.NewDatabaseWithConfig(dbs.mptDb, &trie.Config{Preimages: true}))
-	panicOnError(err, label, "failed to create mpt trie")
+	zkTrie, err := trie.NewZkTrie(zkRoot, trie.NewZktrieDatabaseFromTriedb(trie.NewDatabaseWithConfig(dbs.zkDb, &trie.Config{Preimages: true})))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%s error: failed to create zk trie: %v\n", label, err)
+		os.Exit(1)
+	}
+	
+	mptTrie, err := trie.NewSecureNoTracer(mptRoot, trie.NewDatabaseWithConfig(dbs.mptDb, &trie.Config{Preimages: true}))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%s error: failed to create mpt trie: %v\n", label, err)
+		os.Exit(1)
+	}

116-118: Improve error reporting for leaf count mismatches.

Instead of just panicking, provide more detailed diagnostics about the mismatch.

 	if len(mptLeafs) != len(zkLeafs) {
-		panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafs), len(zkLeafs)))
+		fmt.Fprintf(os.Stderr, "\n%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d\n", label, len(mptLeafs), len(zkLeafs))
+		
+		// Try to provide more diagnostic information
+		if len(mptLeafs) > 0 && len(zkLeafs) > 0 {
+			fmt.Fprintf(os.Stderr, "First few MPT keys: %v\n", formatKeys(mptLeafs[:min(5, len(mptLeafs))]))
+			fmt.Fprintf(os.Stderr, "First few ZK keys: %v\n", formatKeys(zkLeafs[:min(5, len(zkLeafs))]))
+		}
+		
+		os.Exit(1)
 	}

You would need to add these helper functions:

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func formatKeys(kvs []kv) []string {
    keys := make([]string, len(kvs))
    for i, kv := range kvs {
        keys[i] = hex.EncodeToString(kv.key)
    }
    return keys
}

183-188: Optimize worker count based on trie size.

The worker count is hardcoded and doesn't adapt based on trie size. For small tries, this could lead to unnecessary overhead.

 func loadMPT(mptTrie *trie.SecureTrie, top bool) chan []kv {
 	startKey := make([]byte, 32)
-	workers := 1 << 5
+	workers := runtime.GOMAXPROCS(0) * 2 // Base worker count on available CPUs
 	if !top {
-		workers = 1 << 3
+		workers = runtime.GOMAXPROCS(0) // Use fewer workers for non-top tries
 	}
+	// Cap workers to a reasonable maximum
+	if workers > 32 {
+		workers = 32
+	}

191-193: Prefer a more dynamic initial capacity for mptLeafs.

The initial capacity is hardcoded to 1000, which might be too small for large tries and too large for small ones.

-	mptLeafs := make([]kv, 0, 1000)
+	// Estimate an appropriate initial capacity
+	initialCapacity := 1000
+	if stats, err := mptTrie.DB().DiskDB().Stat("state"); err == nil && stats["state/chunks"] > 0 {
+		// If we have stats, use them to estimate the capacity
+		initialCapacity = int(stats["state/chunks"])
+	}
+	mptLeafs := make([]kv, 0, initialCapacity)

234-237: Align initial capacity handling in loadZkTrie with loadMPT.

For consistency, use the same approach to determine initial capacity in both functions.

 func loadZkTrie(zkTrie *trie.ZkTrie, top, paranoid bool) chan []kv {
-	zkLeafs := make([]kv, 0, 1000)
+	// Estimate an appropriate initial capacity
+	initialCapacity := 1000
+	if stats, err := zkTrie.Database().DiskDB().Stat("state"); err == nil && stats["state/chunks"] > 0 {
+		// If we have stats, use them to estimate the capacity
+		initialCapacity = int(stats["state/chunks"])
+	}
+	zkLeafs := make([]kv, 0, initialCapacity)

258-261: Improve ZkTrie key padding for non-32-byte keys.

The current implementation creates a new slice and copies the content. This can be simplified.

-				// storage leafs should have 32 byte keys, pad them if needed
-				zeroes := make([]byte, 32)
-				copy(zeroes, []byte(preimageKey))
-				preimageKey = zeroes
+				// storage leafs should have 32 byte keys, pad them if needed
+				if preimageKey != nil {
+					paddedKey := make([]byte, 32)
+					copy(paddedKey, preimageKey)
+					preimageKey = paddedKey
+				} else {
+					preimageKey = make([]byte, 32) // Handle nil case
+				}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed543b and b006ae5.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
cmd/migration-checker/main.go (2)
trie/zk_trie.go (2) (2)
  • NewZkTrie (53-59)
  • ZkTrie (35-38)
trie/secure_trie.go (2) (2)
  • NewSecureNoTracer (68-76)
  • SecureTrie (38-44)
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 122-122:
unnecessary conversion (unconvert)


[failure] 249-249:
unnecessary conversion (unconvert)


[failure] 251-251:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
cmd/migration-checker/main.go (3)

41-47: Add graceful error handling for database connections.

Instead of panicking on database connection errors, consider returning errors and properly shutting down resources.

-	zkDb, err := leveldb.New(*zkDbPath, 1024, 128, "", true)
-	panicOnError(err, "", "failed to open zk db")
-	mptDb, err := leveldb.New(*mptDbPath, 1024, 128, "", true)
-	panicOnError(err, "", "failed to open mpt db")
+	zkDb, err := leveldb.New(*zkDbPath, 1024, 128, "", true)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to open zk db: %v\n", err)
+		os.Exit(1)
+	}
+	defer zkDb.Close()
+
+	mptDb, err := leveldb.New(*mptDbPath, 1024, 128, "", true)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to open mpt db: %v\n", err)
+		zkDb.Close()
+		os.Exit(1)
+	}
+	defer mptDb.Close()

79-83: Prefer returning errors instead of calling panicOnError.

This helper function abruptly terminates the program. For production tools or libraries, returning errors leads to more flexible handling.

Instead of relying on panics, consider refactoring the code to use proper error handling. This would make the tool more reliable and easier to integrate with other systems.


150-156: Improve goroutine error handling in account trie checking.

The current error handling in the goroutine only prints the panic and exits the process. This is a drastic measure that prevents proper error reporting and recovery.

 		go func() {
 			defer func() {
 				if p := recover(); p != nil {
-					fmt.Println(p)
-					os.Exit(1)
+					var err error
+					if e, ok := p.(error); ok {
+						err = fmt.Errorf("%s: %w", label, e)
+					} else {
+						err = fmt.Errorf("%s: %v", label, p)
+					}
+					fmt.Fprintf(os.Stderr, "Fatal error during storage trie check: %v\n", err)
+					// Signal other goroutines to terminate gracefully
+					// Consider using a context with cancel for cleaner termination
+					os.Exit(1) // If you must exit, provide more context
 				}
 			}()

Comment on lines +251 to +252
panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix another unnecessary type conversion in error message.

The unnecessary conversion of []byte(preimageKey) in the hex encoding call.

-						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
}
panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
}
🧰 Tools
🪛 GitHub Check: check

[failure] 251-251:
unnecessary conversion (unconvert)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
cmd/migration-checker/main.go (2)

31-39: ⚠️ Potential issue

Add validation for required command-line flags.

The code should validate that all required flags (mpt-db, zk-db, mpt-root, zk-root) are provided before proceeding.

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 	)
 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		fmt.Fprintf(os.Stderr, "Error: Missing required flags\n")
+		flag.Usage()
+		os.Exit(1)
+	}

149-162: 🛠️ Refactor suggestion

Improve goroutine error handling in account trie checking.

The current error handling in the goroutine only prints the panic and exits the process. This is a drastic measure that prevents proper error reporting and recovery.

 		<-trieCheckers
 		go func() {
 			defer func() {
 				if p := recover(); p != nil {
-					fmt.Println(p)
-					os.Exit(1)
+					var err error
+					if e, ok := p.(error); ok {
+						err = fmt.Errorf("%s: %w", label, e)
+					} else {
+						err = fmt.Errorf("%s: %v", label, p)
+					}
+					fmt.Fprintf(os.Stderr, "Fatal error during storage trie check: %v\n", err)
+					// Consider using a synchronized error channel to report errors back to the main goroutine
+					os.Exit(1) // If you must exit, provide more context
 				}
 			}()
 
 			checkTrieEquality(dbs, zkRoot, mptRoot, label, checkStorageEquality, false, paranoid)
 			accountsDone.Add(1)
 			fmt.Println("Accounts done:", accountsDone.Load())
 			trieCheckers <- struct{}{}
 		}()
🧹 Nitpick comments (6)
cmd/migration-checker/main.go (6)

79-83: Consider returning errors instead of using panic.

Using panic for error handling can make the tool difficult to use as a library or integrate with other systems.

-func panicOnError(err error, label, msg string) {
+func panicOnError(err error, label, msg string) {
 	if err != nil {
 		panic(fmt.Sprint(label, " error: ", msg, " ", err))
 	}
 }

A better approach would be to return errors and handle them gracefully:

func handleError(err error, label, msg string) error {
	if err != nil {
		return fmt.Errorf("%s error: %s %w", label, msg, err)
	}
	return nil
}

// Then in your main function:
if err := handleError(someOperation(), label, msg); err != nil {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}

122-122: Fix unnecessary type conversion.

There's an unnecessary conversion of []byte(zkKv.key) when zkKv.key is already a byte slice.

-		leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(zkKv.key))), dbs, zkKv.value, mptKv.value, paranoid)
+		leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString(zkKv.key)), dbs, zkKv.value, mptKv.value, paranoid)

249-254: Fix unnecessary type conversions in preimage key handling.

There are unnecessary conversions of preimageKey to byte slices when it's already a byte slice.

 				if len(preimageKey) > 20 {
-					for _, b := range []byte(preimageKey)[20:] {
+					for _, b := range preimageKey[20:] {
 						if b != 0 {
-							panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+							panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
 						}
 					}
 					preimageKey = preimageKey[:20]
🧰 Tools
🪛 GitHub Check: check

[failure] 249-249:
unnecessary conversion (unconvert)


[failure] 251-251:
unnecessary conversion (unconvert)


259-259: Fix another unnecessary type conversion.

There's an unnecessary conversion of preimageKey to byte slice when it's already a byte slice.

 				// storage leafs should have 32 byte keys, pad them if needed
 				zeroes := make([]byte, 32)
-				copy(zeroes, []byte(preimageKey))
+				copy(zeroes, preimageKey)
 				preimageKey = zeroes
🧰 Tools
🪛 GitHub Check: check

[failure] 259-259:
unnecessary conversion (unconvert)


183-232: Refactor loadMPT to avoid potential issues with concurrent access.

The function is designed to handle partial iteration based on key ranges, but lacks synchronization for cases where multiple goroutines might try to access overlapping key ranges.

Consider adding a context for coordination and better error handling:

-func loadMPT(mptTrie *trie.SecureTrie, top bool) chan []kv {
+func loadMPT(ctx context.Context, mptTrie *trie.SecureTrie, top bool) chan []kv {
 	startKey := make([]byte, 32)
 	workers := 1 << 5
 	if !top {
 		workers = 1 << 3
 	}
 	step := byte(256 / workers)
 
 	mptLeafs := make([]kv, 0, 1000)
 	var mptLeafMutex sync.Mutex
+	errCh := make(chan error, workers)
 
 	var mptWg sync.WaitGroup
 	for i := 0; i < workers; i++ {
 		startKey[0] = byte(i) * step
 		trieIt := trie.NewIterator(mptTrie.NodeIterator(startKey))
 
 		stopKey := (i + 1) * int(step)
 		mptWg.Add(1)
 		go func() {
 			defer mptWg.Done()
+			defer func() {
+				if r := recover(); r != nil {
+					if err, ok := r.(error); ok {
+						errCh <- fmt.Errorf("panic in loadMPT worker: %w", err)
+					} else {
+						errCh <- fmt.Errorf("panic in loadMPT worker: %v", r)
+					}
+				}
+			}()
 			for trieIt.Next() {
+				select {
+				case <-ctx.Done():
+					return
+				default:
+				}
 				if int(trieIt.Key[0]) >= stopKey {
 					break
 				}
 
 				preimageKey := mptTrie.GetKey(trieIt.Key)
 				if len(preimageKey) == 0 {
-					panic(fmt.Sprintf("preimage not found mpt trie %s", hex.EncodeToString(trieIt.Key)))
+					errCh <- fmt.Errorf("preimage not found mpt trie %s", hex.EncodeToString(trieIt.Key))
+					return
 				}
 
 				mptLeafMutex.Lock()
 				mptLeafs = append(mptLeafs, kv{key: preimageKey, value: dup(trieIt.Value)})
 				mptLeafMutex.Unlock()
 				if top && len(mptLeafs)%10000 == 0 {
 					fmt.Println("MPT Accounts Loaded:", len(mptLeafs))
 				}
 			}
 		}()
 	}
 
 	respChan := make(chan []kv)
 	go func() {
+		// Create a channel to signal cancellation to other goroutines if an error occurs
+		ctx, cancel := context.WithCancel(ctx)
+		defer cancel()
+		
+		// Check for errors from workers
+		go func() {
+			select {
+			case err := <-errCh:
+				fmt.Fprintf(os.Stderr, "Error loading MPT: %v\n", err)
+				cancel()
+				os.Exit(1)
+			case <-ctx.Done():
+				return
+			}
+		}()
+		
 		mptWg.Wait()
 		sort.Slice(mptLeafs, func(i, j int) bool {
 			return bytes.Compare(mptLeafs[i].key, mptLeafs[j].key) < 0
 		})
 		respChan <- mptLeafs
 	}()
 	return respChan
 }

Apply similar changes to loadZkTrie and update function calls accordingly.


1-20: Add HTTP server for pprof profiling.

Since this tool deals with large data structures, adding profiling would help debug performance issues.

 package main
 
 import (
 	"bytes"
 	"encoding/hex"
 	"flag"
 	"fmt"
+	"net/http"
+	_ "net/http/pprof"
 	"os"
 	"runtime"
 	"sort"
 	"sync"
 	"sync/atomic"
 	"time"
 
 	"github.com/scroll-tech/go-ethereum/common"
 	"github.com/scroll-tech/go-ethereum/core/types"
 	"github.com/scroll-tech/go-ethereum/ethdb/leveldb"
 	"github.com/scroll-tech/go-ethereum/rlp"
 	"github.com/scroll-tech/go-ethereum/trie"
 )

Then add the HTTP server initialization in the main function:

func main() {
    // ... existing code ...
    
    // Start pprof server
    pprofPort := flag.String("pprof-port", "6060", "Port for pprof HTTP server")
    flag.Parse()
    
    go func() {
        fmt.Printf("Starting pprof server on :%s\n", *pprofPort)
        if err := http.ListenAndServe(":"+*pprofPort, nil); err != nil {
            fmt.Printf("Failed to start pprof server: %v\n", err)
        }
    }()
    
    // ... rest of the main function ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b006ae5 and 08a049b.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
cmd/migration-checker/main.go (2)
trie/zk_trie.go (2) (2)
  • NewZkTrie (53-59)
  • ZkTrie (35-38)
trie/secure_trie.go (2) (2)
  • NewSecureNoTracer (68-76)
  • SecureTrie (38-44)
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 249-249:
unnecessary conversion (unconvert)


[failure] 251-251:
unnecessary conversion (unconvert)


[failure] 259-259:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch from b088a58 to 08a049b Compare March 20, 2025 09:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
cmd/migration-checker/main.go (1)

186-235: 🛠️ Refactor suggestion

Prevent potential race condition when breaking.

When a duplicate key is found, the loop breaks but other goroutines might still be iterating, which could lead to inconsistent state. Consider adding coordination to ensure all goroutines stop together.

Add a shared flag or context cancellation to coordinate between goroutines:

 func loadMPT(mptTrie *trie.SecureTrie, top bool) chan []kv {
+    ctx, cancel := context.WithCancel(context.Background())
+    defer cancel() // Ensure context is cancelled when function returns
     
     // Rest of the function remains the same
     
     for i := 0; i < workers; i++ {
         mptWg.Add(1)
         go func() {
             defer mptWg.Done()
             for trieIt.Next() {
+                select {
+                case <-ctx.Done():
+                    // Context was cancelled, stop processing
+                    return
+                default:
+                    // Continue processing
+                }
                 
                 if int(trieIt.Key[0]) >= stopKey {
                     break
                 }
                 
                 // Rest of the goroutine logic remains the same
                 
                 // If duplicate found, cancel context to signal other goroutines
+                if duplicateFound {
+                    cancel()
+                    break
+                }
             }
         }()
     }
🧹 Nitpick comments (8)
cmd/migration-checker/main.go (8)

42-45: Add defer statements to close database connections.

The database connections are opened but never properly closed, which could lead to resource leaks. Add defer statements after opening the databases to ensure they're properly closed when the program exits.

  zkDb, err := leveldb.New(*zkDbPath, 1024, 128, "", true)
  panicOnError(err, "", "failed to open zk db")
+ defer zkDb.Close()
  mptDb, err := leveldb.New(*mptDbPath, 1024, 128, "", true)
  panicOnError(err, "", "failed to open mpt db")
+ defer mptDb.Close()

80-84: Consider returning errors instead of panicking.

The panicOnError function causes the program to terminate abruptly, which may not be ideal for a tool that could be used as part of larger workflows. Consider modifying the error handling approach to return errors and allow the caller to decide how to handle them.

-func panicOnError(err error, label, msg string) {
+func checkError(err error, label, msg string) error {
 	if err != nil {
-		panic(fmt.Sprint(label, " error: ", msg, " ", err))
+		return fmt.Errorf("%s error: %s %w", label, msg, err)
 	}
+	return nil
 }

This would require updating all calling code to handle returned errors appropriately.


125-125: Fix unnecessary type conversion.

There's an unnecessary conversion of []byte(zkKv.key) when zkKv.key is already a byte slice.

-	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(zkKv.key))), dbs, zkKv.value, mptKv.value, paranoid)
+	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString(zkKv.key)), dbs, zkKv.value, mptKv.value, paranoid)
🧰 Tools
🪛 GitHub Check: check

[failure] 125-125:
unnecessary conversion (unconvert)


153-165: Improve error handling in goroutine.

The current error handling in the goroutine only prints the panic and exits the process. This is a drastic measure that prevents proper error reporting and recovery.

 go func() {
 	defer func() {
 		if p := recover(); p != nil {
-			fmt.Println(p)
-			os.Exit(1)
+			var err error
+			if e, ok := p.(error); ok {
+				err = fmt.Errorf("%s: %w", label, e)
+			} else {
+				err = fmt.Errorf("%s: %v", label, p)
+			}
+			fmt.Fprintf(os.Stderr, "Fatal error during storage trie check: %v\n", err)
+			// Consider using a context with cancel for cleaner termination
+			os.Exit(1) // If you must exit, provide more context
 		}
 	}()

 	checkTrieEquality(dbs, zkRoot, mptRoot, label, checkStorageEquality, false, paranoid)
 	accountsDone.Add(1)
 	fmt.Println("Accounts done:", accountsDone.Load(), "/", totalAccounts)
 	trieCheckers <- struct{}{}
 }()

252-257: Fix unnecessary type conversions in preimage key handling.

There's an unnecessary conversion of []byte(preimageKey) when iterating over bytes.

-				for _, b := range []byte(preimageKey)[20:] {
+				for _, b := range preimageKey[20:] {

Also on line 254:

-						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
🧰 Tools
🪛 GitHub Check: check

[failure] 252-252:
unnecessary conversion (unconvert)


[failure] 254-254:
unnecessary conversion (unconvert)


31-40: Add validation for required command-line flags.

The code should validate that required flags are provided before proceeding to avoid potential issues when accessing empty values.

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 	)
 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		flag.Usage()
+		os.Exit(1)
+	}

89-127: Consider adding a HTTP pprof endpoint for profiling.

Given the CPU and memory-intensive nature of trie comparison, it would be beneficial to add a HTTP pprof endpoint to diagnose performance issues during execution.

Consider adding this near the beginning of the checkTrieEquality function or in the main function:

import (
	"net/http"
	_ "net/http/pprof"
)

// Add near the beginning of main():
go func() {
	fmt.Println("Starting pprof server on :6060")
	http.ListenAndServe("localhost:6060", nil)
}()

This will allow you to profile the application using standard Go tools:

🧰 Tools
🪛 GitHub Check: check

[failure] 125-125:
unnecessary conversion (unconvert)


117-121: Provide more detailed error message for trie leaf count mismatch.

When there's a mismatch in leaf counts, it would be helpful to have more context about the specific keys that exist in one trie but not the other.

 if len(mptLeafs) != len(zkLeafs) {
-	panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafs), len(zkLeafs)))
+	// Create maps to find unique keys
+	mptKeys := make(map[string]struct{}, len(mptLeafs))
+	zkKeys := make(map[string]struct{}, len(zkLeafs))
+	
+	for _, kv := range mptLeafs {
+		mptKeys[hex.EncodeToString(kv.key)] = struct{}{}
+	}
+	for _, kv := range zkLeafs {
+		zkKeys[hex.EncodeToString(kv.key)] = struct{}{}
+	}
+	
+	// Find keys in MPT but not in ZK
+	var mptOnly []string
+	for k := range mptKeys {
+		if _, exists := zkKeys[k]; !exists {
+			mptOnly = append(mptOnly, k)
+		}
+	}
+	
+	// Find keys in ZK but not in MPT
+	var zkOnly []string
+	for k := range zkKeys {
+		if _, exists := mptKeys[k]; !exists {
+			zkOnly = append(zkOnly, k)
+		}
+	}
+	
+	// Limit the number of keys to show in the error message
+	const maxKeys = 5
+	if len(mptOnly) > maxKeys {
+		mptOnly = mptOnly[:maxKeys]
+	}
+	if len(zkOnly) > maxKeys {
+		zkOnly = zkOnly[:maxKeys]
+	}
+	
+	panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d\nKeys in MPT only: %v\nKeys in ZK only: %v", 
+		label, len(mptLeafs), len(zkLeafs), mptOnly, zkOnly))
 } else if top {
 	totalAccounts = len(mptLeafs)
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08a049b and c933b5c.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 125-125:
unnecessary conversion (unconvert)


[failure] 252-252:
unnecessary conversion (unconvert)


[failure] 254-254:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
cmd/migration-checker/main.go (4)

43-46: 🛠️ Refactor suggestion

Add resource cleanup for database connections.

The databases are opened but never properly closed, which could lead to resource leaks.

 	zkDb, err := leveldb.New(*zkDbPath, 1024, 128, "", true)
 	panicOnError(err, "", "failed to open zk db")
 	mptDb, err := leveldb.New(*mptDbPath, 1024, 128, "", true)
 	panicOnError(err, "", "failed to open mpt db")
+	defer zkDb.Close()
+	defer mptDb.Close()

41-41: 🛠️ Refactor suggestion

Add validation for required command-line flags.

The code should validate that required flags are provided before proceeding.

 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		flag.Usage()
+		os.Exit(1)
+	}

155-167: 🛠️ Refactor suggestion

Improve concurrency error handling.

When comparing account data, the code spawns a goroutine and calls os.Exit(1) on panic. Consider returning errors or using an error channel to handle partial failures without terminating the entire process.

 		go func() {
 			defer func() {
 				if p := recover(); p != nil {
-					fmt.Println(p)
-					os.Exit(1)
+					var err error
+					if e, ok := p.(error); ok {
+						err = fmt.Errorf("%s: %w", label, e)
+					} else {
+						err = fmt.Errorf("%s: %v", label, p)
+					}
+					fmt.Fprintf(os.Stderr, "Fatal error during storage trie check: %v\n", err)
+					// Consider using a context with cancel for cleaner termination
+					os.Exit(1) // If you must exit, provide more context
 				}
 			}()
 
 			checkTrieEquality(dbs, zkRoot, mptRoot, label, checkStorageEquality, false, paranoid, 0)
 			accountsDone.Add(1)
 			fmt.Println("Accounts done:", accountsDone.Load(), "/", totalAccounts)
 			trieCheckers <- struct{}{}
 		}()

201-225: 🛠️ Refactor suggestion

Fix potential race condition in break condition.

The break condition in the MPT loading logic could lead to inconsistent state if multiple goroutines are processing concurrently.

 		mptWg.Add(1)
 		go func() {
 			defer mptWg.Done()
 			for trieIt.Next() {
 				if int(trieIt.Key[0]) >= stopKey {
 					break
 				}
 
 				preimageKey := mptTrie.GetKey(trieIt.Key)
 				if len(preimageKey) == 0 {
 					panic(fmt.Sprintf("preimage not found mpt trie %s", hex.EncodeToString(trieIt.Key)))
 				}
 
+				var shouldAdd bool
 				mptLeafMutex.Lock()
-				mptLeafs = append(mptLeafs, kv{key: preimageKey, value: dup(trieIt.Value)})
+				shouldAdd = true
+				if shouldAdd {
+					mptLeafs = append(mptLeafs, kv{key: preimageKey, value: dup(trieIt.Value)})
+				}
 				mptLeafMutex.Unlock()
+				
 				if top && len(mptLeafs)%10000 == 0 {
 					fmt.Println("MPT Accounts Loaded:", len(mptLeafs))
 				}
 			}
 		}()
🧹 Nitpick comments (4)
cmd/migration-checker/main.go (4)

39-39: Fix typo in flag name.

There's a typo in the flag name: start-form should be start-from to correctly reflect its purpose.

-		startFrom            = flag.Int("start-form", 0, "start checking from account at the given index")
+		startFrom            = flag.Int("start-from", 0, "start checking from account at the given index")

250-266: Refactor key normalization into a separate function.

The key normalization logic is complex and would be more maintainable as a separate function.

+func normalizePreimageKey(preimageKey []byte, top bool) []byte {
+	if top {
+		// ZkTrie pads preimages with 0s to make them 32 bytes.
+		// So we might need to clear those zeroes here since we need 20 byte addresses at top level (ie state trie)
+		if len(preimageKey) > 20 {
+			for _, b := range preimageKey[20:] {
+				if b != 0 {
+					panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
+				}
+			}
+			return preimageKey[:20]
+		}
+	} else if len(preimageKey) != 32 {
+		// storage leafs should have 32 byte keys, pad them if needed
+		zeroes := make([]byte, 32)
+		copy(zeroes, preimageKey)
+		return zeroes
+	}
+	return preimageKey
+}

 // In loadZkTrie
 			preimageKey := zkTrie.GetKey(key)
 			if len(preimageKey) == 0 {
 				panic(fmt.Sprintf("preimage not found zk trie %s", hex.EncodeToString(key)))
 			}
 
-			if top {
-				// ZkTrie pads preimages with 0s to make them 32 bytes.
-				// So we might need to clear those zeroes here since we need 20 byte addresses at top level (ie state trie)
-				if len(preimageKey) > 20 {
-					for _, b := range []byte(preimageKey)[20:] {
-						if b != 0 {
-							panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
-						}
-					}
-					preimageKey = preimageKey[:20]
-				}
-			} else if len(preimageKey) != 32 {
-				// storage leafs should have 32 byte keys, pad them if needed
-				zeroes := make([]byte, 32)
-				copy(zeroes, []byte(preimageKey))
-				preimageKey = zeroes
-			}
+			preimageKey = normalizePreimageKey(preimageKey, top)
🧰 Tools
🪛 GitHub Check: check

[failure] 254-254:
unnecessary conversion (unconvert)


[failure] 256-256:
unnecessary conversion (unconvert)


265-265: Fix unnecessary type conversion in zeroes copy.

There's an unnecessary type conversion when copying preimageKey to zeroes.

-				copy(zeroes, []byte(preimageKey))
+				copy(zeroes, preimageKey)

31-79: Add HTTP server for profiling.

To assist with debugging and performance optimization, consider adding an HTTP server for profiling.

 import (
 	"bytes"
 	"encoding/hex"
 	"flag"
 	"fmt"
+	"net/http"
+	_ "net/http/pprof"
 	"os"
 	"runtime"
 	"sort"
 	"sync"
 	"sync/atomic"
 	"time"
 
 	"github.com/scroll-tech/go-ethereum/common"
 	"github.com/scroll-tech/go-ethereum/core/types"
 	"github.com/scroll-tech/go-ethereum/ethdb/leveldb"
 	"github.com/scroll-tech/go-ethereum/rlp"
 	"github.com/scroll-tech/go-ethereum/trie"
 )

 func main() {
 	var (
 		mptDbPath            = flag.String("mpt-db", "", "path to the MPT node DB")
 		zkDbPath             = flag.String("zk-db", "", "path to the ZK node DB")
 		mptRoot              = flag.String("mpt-root", "", "root hash of the MPT node")
 		zkRoot               = flag.String("zk-root", "", "root hash of the ZK node")
 		paranoid             = flag.Bool("paranoid", false, "verifies all node contents against their expected hash")
 		parallelismMultipler = flag.Int("parallelism-multiplier", 4, "multiplier for the number of parallel workers")
 		startFrom            = flag.Int("start-form", 0, "start checking from account at the given index")
+		pprofAddr            = flag.String("pprof", "", "enable pprof HTTP server on given address (e.g., localhost:6060)")
 	)
 	flag.Parse()
+
+	if *pprofAddr != "" {
+		go func() {
+			fmt.Println("Starting pprof server on", *pprofAddr)
+			if err := http.ListenAndServe(*pprofAddr, nil); err != nil {
+				fmt.Println("Error starting pprof server:", err)
+			}
+		}()
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c933b5c and 59951b3.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
cmd/migration-checker/main.go (2)
trie/zk_trie.go (2) (2)
  • NewZkTrie (53-59)
  • ZkTrie (35-38)
trie/secure_trie.go (2) (2)
  • NewSecureNoTracer (68-76)
  • SecureTrie (38-44)
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 127-127:
unnecessary conversion (unconvert)


[failure] 254-254:
unnecessary conversion (unconvert)


[failure] 256-256:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
cmd/migration-checker/main.go (5)

81-85: Prefer returning errors instead of calling panicOnError.

This helper function abruptly terminates the program. For production tools or libraries, returning errors instead of panicking allows for more graceful error handling.

-func panicOnError(err error, label, msg string) {
-	if err != nil {
-		panic(fmt.Sprint(label, " error: ", msg, " ", err))
-	}
+func handleError(err error, label, msg string) error {
+	if err != nil {
+		return fmt.Errorf("%s error: %s %w", label, msg, err)
+	}
+	return nil
}

118-122: Consider returning an error for leaf mismatch.

Currently, the function panics if the trie leaf counts differ. Returning an error would allow for more flexible handling of this condition.

 	if len(mptLeafs) != len(zkLeafs) {
-		panic(fmt.Sprintf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafs), len(zkLeafs)))
+		return fmt.Errorf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafs), len(zkLeafs))
 	} else if top {
 		totalAccounts = len(mptLeafs)
 	}

127-127: Fix unnecessary type conversion.

There's an unnecessary conversion of []byte(zkKv.key) when zkKv.key is already a byte slice.

-		leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(zkKv.key))), dbs, zkKv.value, mptKv.value, paranoid)
+		leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString(zkKv.key)), dbs, zkKv.value, mptKv.value, paranoid)
🧰 Tools
🪛 GitHub Check: check

[failure] 127-127:
unnecessary conversion (unconvert)


254-254: Fix unnecessary type conversions in preimage key handling.

There are unnecessary conversions of byte slices that are already in the correct type.

-					for _, b := range []byte(preimageKey)[20:] {
+					for _, b := range preimageKey[20:] {
 						if b != 0 {
-							panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+							panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))

Also applies to: 256-256

🧰 Tools
🪛 GitHub Check: check

[failure] 254-254:
unnecessary conversion (unconvert)


90-129: Refactor for better modularity and readability.

The checkTrieEquality function is doing too many things: creating tries, loading data, comparing data, and handling preimage keys. Consider splitting it into smaller, more focused functions.

+func createTries(dbs *dbs, zkRoot, mptRoot common.Hash, label string) (*trie.ZkTrie, *trie.SecureTrie, error) {
+	zkTrie, err := trie.NewZkTrie(zkRoot, trie.NewZktrieDatabaseFromTriedb(trie.NewDatabaseWithConfig(dbs.zkDb, &trie.Config{Preimages: true})))
+	if err != nil {
+		return nil, nil, fmt.Errorf("%s failed to create zk trie: %w", label, err)
+	}
+	
+	mptTrie, err := trie.NewSecureNoTracer(mptRoot, trie.NewDatabaseWithConfig(dbs.mptDb, &trie.Config{Preimages: true}))
+	if err != nil {
+		return nil, nil, fmt.Errorf("%s failed to create mpt trie: %w", label, err)
+	}
+	
+	return zkTrie, mptTrie, nil
+}

+func compareLeafCounts(zkLeafs, mptLeafs []kv, label string, top bool) error {
+	if len(mptLeafs) != len(zkLeafs) {
+		return fmt.Errorf("%s MPT and ZK trie leaf count mismatch: MPT: %d, ZK: %d", label, len(mptLeafs), len(zkLeafs))
+	} else if top {
+		totalAccounts = len(mptLeafs)
+	}
+	return nil
+}
🧰 Tools
🪛 GitHub Check: check

[failure] 127-127:
unnecessary conversion (unconvert)

@omerfirmak omerfirmak force-pushed the omerfirmak/checker-cmd branch from 59951b3 to 8e0b29c Compare March 20, 2025 10:10
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
cmd/migration-checker/main.go (3)

39-39: Typo in flag name
The flag is declared as "start-form" instead of "start-from". Consider fixing the name to match the usage context.

-	startFrom            = flag.Int("start-form", 0, "start checking from account at the given index")
+	startFrom            = flag.Int("start-from", 0, "start checking from account at the given index")

134-134: Remove unnecessary conversion
zkKv.key is already a []byte. You can directly pass it to hex.EncodeToString() without casting.

-	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(zkKv.key))), dbs, zkKv.value, mptKv.value, paranoid)
+	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString(zkKv.key)), dbs, zkKv.value, mptKv.value, paranoid)

260-260: Remove unnecessary conversions
There’s no need to cast preimageKey to []byte since it’s already a byte slice:

-for _, b := range []byte(preimageKey)[20:] {
+for _, b := range preimageKey[20:] {

-	panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+	panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))

-	copy(zeroes, []byte(preimageKey))
+	copy(zeroes, preimageKey)

Also applies to: 262-262, 270-270

🧰 Tools
🪛 GitHub Check: check

[failure] 260-260:
unnecessary conversion (unconvert)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59951b3 and 8e0b29c.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
cmd/migration-checker/main.go (2)
trie/zk_trie.go (2) (2)
  • NewZkTrie (53-59)
  • ZkTrie (35-38)
trie/secure_trie.go (2) (2)
  • NewSecureNoTracer (68-76)
  • SecureTrie (38-44)
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 260-260:
unnecessary conversion (unconvert)


[failure] 262-262:
unnecessary conversion (unconvert)


[failure] 270-270:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
🔇 Additional comments (5)
cmd/migration-checker/main.go (5)

31-41: Add validation for required flags
All four flags are required for the script to run properly, but there's no code to confirm that these flags are set. This can lead to cryptic errors if the user forgets to provide them.


43-46: Close database connections to prevent resource leaks
You open the databases but never close them. This can cause file locks or resource leaks.

 // after opening the DBs successfully
 defer zkDb.Close()
 defer mptDb.Close()

96-135: Refactor for better modularity and readability
This function handles multiple responsibilities, including setting up the trie, loading leaves, sorting them, and verifying their consistency. Splitting it into smaller helpers can improve clarity and testability.


130-135: Verify partial iteration logic
Starting from an arbitrary index could misalign comparison if either leaf list has changed or doesn’t match in sorted order. Ensure your partial iteration logic won’t skip mismatched keys.


163-165: Refine concurrency error handling
Panicking in the goroutine combined with os.Exit(1) can abruptly terminate the entire process, losing context for debugging. Prefer returning an error or using an error channel for more graceful handling.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
cmd/migration-checker/main.go (3)

41-41: 🛠️ Refactor suggestion

Add validation for required command-line flags.

The code should validate that required flags are provided before proceeding.

 	flag.Parse()
+
+	if *mptDbPath == "" || *zkDbPath == "" || *mptRoot == "" || *zkRoot == "" {
+		flag.Usage()
+		os.Exit(1)
+	}

31-71: ⚠️ Potential issue

Add proper database resource cleanup.

The databases opened at lines 43-46 are never closed, which could lead to resource leaks. Ensure proper cleanup with defer statements after opening the databases.

 	zkDb, err := leveldb.New(*zkDbPath, 1024, 128, "", true)
 	panicOnError(err, "", "failed to open zk db")
 	mptDb, err := leveldb.New(*mptDbPath, 1024, 128, "", true)
 	panicOnError(err, "", "failed to open mpt db")
+	defer zkDb.Close()
+	defer mptDb.Close()

147-159: 🛠️ Refactor suggestion

Improve error handling in the goroutine.

The current implementation panics and exits the process on any error, which prevents proper cleanup and error reporting. Consider using a more graceful approach.

 		go func() {
 			defer func() {
 				if p := recover(); p != nil {
-					fmt.Println(p)
-					os.Exit(1)
+					var err error
+					if e, ok := p.(error); ok {
+						err = fmt.Errorf("%s: %w", label, e)
+					} else {
+						err = fmt.Errorf("%s: %v", label, p)
+					}
+					fmt.Fprintf(os.Stderr, "Fatal error during storage trie check: %v\n", err)
+					// Signal other goroutines to terminate gracefully
+					// Consider using a context with cancel for cleaner termination
+					os.Exit(1) // If you must exit, provide more context
 				}
 			}()
🧹 Nitpick comments (5)
cmd/migration-checker/main.go (5)

119-119: Fix unnecessary type conversion at line 119.

The expression []byte(zkKv.key) is unnecessary as zkKv.key is already a byte slice.

-	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString([]byte(zkKv.key))), dbs, zkKv.value, mptKv.value, paranoid)
+	leafChecker(fmt.Sprintf("%s key: %s", label, hex.EncodeToString(zkKv.key)), dbs, zkKv.value, mptKv.value, paranoid)
🧰 Tools
🪛 GitHub Check: check

[failure] 119-119:
unnecessary conversion (unconvert)


246-251: Fix unnecessary type conversion in preimage key handling.

The expression []byte(preimageKey) is unnecessary when iterating over bytes, as preimageKey is already a byte slice.

-				for _, b := range []byte(preimageKey)[20:] {
+				for _, b := range preimageKey[20:] {
🧰 Tools
🪛 GitHub Check: check

[failure] 248-248:
unnecessary conversion (unconvert)


248-248: Fix another unnecessary type conversion in error message.

The expression []byte(preimageKey) is unnecessary in the hex encoding call.

-						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString([]byte(preimageKey))))
+						panic(fmt.Sprintf("padded byte is not 0 (preimage %s)", hex.EncodeToString(preimageKey)))
🧰 Tools
🪛 GitHub Check: check

[failure] 248-248:
unnecessary conversion (unconvert)


256-256: Fix unnecessary type conversion when copying preimage key.

The expression []byte(preimageKey) is unnecessary as preimageKey is already a byte slice.

-				copy(zeroes, []byte(preimageKey))
+				copy(zeroes, preimageKey)
🧰 Tools
🪛 GitHub Check: check

[failure] 256-256:
unnecessary conversion (unconvert)


110-114: Consider a more graceful approach for handling leaf count mismatches.

Instead of panicking when leaf counts don't match, consider logging the discrepancy and continuing with what you can compare, or providing more detailed information about the mismatched tries.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0b29c and 372533b.

📒 Files selected for processing (1)
  • cmd/migration-checker/main.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: check
cmd/migration-checker/main.go

[failure] 248-248:
unnecessary conversion (unconvert)


[failure] 256-256:
unnecessary conversion (unconvert)


[failure] 119-119:
unnecessary conversion (unconvert)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: test
🔇 Additional comments (2)
cmd/migration-checker/main.go (2)

73-77: Consider returning errors instead of calling panicOnError.

Using panic for error handling makes the code less robust and harder to test. For production tools or libraries, returning errors allows for more flexible handling by callers.


231-275: Improve error handling in loadZkTrie.

Consider returning errors instead of panicking when preimages are not found. This allows for more flexible handling of incomplete or invalid tries.

🧰 Tools
🪛 GitHub Check: check

[failure] 248-248:
unnecessary conversion (unconvert)


[failure] 256-256:
unnecessary conversion (unconvert)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant