UMAprotocol / UMAprotocol/protocol

fx-tunnel-relayer: only the first MessageSent event per Polygon transaction is relayed

Open
#4,962 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
485
Forks
215
Avg merge
18m
Merged PRs (30d)
1

Description

Bug

When a single Polygon transaction emits multiple MessageSent events from the OracleChildTunnel (e.g. two OO disputes batched in one transaction), only the first message is ever relayed to the OracleRootTunnel on mainnet. The remaining messages are never received, so those price requests never reach the DVM and the corresponding OO requests stay stuck in Disputed with bonds locked.

Root cause

Relayer._relayMessage builds the exit proof with:

proof = await this.maticPosClient.exitUtil.buildPayloadForExit(
  transactionHash,
  POLYGON_MESSAGE_SENT_EVENT_SIG,
  false
);

matic.js buildPayloadForExit(burnTxHash, logEventSig, isFast, index = 0) proves the index-th log matching the event signature in the transaction, defaulting to the first. The relayer loops over all fetched MessageSent events but never passes an index, so every event in the same transaction produces an identical proof for message #1. The first submission succeeds; subsequent ones revert with FxRootTunnel: EXIT_ALREADY_PROCESSED, which the catch block swallows silently — so the failure is invisible in logs.

Real-world impact (2026-07)

Two Polymarket dispute transactions on Polygon each bridged two disputes atomically; in both cases the second dispute never reached the DVM:

  • 0x0874d7b0… — relayed: Will "Blue" be said…; stuck: Will "Honestly" be said…
  • 0xc6d74727… — relayed: Map Handicap: paiN (-1.5)…; stuck: Map 3 Rounds Handicap: paiN (-3.5)…

Both stuck messages have since been relayed manually with proofs built at tokenIndex=1 (0x37ba42b6…, 0xeaf7b45c…), confirming both the diagnosis and that recovery is permissionless.

Fix

For each MessageSent event, compute its position among all logs matching the MessageSent signature in its transaction receipt and pass that index to buildPayloadForExit. Also log (debug) instead of silently returning when a relay reverts with EXIT_ALREADY_PROCESSED.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at Relayer._relayMessage and inspect the maticPosClient.exitUtil.buildPayloadForExit call, then trace the fetched MessageSent events to their transaction receipts. Use the documented Polygon transactions to verify that each matching log is handled independently and that EXIT_ALREADY_PROCESSED failures are debug-logged rather than silently swallowed.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
backend, blockchain
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.