DEV Community

Mack Schneider
Mack Schneider

Posted on

Why Omnichain Messages Need Application Ordering

Omnichain transactions need explicit ordering rules because independent blockchains do not share one clock or a single transaction queue. For an app coordinating state across chains, the useful distinction in omnichain vs multichain is that connected contracts still need to decide which messages may execute first; cross-chain messaging alone cannot set that policy.

Ordering Is Local to a Message Path

A message usually travels from a source contract to a destination contract, where it is verified and later executed. The source chain can assign messages a sequence number, or nonce, for that path; the destination can use it to detect a missing or repeated message.

That sequence does not create a total order across independent source chains. If Chain A and Chain B both send updates to Chain C, each path can have its own nonce 1, and the messages can arrive in either order. Block timestamps are not a reliable tie-breaker: chain clocks and finality differ, and two transactions on separate chains have no shared block order.

Keep these four cases distinct when deciding what your app requires:

  • One path, dependent commands: preserve the sender’s sequence when later commands rely on earlier ones.
  • Several paths, independent updates: allow either arrival order if applying both produces the same valid state.
  • Several paths, conflicting updates: define a rule such as an app-level sequence, a designated writer, or explicit conflict handling.
  • State-changing action with a prerequisite: verify the prerequisite on the destination before allowing the action to execute.

An OApp, or Omnichain Application, can implement its own ordering rules on top of message transport. Hyperlane is another example of cross-chain messaging infrastructure; the app still needs to decide how its receiver handles messages that arrive out of order.

Choose Ordering From the State Dependency

Order only the operations whose results depend on sequence. If two messages add separate deposits to a balance, addition is commutative: processing the deposits in either order gives the same result. If one message changes a price and the next trades against that price, swapping them changes the outcome, so the app must enforce a dependency.

Strict ordering has a real cost: a missing, delayed, or failing message can hold later messages behind it on that path. If each update is independent, a sequence check that rejects older updates may be enough. If commands must run one after another, queue them by nonce and accept that later work can wait for a gap to be filled or handled.

Trace One Operation From Send to State Change

Consider a lending app where Chain A updates a collateral factor and then sends a command to borrow using that factor on Chain C. The borrow must not execute against the previous value just because its message reaches C first.

  1. On A, assign both messages a monotonically increasing sequence for the A-to-C app channel.
  2. On C, verify each message’s origin and record verified messages without treating verification as execution.
  3. Before applying a message, compare its sequence with the next expected value for that channel.
  4. Apply the factor update, advance the expected value, then allow the borrow command to run.

If message 2 arrives first, C stores or defers it because message 1 is missing. Once message 1 is available and applied, message 2 can proceed. This protects the dependency, though it may add delay; include a recovery path for failed execution so one bad payload does not silently leave the channel stuck.

Use an App-Level Rule for Cross-Chain Conflicts

Now suppose both A and B can update the same collateral factor. Two valid per-path nonces still cannot tell C which update is newer or more authoritative. The app needs a shared rule: for example, allow updates only from one configured chain, require a common governance epoch, or reject any update whose version is not greater than the stored version.

For a task you need to ship now, write down the state each message changes, whether two such changes commute, and what the receiver should do with a duplicate, stale version, or missing predecessor. Enforce the smallest rule that preserves the invariant. Add global sequencing only when the business logic truly requires one total order across chains; it adds coordination and waiting to every operation that depends on it.

Top comments (0)