Skip to content

feat: check checkpoint index and block height to identify reorg earlier#7266

Closed
kamiyaa wants to merge 1 commit intomainfrom
jeff/validator-block-height-electric-boogaloo
Closed

feat: check checkpoint index and block height to identify reorg earlier#7266
kamiyaa wants to merge 1 commit intomainfrom
jeff/validator-block-height-electric-boogaloo

Conversation

@kamiyaa
Copy link
Copy Markdown
Contributor

@kamiyaa kamiyaa commented Oct 28, 2025

Description

  • add additional checkpoint checks so we can detect reorg earlier

Related issues

Backward compatibility

reorg_flag.json content has changed

Testing

  • unittests

Summary by CodeRabbit

  • Improvements
    • Strengthened validator resilience during chain reorganization events with enhanced checkpoint tracking and validation.
    • Added persistent storage and retrieval of checkpoint information for improved recovery during network disruptions.
    • Improved distinction between local and on-chain checkpoint states for better synchronization accuracy.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Oct 28, 2025

⚠️ No Changeset found

Latest commit: 912595f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 28, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (5a4e22d) to head (912595f).
⚠️ Report is 83 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #7266   +/-   ##
=====================================
  Coverage   0.00%   0.00%           
=====================================
  Files          1       1           
  Lines         14      14           
=====================================
  Misses        14      14           
Components Coverage Δ
core ∅ <ø> (∅)
hooks ∅ <ø> (∅)
isms ∅ <ø> (∅)
token ∅ <ø> (∅)
middlewares ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 28, 2025

📝 Walkthrough

Walkthrough

This PR introduces checkpoint persistence and reorg detection mechanisms. A new CheckpointInfo struct stores block height and checkpoint index. The ReorgEvent is restructured to track both local and canonical checkpoint indices. Database layer gets new methods to store and retrieve latest checkpoint info. Validator submit logic gains checkpoint validation and enhanced reorg handling with persistence.

Changes

Cohort / File(s) Summary
New CheckpointInfo Type
rust/main/hyperlane-core/src/types/checkpoint.rs
Introduces CheckpointInfo struct with block_height and checkpoint_index fields, including Encode/Decode trait implementations for serialization.
ReorgEvent Restructuring
rust/main/hyperlane-core/src/types/reorg.rs
Replaces single checkpoint_index field with local_checkpoint_index and canonical_checkpoint_index to track both local and onchain checkpoint states.
Database Trait & Core Implementation
rust/main/hyperlane-base/src/db/mod.rs, rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs
Extends HyperlaneDb trait with store_latest_checkpoint_info() and retrieve_latest_checkpoint_info() methods; adds RocksDB implementations with dedicated storage key.
Validator Submit Logic
rust/main/agents/validator/src/submit.rs
Adds validate_checkpoint(), panic_with_reorg(), and report_reorg_with_checkpoint() helpers; integrates latest checkpoint tracking and validation into main submission workflow with reorg persistence.
Validator Tests
rust/main/agents/validator/src/submit/tests.rs
Updates test fixtures to use local_checkpoint_index and canonical_checkpoint_index in ReorgEvent comparisons; adds setup_validate_checkpoint() helper and comprehensive validation scenario tests.
Checkpoint Syncer Configuration
rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs
Updates ReorgEvent usage to match new field names (local_checkpoint_index, canonical_checkpoint_index).
Test Mock Updates
rust/main/agents/relayer/src/msg/db_loader.rs, rust/main/agents/relayer/src/msg/pending_message.rs
Adds store_latest_checkpoint_info() and retrieve_latest_checkpoint_info() mock implementations to test database layer.

Sequence Diagram(s)

sequenceDiagram
    participant Validator
    participant ValidateCP as validate_checkpoint()
    participant Database
    participant ReorgHandler as panic_with_reorg()
    
    Validator->>Database: retrieve_latest_checkpoint_info()
    Database-->>Validator: CheckpointInfo {local_idx, height}
    
    Validator->>ValidateCP: latest_checkpoint (new)
    ValidateCP->>ValidateCP: Compare indices & heights
    
    alt Index increased (normal)
        ValidateCP-->>Validator: Valid, proceed
    else Index same or lower (reorg detected)
        ValidateCP-->>Validator: Reorg event
        Validator->>ReorgHandler: Latest checkpoint + block_height
        ReorgHandler->>Database: store_latest_checkpoint_info()
        ReorgHandler->>ReorgHandler: Log + report_reorg_with_checkpoint()
        ReorgHandler->>Validator: Panic with ReorgEvent
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring close attention:

  • Validator submit reorg logic (rust/main/agents/validator/src/submit.rs): Dense checkpoint validation, reorg detection, and persistence flow integration—verify correctness of index/height comparisons and edge cases.
  • ReorgEvent field changes (rust/main/hyperlane-core/src/types/reorg.rs, checkpoint_syncer.rs): Breaking API change affecting multiple downstream consumers; ensure all callers properly use local_checkpoint_index and canonical_checkpoint_index.
  • Database trait expansion (mod.rs, hyperlane_db.rs): New trait methods and their RocksDB implementations—verify serialization, error handling, and storage key consistency.
  • Test fixture updates (rust/main/agents/validator/src/submit/tests.rs): Field renames and new assertions; confirm all test scenarios properly validate the new checkpoint tracking behavior.

Possibly related PRs

Suggested reviewers

  • yjamin
  • ameten

Poem

🟢 Checkpoints now persist, nice and deep,
Local and canonical indices we keep,
Reorgs detected, validation precise,
The database stores wisdom, layer so nice. 🛡️

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat: check checkpoint index and block height to identify reorg earlier" directly aligns with the core objective of this changeset. Looking at the raw summary, the primary changes involve adding CheckpointInfo struct with block_height and checkpoint_index fields, introducing database methods to persist and retrieve checkpoint information, modifying ReorgEvent to track local and canonical checkpoint indices separately, and implementing validation logic to detect chain reorgs earlier. The title accurately captures this main purpose without being vague or misleading.
Description Check ✅ Passed The PR description covers the key required sections from the template: it describes what's included (checkpoint checks for earlier reorg detection), links to the related issue (ENG-2223), notes backward compatibility (reorg_flag.json format changed), and mentions testing (unittests). However, the description is sparse and the "Drive-by changes" section is entirely missing. While the content is present, it lacks the detail and completeness that would typically be expected for a change of this scope, which touches multiple files and introduces new public types and methods. The description reads more like a summary than a thorough explanation.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jeff/validator-block-height-electric-boogaloo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/main/agents/validator/src/submit.rs (1)

586-591: Compile-time type mismatch: tree_exceeds_checkpoint expects Checkpoint, callers pass CheckpointAtBlock

This won’t build as-is. Either change the helper to take CheckpointAtBlock or pass .checkpoint at call sites. Minimal change below.

Apply:

-    if tree_exceeds_checkpoint(&latest_checkpoint, &tree) {
+    if tree_exceeds_checkpoint(&latest_checkpoint.checkpoint, &tree) {
-        assert!(
-            !tree_exceeds_checkpoint(correctness_checkpoint, tree),
+        assert!(
+            !tree_exceeds_checkpoint(&correctness_checkpoint.checkpoint, tree),

Alternatively, change the helper signature to take &CheckpointAtBlock and read .index.

Also applies to: 174-187, 234-243

🧹 Nitpick comments (7)
rust/main/hyperlane-core/src/types/checkpoint.rs (2)

111-120: Binary encoding order is index-then-height; please call it out or align fields

You serialize checkpoint_index first and block_height second. Decode matches, so it’s correct, but the struct lists block_height first. Either:

  • add a short comment noting the binary order, or
  • reorder struct fields to match the encoding to avoid head‑scratching later.

Your call—just keep it tidy like a well‑kept swamp.

 pub struct CheckpointInfo {
-    /// block height of checkpoint
-    pub block_height: u64,
-    /// index of checkpoint
-    pub checkpoint_index: u32,
+    /// index of checkpoint (serialized first)
+    pub checkpoint_index: u32,
+    /// block height of checkpoint (serialized second)
+    pub block_height: u64,
 }

122-135: Consider a quick round‑trip test for Encode/Decode

Tiny ask: add a test asserting that encoding then decoding yields the same CheckpointInfo, and that the written size equals size_of::() + size_of::(). Helps catch future drift.

rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs (1)

210-219: Add a mismatch test to exercise the new indices

You set both indices to 56. Consider adding a case where local != canonical to prove we surface/propagate the right values end‑to‑end. Keeps the mud off future regressions.

rust/main/agents/validator/src/submit.rs (2)

198-221: Wording nit in warn; also OK to persist the new canonical after reorg

Message says “higher index” without checking it. Tweak copy to match the condition you check (lower block height).

-                    tracing::warn!(
+                    tracing::warn!(
                         ?latest_checkpoint,
                         ?latest_seen_checkpoint,
-                        "Receive a checkpoint with a higher index, but lower block height"
+                        "Received a checkpoint with lower block height than previously seen"
                     );

Persisting latest_seen_checkpoint here is reasonable (helps converge after a reorg), and the extra store error log is good.


331-365: validate_checkpoint logic reads right; consider documenting the None-height case

Rules cover higher/same index, lower index with old height, and lower index with same/new height (reorg). Consider a short doc comment noting: “If block_height is None, we accept the checkpoint.”

rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (1)

43-44: Version the storage key to future-proof migrations

Other keys use versioned suffixes (e.g., v2/v3). Do the same here to avoid schema ambiguity later.

-const LATEST_CHECKPOINT_INFO: &str = "latest_checkpoint_info";
+const LATEST_CHECKPOINT_INFO: &str = "latest_checkpoint_info_v1";

If data already exists in environments, gate this behind a migration toggle.

rust/main/agents/validator/src/submit/tests.rs (1)

583-635: Great coverage of validate_checkpoint scenarios

Higher/same/lower index cases behave as intended. One more edge case worth adding: when latest_checkpoint.block_height is None, validate_checkpoint should accept regardless of indices; add a test to lock this in.

Happy to draft that test if you want it in this PR.

Also applies to: 637-681, 683-731, 732-777

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61c7ac7 and 912595f.

📒 Files selected for processing (9)
  • rust/main/agents/relayer/src/msg/db_loader.rs (2 hunks)
  • rust/main/agents/relayer/src/msg/pending_message.rs (1 hunks)
  • rust/main/agents/validator/src/submit.rs (6 hunks)
  • rust/main/agents/validator/src/submit/tests.rs (10 hunks)
  • rust/main/hyperlane-base/src/db/mod.rs (2 hunks)
  • rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (3 hunks)
  • rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs (1 hunks)
  • rust/main/hyperlane-core/src/types/checkpoint.rs (2 hunks)
  • rust/main/hyperlane-core/src/types/reorg.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
rust/main/{hyperlane-core,hyperlane-base}/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Keep shared Rust core crates in rust/main/{hyperlane-core,hyperlane-base}

Files:

  • rust/main/hyperlane-core/src/types/checkpoint.rs
  • rust/main/hyperlane-base/src/db/mod.rs
  • rust/main/hyperlane-core/src/types/reorg.rs
  • rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs
  • rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs
rust/main/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Run cargo clippy for Rust code linting

Files:

  • rust/main/hyperlane-core/src/types/checkpoint.rs
  • rust/main/agents/relayer/src/msg/pending_message.rs
  • rust/main/hyperlane-base/src/db/mod.rs
  • rust/main/agents/validator/src/submit.rs
  • rust/main/hyperlane-core/src/types/reorg.rs
  • rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs
  • rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs
  • rust/main/agents/validator/src/submit/tests.rs
  • rust/main/agents/relayer/src/msg/db_loader.rs
rust/main/agents/relayer/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain relayer agent Rust sources under rust/main/agents/relayer

Files:

  • rust/main/agents/relayer/src/msg/pending_message.rs
  • rust/main/agents/relayer/src/msg/db_loader.rs
rust/main/agents/validator/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain validator agent Rust sources under rust/main/agents/validator

Files:

  • rust/main/agents/validator/src/submit.rs
  • rust/main/agents/validator/src/submit/tests.rs
🧬 Code graph analysis (8)
rust/main/hyperlane-core/src/types/checkpoint.rs (1)
rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs (1)
  • block_height (326-331)
rust/main/agents/relayer/src/msg/pending_message.rs (2)
rust/main/hyperlane-base/src/db/mod.rs (2)
  • store_latest_checkpoint_info (178-178)
  • retrieve_latest_checkpoint_info (180-180)
rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (2)
  • store_latest_checkpoint_info (703-705)
  • retrieve_latest_checkpoint_info (706-708)
rust/main/hyperlane-base/src/db/mod.rs (1)
rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (2)
  • store_latest_checkpoint_info (703-705)
  • retrieve_latest_checkpoint_info (706-708)
rust/main/agents/validator/src/submit.rs (3)
rust/main/chains/hyperlane-starknet/src/merkle_tree_hook.rs (3)
  • latest_checkpoint (67-91)
  • tree (95-130)
  • tree (107-111)
rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs (3)
  • latest_checkpoint (253-277)
  • tree (301-311)
  • block_height (326-331)
rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs (2)
  • latest_checkpoint (49-50)
  • tree (37-37)
rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs (1)
rust/main/hyperlane-core/src/chain.rs (1)
  • from_blocks (53-57)
rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (1)
rust/main/hyperlane-base/src/db/mod.rs (2)
  • store_latest_checkpoint_info (178-178)
  • retrieve_latest_checkpoint_info (180-180)
rust/main/agents/validator/src/submit/tests.rs (5)
rust/main/hyperlane-core/src/test_utils.rs (1)
  • dummy_domain (44-53)
rust/main/hyperlane-base/src/db/mod.rs (2)
  • store_latest_checkpoint_info (178-178)
  • retrieve_latest_checkpoint_info (180-180)
rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (3)
  • store_latest_checkpoint_info (703-705)
  • retrieve_latest_checkpoint_info (706-708)
  • new (74-76)
rust/main/agents/validator/src/submit.rs (3)
  • new (43-67)
  • new (602-616)
  • checkpoint_at_block (78-85)
rust/main/hyperlane-core/src/chain.rs (1)
  • from_blocks (53-57)
rust/main/agents/relayer/src/msg/db_loader.rs (2)
rust/main/hyperlane-base/src/db/mod.rs (2)
  • store_latest_checkpoint_info (178-178)
  • retrieve_latest_checkpoint_info (180-180)
rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (2)
  • store_latest_checkpoint_info (703-705)
  • retrieve_latest_checkpoint_info (706-708)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (53)
  • GitHub Check: infra-test
  • GitHub Check: cli-evm-e2e-matrix (warp-read)
  • GitHub Check: cli-evm-e2e-matrix (warp-extend-basic)
  • GitHub Check: cli-evm-e2e-matrix (core-init)
  • GitHub Check: cli-evm-e2e-matrix (warp-extend-recovery)
  • GitHub Check: cli-evm-e2e-matrix (warp-check-3)
  • GitHub Check: cli-evm-e2e-matrix (warp-init)
  • GitHub Check: cli-evm-e2e-matrix (warp-send)
  • GitHub Check: cli-evm-e2e-matrix (warp-apply-ism-updates)
  • GitHub Check: cli-evm-e2e-matrix (warp-rebalancer)
  • GitHub Check: cli-evm-e2e-matrix (warp-extend-config)
  • GitHub Check: cli-evm-e2e-matrix (warp-check-1)
  • GitHub Check: cli-evm-e2e-matrix (core-deploy)
  • GitHub Check: cli-evm-e2e-matrix (relay)
  • GitHub Check: cli-evm-e2e-matrix (warp-deploy)
  • GitHub Check: cli-evm-e2e-matrix (warp-bridge-2)
  • GitHub Check: cli-evm-e2e-matrix (warp-apply-2)
  • GitHub Check: cli-evm-e2e-matrix (warp-apply-1)
  • GitHub Check: cli-evm-e2e-matrix (warp-apply-submitters)
  • GitHub Check: cli-evm-e2e-matrix (warp-check-2)
  • GitHub Check: cli-evm-e2e-matrix (warp-bridge-1)
  • GitHub Check: cli-evm-e2e-matrix (core-read)
  • GitHub Check: cli-evm-e2e-matrix (core-apply)
  • GitHub Check: env-test-matrix (mainnet3, inevm, core)
  • GitHub Check: cli-evm-e2e-matrix (core-check)
  • GitHub Check: env-test-matrix (testnet4, sepolia, core)
  • GitHub Check: env-test-matrix (mainnet3, arbitrum, igp)
  • GitHub Check: env-test-matrix (mainnet3, optimism, igp)
  • GitHub Check: env-test-matrix (mainnet3, ethereum, igp)
  • GitHub Check: env-test-matrix (mainnet3, inevm, igp)
  • GitHub Check: env-test-matrix (mainnet3, optimism, core)
  • GitHub Check: env-test-matrix (mainnet3, arbitrum, core)
  • GitHub Check: cli-cosmos-e2e-matrix (warp-read)
  • GitHub Check: env-test-matrix (mainnet3, ethereum, core)
  • GitHub Check: cli-cosmos-e2e-matrix (core-read)
  • GitHub Check: cli-cosmos-e2e-matrix (core-apply)
  • GitHub Check: cli-cosmos-e2e-matrix (warp-deploy)
  • GitHub Check: cli-cosmos-e2e-matrix (core-deploy)
  • GitHub Check: cli-cosmos-e2e-matrix (core-check)
  • GitHub Check: cli-install-test-run
  • GitHub Check: cli-cross-chain-e2e-matrix (warp-apply)
  • GitHub Check: cli-cross-chain-e2e-matrix (warp-deploy)
  • GitHub Check: cosmos-sdk-e2e-run
  • GitHub Check: build-and-push-to-gcr
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: test-rs
  • GitHub Check: lint-rs
  • GitHub Check: lander-coverage
🔇 Additional comments (16)
rust/main/hyperlane-core/src/types/checkpoint.rs (1)

102-109: New CheckpointInfo type reads clean

Shape and visibility look good; defaults won’t bite. No blockers from me.

rust/main/agents/relayer/src/msg/db_loader.rs (2)

425-428: Import looks right

Pulling in CheckpointInfo here keeps tests in lockstep with the core type. All good.


740-742: Mock trait parity maintained

store_latest_checkpoint_info / retrieve_latest_checkpoint_info signatures match the base trait. Nice and simple.

rust/main/agents/relayer/src/msg/pending_message.rs (1)

1206-1208: Mocks up to date

Latest checkpoint_info methods added to the mock trait—keeps things consistent with the DB API. Ship it.

rust/main/hyperlane-base/src/db/mod.rs (2)

6-9: Import of CheckpointInfo is appropriate

Brings the right type into scope for the new API surface. No issues.


177-180: All implementors properly updated — breaking change successfully handled

The trait expansion for store_latest_checkpoint_info and retrieve_latest_checkpoint_info has been comprehensively addressed across the codebase. HyperlaneRocksDB contains concrete implementations (lines 703-707), and all three test mocks in validator and relayer modules have been adapted with the required trait method declarations. The methods are actively used in production code (validator/src/submit.rs).

rust/main/agents/validator/src/submit.rs (5)

19-21: New imports look good

Needed for the new validation/reporting helpers.


136-161: Guard is fine, but there’s a type mismatch downstream

Control flow reads well. Note: calls to tree_exceeds_checkpoint currently pass CheckpointAtBlock; the helper expects Checkpoint. See fix below.


299-307: Reorg panic helper usage is consistent

Correct canonical vs local args; good to crash after persisting details and reporting.


367-401: panic_with_reorg builds ReorgEvent correctly

Fields (local/canonical roots and indices) and timestamp look good.


403-414: Reporting path handles both height-aware and period-only chains

Looks fine; nice fallback when height isn’t available.

rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs (2)

8-12: Import of CheckpointInfo is correct

Required for the new persistence API.


703-708: Store/retrieve implementations are consistent with existing patterns

Using bool::default() as the singleton key matches highest_seen_message_nonce. Looks good.

rust/main/agents/validator/src/submit/tests.rs (3)

12-17: Mocks and imports updated for CheckpointInfo

Mock HyperlaneDb includes the new methods; imports are aligned.

Also applies to: 134-136


224-235: ReorgEvent assertions updated to new index fields

Good checks on canonical/local indices after the struct change.


243-357: End-to-end reorg path test remains solid

Nice coverage: reorg detected, event persisted, and reporter invoked at block height.

Comment on lines +122 to 127
let mut latest_seen_checkpoint = self
.db
.retrieve_latest_checkpoint_info()
.unwrap_or_default()
.unwrap_or_default();
loop {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swallow DB errors when loading latest_seen_checkpoint

Using unwrap_or_default twice hides real DB failures and can silently disable early reorg detection (we reset to 0/0 and accept lower indices as “fine”). Log and handle explicitly instead.

Apply:

-        let mut latest_seen_checkpoint = self
-            .db
-            .retrieve_latest_checkpoint_info()
-            .unwrap_or_default()
-            .unwrap_or_default();
+        let mut latest_seen_checkpoint = match self.db.retrieve_latest_checkpoint_info() {
+            Ok(Some(info)) => info,
+            Ok(None) => CheckpointInfo::default(),
+            Err(err) => {
+                tracing::error!(
+                    ?err,
+                    "Failed to retrieve latest checkpoint info; starting with defaults"
+                );
+                CheckpointInfo::default()
+            }
+        };
🤖 Prompt for AI Agents
In rust/main/agents/validator/src/submit.rs around lines 122 to 127, the double
unwrap_or_default on retrieve_latest_checkpoint_info() swallows DB errors and
silently resets latest_seen_checkpoint to defaults; replace this with explicit
error handling: match the Result/Option returned, log the DB error (including
the error details) and propagate or return an Err instead of defaulting to 0/0,
or if you must continue, use a clearly logged fallback branch that preserves
previous state; do not use unwrap_or_default twice—handle Result with ? or match
and handle Option explicitly so real DB failures are not ignored.

Comment on lines +13 to +16
/// the latest local checkpoint index
pub local_checkpoint_index: u32,
/// the latest onchain checkpoint index
pub canonical_checkpoint_index: u32,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fail‑closed or add backward‑compatible deserialization for reorg_flag.json

Old files won’t have the new fields. With derive(Deserialize), parsing the old shape fails, and build_and_validate logs then proceeds “assuming no reorg.” That can let a node start despite a previously flagged reorg. Not great in the swamp.

Two options (prefer 1):

  1. Backward‑compatible Deserialize: accept either the old single checkpoint_index or the new pair, mapping old -> both indices.
  2. Fail‑closed in the syncer: if the reorg file exists but can’t be parsed, return a fatal error instead of assuming no reorg.

Here’s a compact approach for (1), replacing the derive(Deserialize) and accepting both shapes:

-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Deserializer, Serialize};

 #[derive(Debug, Clone, Serialize, new, PartialEq, Default)]
 pub struct ReorgEvent {
   pub local_merkle_root: H256,
   pub canonical_merkle_root: H256,
   /// the latest local checkpoint index
   pub local_checkpoint_index: u32,
   /// the latest onchain checkpoint index
   pub canonical_checkpoint_index: u32,
   pub unix_timestamp: u64,
   pub reorg_period: ReorgPeriod,
 }

+#[derive(Deserialize)]
+#[serde(untagged)]
+enum ReorgEventCompat {
+    Old {
+        local_merkle_root: H256,
+        canonical_merkle_root: H256,
+        checkpoint_index: u32,
+        unix_timestamp: u64,
+        reorg_period: ReorgPeriod,
+    },
+    New {
+        local_merkle_root: H256,
+        canonical_merkle_root: H256,
+        local_checkpoint_index: u32,
+        canonical_checkpoint_index: u32,
+        unix_timestamp: u64,
+        reorg_period: ReorgPeriod,
+    },
+}
+
+impl<'de> Deserialize<'de> for ReorgEvent {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let v = ReorgEventCompat::deserialize(deserializer)?;
+        Ok(match v {
+            ReorgEventCompat::New {
+                local_merkle_root,
+                canonical_merkle_root,
+                local_checkpoint_index,
+                canonical_checkpoint_index,
+                unix_timestamp,
+                reorg_period,
+            } => ReorgEvent {
+                local_merkle_root,
+                canonical_merkle_root,
+                local_checkpoint_index,
+                canonical_checkpoint_index,
+                unix_timestamp,
+                reorg_period,
+            },
+            ReorgEventCompat::Old {
+                local_merkle_root,
+                canonical_merkle_root,
+                checkpoint_index,
+                unix_timestamp,
+                reorg_period,
+            } => ReorgEvent {
+                local_merkle_root,
+                canonical_merkle_root,
+                local_checkpoint_index: checkpoint_index,
+                canonical_checkpoint_index: checkpoint_index,
+                unix_timestamp,
+                reorg_period,
+            },
+        })
+    }
+}

If you’d rather do (2), flip build_and_validate to error if the status file exists but deserialization fails. I can sketch that too.

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

@kamiyaa kamiyaa closed this Nov 3, 2025
@github-project-automation github-project-automation Bot moved this from In Review to Done in Hyperlane Tasks Nov 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant