trufflesuite / ganache

:warning: The Truffle Suite is being sunset. For information on ongoing support, migration options and FAQs, visit the Consensys blog. Thank you for all the support over the years.
https://consensys.io/blog/consensys-announces-the-sunset-of-truffle-and-ganache-and-new-hardhat?utm_source=github&utm_medium=referral&utm_campaign=2023_Sep_truffle-sunset-2023_announcement_
MIT License
2.62k stars 678 forks source link

Ganache mines two different transactions with the same "from" and "nonce" #3794

Open olehmisar opened 2 years ago

olehmisar commented 2 years ago

Reproduction. You need to fork repl (top right corner) to see the console.

Ethereum does not allow two different transactions with the same from and nonce. But ganache does. In the repro, we can verify that it indeed confirmed those two transactions because the balance of the destination address is 3 (1 + 2).

I want simulate transaction replacement behaviour locally but I can't because of this bug.

jeffsmale90 commented 2 years ago

Thanks @olehmisar for raising this issue!

@MicaiahReid and myself have looked into the problem, and have identified that the issue is only reproducible when Ganache is running in the same node process as the caller (in this case Ethers).

We were unable to reproduce the issue with:

See PR #3498 that addresses similar transaction pool race conditions.

Here is a reproduction, against a locally running Ganache instance (with deterministic flag - ganache -d) that fails with the error the tx doesn't have the correct nonce. account has nonce of: 1 tx has nonce of: 0.

const ethers = require("ethers");

// this is the mnemonic generated by running Ganache in deterministic mode
// (ganache -d) and should only be used for development purposes.
// To run this reproduction, Ganache must be running locally (with this mnemonic)
const mnemonic =
  "myth like bonus scare over problem client lizard pioneer submit female collect";
const from = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
const to = "0x14974A7C722E2dbA813519C026ba2D75718bd82D";
const port = 8545;

async function main() {
  const provider = new ethers.providers.StaticJsonRpcProvider(
    `http://localhost:${port}`
  );
  const wallet = new ethers.Wallet.fromMnemonic(mnemonic).connect(provider);

  const tx1 = await wallet.sendTransaction({
    from,
    to,
    nonce: 0,
    value: 1,
  });

  const tx2 = await wallet.sendTransaction({
    from,
    to,
    nonce: 0,
    value: 2,
  });

  await Promise.all([tx1.wait(), tx2.wait()]);
  console.log("balance:", (await provider.getBalance(destination)).toString());

  console.log({
    tx1,
    tx2,
  });
}

main();
davidmurdoch commented 2 years ago

Looks like it is a duplicate of #2489. Let's keep this one open as well since it has good repro steps.

MicaiahReid commented 2 years ago

@davidmurdoch I think it's a slightly different issue. #2489 is when Ganache is generating the nonce for the user. This one is when the user is specifying the same nonce for two transactions and Ganache doesn't reject it. Regardless, I think the new queue we've implemented in #3498 will resolve this.