Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple transactions in one block #4090

Closed
graygt opened this issue Jun 30, 2023 · 3 comments
Closed

Multiple transactions in one block #4090

graygt opened this issue Jun 30, 2023 · 3 comments

Comments

@graygt
Copy link

graygt commented Jun 30, 2023

Version of Hardhat

2.16.0

What happened?

There is a problem in packing multiple transaction in one block. There only one transaction in mempool. To mine more than one tx, "evm_mine" should be called multiple times. There is no way to pack two txs to one block. I will just provide a code that shows pending block, compared to v2.14.0 where it works correctly.

Hardhat v2.16.0, ethers v6.6.0

import { ethers } from "hardhat";

async function main() {
    await ethers.provider.send("evm_setAutomine", [false]);
    await ethers.provider.send("evm_setIntervalMining", [0]);

    const owner = await ethers.provider.getSigner(0);
    const ownerAddress = await owner.getAddress();

    const tx1 = {
        to: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
        value: ethers.parseEther("1.0"),
        type: 0,
    }

    const tx2 = {
        to: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
        value: ethers.parseEther("1.0"),
        type: 0,
    }

    await owner.sendTransaction(tx1);
    await owner.sendTransaction(tx2);

    const pendingTx = await ethers.provider.send("eth_pendingTransactions");
    console.log("pendingTx", pendingTx.map((tx) => tx.hash)); // this gives two pending transactions

    const pendingBlock = await ethers.provider.send("eth_getBlockByNumber", ["pending", false,]);
    console.log(pendingBlock); // gives one pending tx

    // mine a block
    await ethers.provider.send("evm_mine", []);
}

main();

Result:

{
  number: null,
  hash: null,
  parentHash: '0x640d62a47df354b4f3353f136ede741e44b89c6c97dccbd8e30599ace38d3d6f',
  nonce: null,
  mixHash: null,
  sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  transactionsRoot: '0x90d1a983ff6dbc54851fb16b15cb14ed17632e0f8e37ad49c88bea7994454be0',
  stateRoot: '0x4fd2cddd334dab1c4005161c290f25a0e18d4175ecfa898b17095d8ec2dd344a',
  receiptsRoot: '0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa',
  miner: '0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e',
  difficulty: '0x0',
  totalDifficulty: '0x37f4405',
  extraData: '0x',
  size: '0x2a6',
  gasLimit: '0x1c9c380',
  gasUsed: '0x5208',
  timestamp: '0x649d72ad',
  transactions: [
    '0xe06e207f5605ec8efc8d1d08612d36cd59096530dcf479d0b8d6a5f2de188e0c' // ONLY ONE TX
  ],
  uncles: [],
  baseFeePerGas: '0x3b9aca00',
  withdrawals: [],
  withdrawalsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' 
}

Comparison to Hardhat v2.14.0, ethers v5.7.2 where it works correctly.

import { ethers } from "hardhat";

async function main() {
    await ethers.provider.send("evm_setAutomine", [false]);
    await ethers.provider.send("evm_setIntervalMining", [0]);

    const owner = await ethers.provider.getSigner(0);
    const ownerAddress = await owner.getAddress();

    const tx1 = {
        to: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
        value: ethers.utils.parseEther("1.0"),
        type: 0,
    }

    const tx2 = {
        to: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
        value: ethers.utils.parseEther("1.0"),
        type: 0,
    }

    await owner.sendTransaction(tx1);
    await owner.sendTransaction(tx2);

    const pendingBlock = await ethers.provider.send("eth_getBlockByNumber", ["pending", false,]);
    console.log(pendingBlock);

    // mine a block
    await ethers.provider.send("evm_mine", []);
}

main();

Result:

{
  number: null,
  hash: null,
  parentHash: '0x640d62a47df354b4f3353f136ede741e44b89c6c97dccbd8e30599ace38d3d6f',
  nonce: null,
  mixHash: null,
  sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  transactionsRoot: '0x69b19d8ad9952927647f017952633f1027cdc1f5ce9883e54c8f6d907945fd8b',
  stateRoot: '0x49c756d15bbf811f532811dba19f5fda9df678bcdd4017024ef4daded412af7d',
  receiptsRoot: '0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1',
  miner: '0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e',
  difficulty: '0x0',
  totalDifficulty: '0x37f4405',
  extraData: '0x',
  size: '0x31d',
  gasLimit: '0x1c9c380',
  gasUsed: '0xa410',
  timestamp: '0x649d72ad',
  transactions: [
    '0x4f3c84dfcd839177d439a450a2dcf958647d5287cb49ed19022803ba31583124', // FIRST TX
    '0x9b4d72e2fdae47a00e841642567e7fd40e064863d6cfbb84890f8a81cb45bf7e' // SECOND TX
  ],
  uncles: [],
  baseFeePerGas: '0x3b9aca00',
  withdrawals: [],
  withdrawalsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'
}

Minimal reproduction steps

Just check the code above. It's reproducible.

Search terms

No response

@fvictorio
Copy link
Member

Hi @graygt, can you try setting this in your config?

networks: {
  hardhat: {
    gas: "auto"

@graygt
Copy link
Author

graygt commented Jul 3, 2023

@fvictorio Thanks! It works!

@graygt graygt closed this as completed Jul 3, 2023
@fvictorio
Copy link
Member

Glad to hear that! I'll explain what's goin on here just in case:

Normally when you send a transaction a gas estimation is performed first. The way gas is estimated is that the same transaction is executed multiple times with different gas limits until a correct value is found (doing a binary search). This is also what geth does under the hood.

The problem is that doing that for each transaction is kind of slow. So, when you are using the Hardhat network, we don't estimate gas by default. Instead, we just use the full block gas limit as the estimation. In previous versions of hardhat-ethers we would only do this for the in-process Hardhat network, but in the latest major version we also do it when you connect via --network localhost to a Hardhat node.

The problem with using the full block gas limit as an estimation though is that, if there are multiple transactions in the mempool and you are mining a block, only one transaction will be mined. This happens because, when the second transaction is added, the algorithm is something like this:

  1. I have (blockGasLimit - currentlyExecutedGas) gas left
  2. The next transaction can use up to nextTx.gasLimit
  3. If that value is bigger than the gas I have left in this block, then I won't include it. Since all txs use blockGasLimit as their gas limit, this is always the case and no second tx is included.

You could argue that we should run that transaction anyway and include it if the gas that it actually uses fits the remaining gas in the block. But we are following what geth does here (IIRC it works this way to prevent a DoS vector)

So, when you set "gas": "auto" you are basically avoiding the "use the whole block gas limit as the gas limit for every tx" optimization and actually running the gas estimations, which has a performance impact but it's more realistic.

Hope that helps, happy to answer any questions about this.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Archived in project
Development

No branches or pull requests

2 participants