Thanks for posting this, Henri. I completely agree and want to continue the conversation by:
- adding a bit more color on why use nonces in general and which of those properties might be relaxed
- describing our use case
- describing a different potential solution (not mutually exclusive).
Why use nonces?
As I think about it, there are two reasons to use nonces: A. they prevent replay attacks, such that resending a payload that has already been sent will not result in a valid transaction. B. They guarantee ordering and completeness of transactions, i.e. sometimes we want to make sure that a second transaction we send can only be accepted if the previous transaction has already been included.
Our use case (at Empiric)
In our case at Empiric (oracle data feeds), we only need property A. but not property B. At Empiric, we have many data publishers that sign their own data and then publish it directly on-chain. This data is published at a high frequency and by many different entities each running on distributed, highly redundant systems.
We need the replay attack guarantee, because otherwise resending past valid data update transactions could lead to draining the funds from our data partners via recurring gas fees.
We do not however need the ordering and completeness guarantees, in fact we actively don’t want them. As Henri describes, it is difficult to ascertain the correct nonce to use given potentially pending transactions and the possibility that transactions sent a few seconds ago may or may not be valid. For instance, a transaction may seem valid locally, but fail because of insufficient gas if the fee changes from the estimate_fee call to posting the transaction (this has happened multiple times). Older data is automatically excluded by logic in our contracts, so we are not worried about old transactions being resent.
We currently use timestamps as nonces and simply check that the last stored nonce is less than the nonce of the new transaction being validated. This guarantees transaction uniqueness but allows many transactions to be sent simultaneously and only the most recent transaction will be included.
If I understand the proposed nonce validation by the sequencer properly, checking inequality rather than old_nonce = nonce + 1 would have identical complexity, and the former (proposed here) is strictly more flexible. Contracts that wanted the ordering and completeness guarantee could still check it (similar to the way account contracts check both that and uniqueness today).
PS: Henri’s multi-dimensional nonce structure would also work in theory, in that we could just use index as a timestamp and ignore the second dimension of the nonce. However it would be suboptimal in that we wouldn’t store one timestamp nonce but rather add a new storage slot at every update, which would be quite expensive over the long run.