Skip to content

[MD-1520] Handle commitment record separately for v1 and v2 - #1473

Merged
ParthDesai merged 12 commits into
masterfrom
evgeny-commitment-recorder
Feb 3, 2026
Merged

[MD-1520] Handle commitment record separately for v1 and v2#1473
ParthDesai merged 12 commits into
masterfrom
evgeny-commitment-recorder

Conversation

@evgeny-s

Copy link
Copy Markdown

Description

This PR implements unified commitment root handling for Snowbridge outbound queues in the Dancelight runtime. The OutboundMessageCommitmentRecorder pallet now stores commitments from v1 and v2 queues separately and computes a Merkle tree root when both are present, ensuring the relayer receives a single, unified commitment for Ethereum smart contract validation. Changes include adding new storage for v2 commitments, updating the commitment logic, and adding comprehensive integration tests. The pallet version has been bumped to 1 to reflect the new storage addition.

@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds versioned (v1/v2) outbound message commitment recording: new RecordedCommitmentV2 storage and APIs, record_commitment_root_v2/prove_message_v2, merged Merkle-root computation when both versions exist, runtime/runtime-api wiring, tests, Cargo deps, and TypeScript query/type updates.

Changes

Cohort / File(s) Summary
Pallet implementation
pallets/outbound-message-commitment-recorder/src/lib.rs
Introduce RecordedCommitmentV2, require Config::Hashing, accept message_leaves in record calls, add record_commitment_root_v2, merged take_commitment_root behavior, Merkle helpers, and prove_message_v1/prove_message_v2.
Pallet manifest
pallets/outbound-message-commitment-recorder/Cargo.toml
Add snowbridge-merkle-tree dependency and enable its std feature.
Runtime bridge configs
chains/.../runtime/.../src/bridge_to_ethereum_config.rs (dancelight, starlight)
Add CommitmentRecorderV1/CommitmentRecorderV2 (dancelight); OnNewCommitment handlers now read MessageLeaves and call record_commitment_root/record_commitment_root_v2; set type Hashing = Keccak256.
Runtime APIs & wiring
chains/orchestrator-relays/runtime/dancelight/src/lib.rs, chains/orchestrator-relays/runtime/dancelight/Cargo.toml, Cargo.toml
Add snowbridge-outbound-queue-v2-runtime-api dep; route existing OutboundQueueApi::prove_message to v1 prover and add OutboundQueueV2Api::prove_message delegating to v2 prover.
Tests
chains/orchestrator-relays/runtime/dancelight/src/tests/commitment_recorder.rs, .../tests/mod.rs
Add extensive tests for v1, v2, merged-root behavior, proofs, empty cases, and emitted events; register new test module.
TypeScript API queries
typescript-api/src/dancelight/interfaces/augment-api-query.ts, typescript-api/src/starlight/interfaces/augment-api-query.ts
Change recordedCommitment shape to Option<ITuple<[H256, Vec<H256>]>> and add recordedCommitmentV2 with same shape.
TypeScript lookups / annotations
typescript-api/src/*/interfaces/lookup.ts, .../types-lookup.ts
Non-functional renumbering of @name/Lookup annotations and comment metadata across many files.

Sequence Diagram(s)

sequenceDiagram
    participant QueueV1 as OutboundQueueV1
    participant QueueV2 as OutboundQueueV2
    participant Recorder as OutboundMessageCommitmentRecorder
    participant StorageV1 as RecordedCommitmentV1
    participant StorageV2 as RecordedCommitmentV2
    participant Merkle as MerkleRootCalculator

    QueueV1->>Recorder: OnNewCommitment(commitment_v1)
    Recorder->>QueueV1: read message_leaves_v1
    Recorder->>StorageV1: record_commitment_root(commitment_v1, message_leaves_v1)
    Recorder-->>QueueV1: emit NewCommitmentRootRecorded (v1)

    QueueV2->>Recorder: OnNewCommitment(commitment_v2)
    Recorder->>QueueV2: read message_leaves_v2
    Recorder->>StorageV2: record_commitment_root_v2(commitment_v2, message_leaves_v2)
    Recorder-->>QueueV2: emit NewCommitmentRootRecorded (v2)

    Caller->>Recorder: take_commitment_root()
    Recorder->>StorageV1: read maybe_v1
    Recorder->>StorageV2: read maybe_v2
    alt both present
        Recorder->>Merkle: merkle_root(concat(maybe_v1.leaves, maybe_v2.leaves))
        Merkle-->>Recorder: unified_root
        Recorder->>StorageV1: clear
        Recorder->>StorageV2: clear
        Recorder-->>Caller: emit CommitmentRootRead(unified_root) / return unified_root
    else only v1
        Recorder->>StorageV1: clear
        Recorder-->>Caller: emit CommitmentRootRead(v1_root) / return v1_root
    else only v2
        Recorder->>StorageV2: clear
        Recorder-->>Caller: emit CommitmentRootRead(v2_root) / return v2_root
    else none
        Recorder-->>Caller: return None
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Agusrodri
  • tmpolaczyk
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: handling commitment records separately for v1 and v2 queues, which aligns directly with the core objective of the PR.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the unified commitment root handling, separate storage for v1 and v2, Merkle tree computation logic, and test additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 evgeny-commitment-recorder

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.

@evgeny-s

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

WASM runtime size check:

Compared to target branch

dancebox runtime: 1900 KB (no changes) ✅

flashbox runtime: 1124 KB (no changes) ✅

dancelight runtime: 2656 KB (no changes) 🚨

starlight runtime: 2580 KB (-4 KB) 🚨

container chain template simple runtime: 1516 KB (no changes) ✅

container chain template frontier runtime: 1848 KB (no changes) ✅

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
pallets/outbound-message-commitment-recorder/src/lib.rs (1)

34-86: Avoid merging commitments that may come from different blocks.

Line 63-86 merges v1/v2 whenever both are present, but the storage doesn’t track block numbers. If the relayer misses a block (or a pre-upgrade v1 value lingers after the STORAGE_VERSION bump on Line 34), you can merge commitments from different blocks and emit a root that never existed, which breaks Ethereum validation. Consider storing the block number with each commitment and only merging when they match (or clearing stale values on upgrade). This would require a storage migration.

💡 Possible direction (block-number guard)
-    pub type RecordedCommitment<T: Config> = StorageValue<_, H256, OptionQuery>;
+    pub type RecordedCommitment<T: Config> = StorageValue<_, (T::BlockNumber, H256), OptionQuery>;

-    pub type RecordedCommitmentV2<T: Config> = StorageValue<_, H256, OptionQuery>;
+    pub type RecordedCommitmentV2<T: Config> = StorageValue<_, (T::BlockNumber, H256), OptionQuery>;

     pub fn record_commitment_root(commitment: H256) {
-        RecordedCommitment::<T>::put(commitment);
+        let block = frame_system::Pallet::<T>::block_number();
+        RecordedCommitment::<T>::put((block, commitment));
         Pallet::<T>::deposit_event(Event::<T>::NewCommitmentRootRecorded { commitment });
     }

     pub fn record_commitment_root_v2(commitment: H256) {
-        RecordedCommitmentV2::<T>::put(commitment);
+        let block = frame_system::Pallet::<T>::block_number();
+        RecordedCommitmentV2::<T>::put((block, commitment));
         Pallet::<T>::deposit_event(Event::<T>::NewCommitmentRootRecorded { commitment });
     }

-            (Some(v1_commit), Some(v2_commit)) => {
+            (Some((b1, v1_commit)), Some((b2, v2_commit))) if b1 == b2 => {
                 // merge as before
             }
+            (Some((b1, v1_commit)), Some((b2, v2_commit))) => {
+                let commitment = if b1 >= b2 { v1_commit } else { v2_commit };
+                Pallet::<T>::deposit_event(Event::<T>::CommitmentRootRead { commitment });
+                Some(commitment)
+            }
🤖 Fix all issues with AI agents
In `@pallets/outbound-message-commitment-recorder/Cargo.toml`:
- Line 20: Add "snowbridge-merkle-tree/std" to the std feature list in this
crate's Cargo.toml so the workspace dependency is forwarded for
no-default-features builds; specifically, update the std features array that
currently lists other dependencies (the std feature block for this crate) to
include "snowbridge-merkle-tree/std" alongside the existing entries so std
builds can enable the merkle-tree's std feature like other pallets (e.g.,
pallet-external-validators-rewards).

Comment thread pallets/outbound-message-commitment-recorder/Cargo.toml
Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs Outdated
Comment thread chains/orchestrator-relays/runtime/dancelight/src/lib.rs
@evgeny-s
evgeny-s marked this pull request as ready for review January 21, 2026 11:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@pallets/outbound-message-commitment-recorder/src/lib.rs`:
- Around line 82-105: take_commitment_root currently consumes both
RecordedCommitment and RecordedCommitmentV2 (using .take()), which removes the
leaves needed by runtime proof endpoints like prove_message_* and causes proofs
to be unavailable after MMR construction; change take_commitment_root to
preserve leaves for proof generation by only removing/marking the commitment
roots while retaining the associated leaves (or move the extracted leaves into a
new storage like PendingProofLeaves / LastLeavesForProofs inside
take_commitment_root) and update references to
RecordedCommitment/RecordedCommitmentV2 and get_combined_leaves so proofs still
read leaves from the preserved storage until the next commitment replaces them.
- Around line 123-147: The code in prove_message_v2 and prove_message can
overflow or be out-of-bounds: replace the unchecked addition in prove_message_v2
(leaves.len() as u64 + leaf_index) with a checked_add and return None on
overflow; in prove_message, after building combined_leaves compute combined_len
safely (as u64 using try_from or checked conversion) and verify
combined_leaf_index < combined_len before calling merkle_proof, returning None
if out-of-bounds; update references to RecordedCommitment::prove_message_v2,
prove_message, get_combined_leaves and the merkle_proof invocation accordingly.

Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
@evgeny-s evgeny-s added A8-mergeoncegreen Pull request is reviewed well. B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes breaking Needs to be mentioned in breaking changes D5-nicetohaveaudit⚠️ PR contains trivial changes to logic that should be properly reviewed. labels Jan 21, 2026
Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
@evgeny-s

Copy link
Copy Markdown
Author

/cmd generate-ts-api

1 similar comment
@evgeny-s

Copy link
Copy Markdown
Author

/cmd generate-ts-api

@github-actions

github-actions Bot commented Jan 21, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

(master)

@@                      Coverage Diff                       @@
##           master   evgeny-commitment-recorder      +/-   ##
==============================================================
+ Coverage   73.84%                       73.91%   +0.07%     
+ Files         568                          569       +1     
+ Lines       82805                        83085     +280     
==============================================================
+ Hits        61141                        61410     +269     
+ Misses      21664                        21675      +11     
Files Changed Coverage
/chains/orchestrator-relays/runtime/dancelight/src/bridge_to_ethereum_config.rs 76.83% (-1.92%)
/chains/orchestrator-relays/runtime/dancelight/src/lib.rs 36.50% (-0.11%)
/chains/orchestrator-relays/runtime/starlight/src/bridge_to_ethereum_config.rs 25.00% (-1.67%)
/pallets/outbound-message-commitment-recorder/src/lib.rs 94.59% (+49.14%)

Coverage generated

Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs Outdated
Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs Outdated
@evgeny-s evgeny-s added R3-dancelight change affects dancelight runtime V-1800 labels Jan 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pallets/outbound-message-commitment-recorder/src/lib.rs`:
- Around line 119-135: prove_message_v1 and prove_message_v2 must first check
which RecordedCommitment variant is stored and return None when the requested
version isn't recorded; update both helpers to pattern-match
RecordedCommitment::<T>::get() against the expected variant
(RecordedCommitment::V1 vs RecordedCommitment::V2) before calling
Self::prove_message, and in prove_message_v2 only compute the combined index
(using the existing checked_add on leaves.len() and leaf_index) when the stored
commitment is the V2 variant—otherwise return None to avoid producing proofs for
the wrong version or out‑of‑range indices.
🧹 Nitpick comments (1)
pallets/outbound-message-commitment-recorder/src/lib.rs (1)

82-105: Document the ephemeral lifetime of recorded commitments.

take_commitment_root clears both v1/v2 storages; proof data is only available in the recording block. A short doc comment here (and/or on prove_message_v*) would prevent relayer misuse.

📝 Suggested doc clarification
-        pub fn take_commitment_root() -> Option<H256> {
+        /// Takes (and clears) recorded commitments for the current block.
+        /// Proofs must be queried in the same block via the runtime API.
+        pub fn take_commitment_root() -> Option<H256> {

Based on learnings, add explicit guidance about the per-block availability of proofs.

Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
@ParthDesai
ParthDesai force-pushed the evgeny-commitment-recorder branch from ae55c35 to 0627102 Compare January 30, 2026 14:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pallets/outbound-message-commitment-recorder/src/lib.rs`:
- Around line 28-41: The STORAGE_VERSION constant was not bumped after adding
new storage items; update the storage version by changing the declaration of
STORAGE_VERSION (const STORAGE_VERSION: StorageVersion = StorageVersion::new(0))
to the next version number (e.g. StorageVersion::new(1)) so the runtime
recognizes the schema change for this pallet.
🧹 Nitpick comments (1)
chains/orchestrator-relays/runtime/dancelight/src/lib.rs (1)

3143-3156: Document proof timing for relayers.
Since proof data is ephemeral, consider adding a brief doc/comment on these runtime APIs noting proofs are only available for the block where the commitment was recorded.

Based on learnings In the pallet-outbound-message-commitment-recorder pallet, commitment data (including leaves) is intentionally removed in the next block after being recorded via take_commitment_root. Relayers must call the prove_message runtime API at a specific block to obtain proofs before the commitment data is cleared. This ephemeral design is intentional for storage efficiency.

Comment thread pallets/outbound-message-commitment-recorder/src/lib.rs
@ParthDesai
ParthDesai force-pushed the evgeny-commitment-recorder branch from f350238 to ece6a22 Compare January 30, 2026 16:29
@ParthDesai
ParthDesai merged commit d9dca03 into master Feb 3, 2026
69 checks passed
@ParthDesai
ParthDesai deleted the evgeny-commitment-recorder branch February 3, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A8-mergeoncegreen Pull request is reviewed well. B7-runtimenoteworthy Changes should be noted in any runtime-upgrade release notes breaking Needs to be mentioned in breaking changes D5-nicetohaveaudit⚠️ PR contains trivial changes to logic that should be properly reviewed. R3-dancelight change affects dancelight runtime V-1800

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants