SolidusExplorer
⌘K
FINALITY
BLOCK HEIGHT
ACTIVE VALIDATORS

The Nonce That Stops a Replayed Transaction: Strict Equality, and a Failure That Costs Nothing

What the counter is for

A signed transaction is a reusable object. Anybody who sees it holds a valid signature over the same instruction, and nothing about the signature stops them submitting it a second time.

The nonce is what makes the second submission fail. Every account carries a counter, every transaction names the value it expects, and the chain compares them.

The rule is equality, and that is stronger than it sounds

The executor compares the account's counter to the transaction's, and requires them to be equal.

Not "greater than the last one used". Equal.

So transactions from one account are strictly sequential. You cannot skip ahead. Submit a transaction naming a value two beyond the account's current one and it does not wait for the gap to close: it fails on the spot.

If you are building something that submits more than one transaction, that is the property to design around, and it is easy to miss because the word "nonce" suggests a number that merely has to be new.

And a mismatch is a failure, not a rejection

The check produces a failed receipt. The transaction is processed, found wrong, and recorded as having failed.

The ordering matters and it is readable in one function: the comparison returns before the fee is deducted and before the counter is incremented.

So a replayed transaction costs the replayer nothing and moves the account's counter not at all. That is the correct behaviour for the account being replayed, because a stranger cannot burn your balance or desynchronise your counter by rebroadcasting something you signed.

The layer we did not open

Whether a wrong-nonce transaction reaches a block at all was not resolved.

What we did read: the submit method decodes the transaction and hands it to the backend without comparing nonces itself, and the mempool crate's only references to nonces are in test fixtures, with that crate's documented concern being availability rather than transaction validity.

What we did not read is the backend's own admission path, which the submit method allows to reject. So "a replay is free to submit" is a claim about the layers we opened, and somebody with the node's internals can close it in minutes.

We would rather leave that visible than round it up into a spam-vector claim we cannot support.

What the implementation gets right

The error names both numbers. A mismatch reports the value expected and the value received, which is the difference between a debuggable failure and a mystery.

The check runs before any state change, so a rejected transaction cannot leave the account partially modified.

And the counter is per account rather than global, so one account's activity never invalidates another's pending work.

What you can check yourself

Ask for an account's counter, with no account of your own:

solidus_getNonce with an address

Then read a block and look at the transactions it carries:

solidus_getBlock with a height

A counter that has moved tells you an account has sent transactions. It does not tell you they succeeded, because the failure path records rather than discards, which is exactly the distinction this page is about.

Keep reading

The Nonce That Stops a Replayed Transaction: Strict Equality, and a Failure That Costs Nothing · Solidus — Solidus Explorer