Skip to main content
Back to Disclosures
MEDIUMSDX-2026-005 August 2026

Batch Timeout Race Condition in PeggyJV Gravity Bridge

Category: Web3 & Smart Contracts
Status: Remediated (GHSA-4vf2-m5pw-3r3r)

During an independent protocol architecture review, SDX Shadow Labs identified a subtle but high-impact state-synchronization race condition in the PeggyJV Gravity Bridge module, a prominent Cosmos-to-Ethereum cross-chain bridge. The vulnerability was located at the intersection of Cosmos-side batch timeout management and Ethereum event attestation.

This architectural flaw allowed cancelled bridge transactions to silently bypass state cleanup, artificially re-enter the unbatched transaction pool, and subsequently execute multiple times on the Ethereum mainnet-creating a direct path to bridge insolvency.

Disclosure Timeline: This vulnerability was discovered and reported by Dhruva (SDX Shadow Labs) via coordinated responsible disclosure in April 2026. Following confirmed successful remediation by the vendor, it has been published as an official GitHub Security Advisory (GHSA-4vf2-m5pw-3r3r) in August 2026.


1. Background: The Economics of Cross-Chain Batching

To optimize for steep Ethereum gas fees, Cosmos-to-Ethereum bridges group multiple user withdrawal requests into a single unified payload known as a Batch. Once a batch is formed and assigned a unique Nonce on the Cosmos side, decentralized relayers submit it to the Ethereum smart contract for final execution.

However, cross-chain state is asynchronous. If the Ethereum network experiences severe congestion, a batch may fail to execute within its intended block-height window. To prevent assets from being locked indefinitely, the bridge module enforces a Timeout mechanism, allowing stalled transactions to be safely cancelled and refunded on Cosmos.


2. The Vulnerability: Breaking State Synchronization

The vulnerability stems from a loss of synchronization between two distinct time-keeping mechanisms within the Cosmos module.

When a batch times out, the cleanupTimedOutBatchTxs (and cleanupTimedOutContractCallTxs) functions are responsible for securely cancelling the outgoing batch. The cancellation trigger evaluates whether the LastObservedEthereumBlockHeight has exceeded the batch's timeout threshold.

However, we discovered that this height metric was being advanced by two entirely independent code paths:

  1. Strict Event Observation: Updated via TryEventVoteRecord whenever a new Ethereum event is actually applied.
  2. Optimistic Validator Consensus: Updated via updateObservedEthereumHeight, driven by MsgEthereumHeightVote gossip.

Because Path 2 advances purely on validator consensus (independent of actual event attestation), the bridge's internal clock could cross the timeout threshold before the corresponding BatchExecutedEvent finished processing.

The Silent Failure

If a batch executed successfully on Ethereum, but the event relay back to Cosmos was momentarily delayed, the Cosmos BeginBlocker would erroneously declare the batch as "timed out." The module would then cancel the batch and return the underlying transactions to the unbatched pool. The subsequent arrival of the valid BatchExecutedEvent would trigger a silent failure-the batch record was already deleted, leaving the system unaware that the funds had actually moved on Ethereum.

(Note: The root cause traces back to an April 2022 feature addition-commit d7f6991-which introduced periodic height updates but failed to refactor the legacy timeout invariant.)


3. Exploitation & Double-Spend Mechanics

By engineering or capitalizing on this race condition, an attacker achieves a highly profitable double-spend scenario. Once the premature cancellation fires on Cosmos, the protocol forks into two exploitable paths:

  • Path A (The Ethereum Re-execution): The improperly refunded transactions are scooped up by relayers and bundled into a new batch with a higher nonce. The Ethereum Solidity contract's submitBatch function only checks the nonce and block timeout parameters, both of which the new batch passes. The contract pays out the identical recipients a second time, directly draining the bridge's ERC20 reserve.
  • Path B (The Cosmos-side Refund): Knowing the batch has already executed on Ethereum, the original sender can quickly call MsgCancelSendToEthereum on Cosmos. The protocol, believing the batch failed, unlocks or mints the original funds back to the user, allowing them to walk away with double the capital.

Exploitability Dynamics: A malicious validator controlling >1/3 of the network stake can deterministically trigger this by intentionally splitting their attestation votes to stall event nonces while pushing height votes forward. However, the flaw is also opportunistically exploitable without any validator stake. Any natural network congestion or honest stall in attestation creates a window where the cancellation fires, allowing senders to quietly extract value via Path B.


4. Remediation & Secure Architecture

The vendor responded swiftly via our responsible disclosure channels. The vulnerability is fully patched in version v6.1.0 (PeggyJV fork) through a robust architectural redesign:

  • Strict Monotonic Observation: The patch introduces a new, dedicated LastEventObservedEthereumBlockHeight store key. This key is written exclusively by TryEventVoteRecord at the exact moment an event is applied, tightly coupling time with state.
  • Refactored Invariants: The cleanup logic (cleanupTimedOutBatchTxs) was migrated to read this new event-driven key, completely eliminating the race condition caused by optimistic height consensus.
  • Migration Safety: To prevent chain halts or resurrected race windows, the state migration deliberately leaves the new key unset rather than unsafely seeding it from the legacy height tracker.

(Note: The Althea/Gravity-Bridge upstream fork was audited and confirmed to be unaffected by this vulnerability, as it never integrated the optimistic MsgEthereumHeightVote pattern.)