Skip to main content
Merkle Street

Protocols, markets, policy, people

When a Destination Chain Goes Down, Bound the Queue

When a destination chain stalls, a bounded, ordered message queue limits congestion while preserving retries, clear status and safe recovery for cross-chain users.

Merkle Street Newsroom#5478db3 min read

Abstract cover artwork

When a destination chain goes down, the app should cap the messages waiting for it and keep each accepted message traceable. A typical cross-chain transfer begins when a contract on the source chain emits a message. A verifier checks that event under the protocol’s security rules. A relayer then submits the verified message to a contract on the destination chain, which runs the requested action. If that chain stops producing blocks or its transaction endpoint becomes unavailable, submission or execution pauses. The source transaction may already be final, so the assets or instruction can remain in flight.

The waiting work may sit in a relayer’s database, in protocol contracts, or in both; there is no single queue design shared by every omnichain app. For a closer look at how omnichain apps move assets, see the separate explainer. Here, the key distinction is between a message that has not been delivered and one that reached the destination but failed during execution. The first needs delivery to resume. The second may need a retry after the cause, such as a contract revert, is fixed.

What happens to a cross-chain message during an outage?

The message waits at the stage that cannot proceed, and its status should say which stage that is. A relayer can detect the source event and record its identifier even when the destination is offline. It can retry later, but it should not report success until the destination contract has executed the message. A destination outage therefore creates delay, not an automatic cancellation.

Think of the queue as a waiting room with a fixed number of seats. If arrivals continue while service stops, the room fills; a larger room only postpones the limit. Each message needs a stable identifier, an ordering rule where order matters, and a recorded outcome. The app should also make clear whether the source action is final, pending, or failed. These details stop a delayed transfer from looking like a completed one.

How should an app bound its message queue?

Set limits on accepted backlog and on how quickly messages are submitted for execution. Apply limits per route or sender so one busy source cannot consume the whole capacity. A practical policy defines:

  • A maximum pending count or total payload size.
  • A submission rate that rises gradually after the destination recovers.
  • A maximum age that triggers review or a documented recovery path.
  • A visible outcome for new requests when the queue is full.

Where possible, reject or pause new requests before committing the source-side action. A sudden outage can still happen after a request is accepted, so the app needs a bounded backlog and a clear full-queue response. Do not silently drop accepted messages when a limit is reached. Keep them recorded, stop admitting more, and explain how users can check their status. If messages carry different amounts or execution costs, count those too; a message-count limit alone may not bound the work.

How can users and app teams recover safely?

Resume delivery in controlled batches, then watch destination execution and queue depth before increasing the rate. The app should retry messages using their original identifiers and make execution safe against duplicate submissions. If messages must run in order, a failed earlier message may hold back later ones on that route; if they are independent, separate lanes can prevent that blockage.

Users should check the message status before sending again. A source transaction that succeeded may still be waiting for destination execution, and a second request could repeat the intended action. The app should offer a support or recovery path for messages that age past their normal window, with the outcome stated plainly. The sound default is to preserve every accepted message, cap new arrivals, and resume only as fast as the destination can execute them.