Why can only OpenZeppelin handle a nonce mismatch due to using mainnet network ID on forked ganache?

So I was trying to develop a frontend for a contract against a local ganache instance that forks the mainnet. I would deploy the contract to the local network with npx oz deploy, and afterwards try to send a transaction from the first ganache account via the frontend. When sending this via MetaMask, with both web3 and ethers, I’d receive the error below:

inpage.js:1 MetaMask - RPC Error: Error: [ethjs-rpc] rpc error with payload {"id":1334711499506,"jsonrpc":"2.0","params":["0xf9024b0184773594008401312d0094e78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab80b901e4bade9ea300000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c10000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000043800000000000000000000000000000000000000000000000000000000000000023000000000000000000000000000000000000000000000000000000000000002300000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000954657374204e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e416e64206865726520697320612074657374206465736372697074696f6e000025a097a7034ebdf601a24d6da1024e9d908105b11d0e26d83860795bece1bcf5c812a050975ab8663713ad8f3cfe3f84b8194878f036b46d46f1b88b8a448248271424"],"method":"eth_sendRawTransaction"} [object Object]

My next step was to try to reproduce this from a script without the frontend. I again deployed using npx oz deploy, then sent the transaction with both ethers and web3. These tests returned a different error:

UnhandledPromiseRejectionWarning: Error: exceeds block gas limit

However, given that the ganache block gas limit and transaction limit were set to 100,000,000, this error message is incorrect.

I then tried sending the same transaction from the command line using npx oz send-tx, and to my surprise this succeeded. To recap:

  • Transaction fails when sent via ethers and web3 through a node.js script with the error UnhandledPromiseRejectionWarning: Error: exceeds block gas limit
  • Transaction fails when sent via ethers and web3 through the frontend with the error RPC Error: Error: [ethjs-rpc] rpc error with payload
  • Transaction is successfully sent when sent via OpenZeppelin CLI tool with npx oz send-tx

It turns out the problem was this: Because the account I was using (which was just the default first account generated from the ganache deterministic mnemonic) had transactions on the mainnet there was a nonce mismatch. The issue and solution are described in this GitHub comment:

Account history is tied to network ID. So, when you switch to a private network with a different network ID, you will get a fresh account with the correct nonce. However, if you switch to a private network with the same network ID, the account history is the same and the nonce mismatch will occur.

So one workaround for right now is to ensure that the various private networks you interact with have different network IDs. However, it would still be really nice to have the option to delete the account’s history from the UI, forcing the correct nonce calculation.

Once realizing that was the problem, it was easy to resolve this by starting ganache with a different network ID. But I’m interested to learn more about the underlying issue and have some questions:

  1. Why do the script and frontend generate different, but not helpful, error messages? Why can’t they detect that the submitted nonce does not match the expected none and display a more appropriate error message?

  2. Why was npx oz send-tx not affected by this issue? It must be doing something differently, but what exactly?

3 Likes

This is a cool find. I don’t know why it’s working like this actually.

1 Like