javascript - How to resolve intermittent "nonce has already been used" error when deploying multiple smart con

When I deploy 2 smart contracts at the same time to Hedera Testnet,I get the following error:nonce ha

When I deploy 2 smart contracts at the same time to Hedera Testnet, I get the following error:

nonce has already been used [ See:  ]

This occurs intermittently, roughly about 20% of the time. Their occurrence appears to be non-deterministic, although I am unable confirm this.

My deployment code is:

        const deploymentSigner: Signer = (await hre.ethers.getSigners())[0];
        // console.log('Deployment signer address', (await deploymentSigner.getAddress()));
        const scDeploymentPromises: Promise<Contract>[] = [];
        for (let idx = 0; idx < scNamesToDeploy.length; ++idx) {
            const scName = scNamesToDeploy[idx];
            const scFactory: ContractFactory =
                await hre.ethers.getContractFactory(scName);
            console.log(`Deploying ${scName} on ${networkName} ...`);
            const sc: Contract = await scFactory.deploy();
            // NOTE deployment with constructor params not needed for these particular SCs
            const scDeploymentPromise: Promise<Contract> = sc.deployed();
            scDeploymentPromises.push(scDeploymentPromise);
        }

        // NOTE collect deployment promises without `await`-ing them,
        // so as to be able to run them in parallel.
        const deployedScs: Contract[] = await Promise.all(scDeploymentPromises);
        deployedScs.forEach((sc, idx) => {
            const scName = scNamesToDeploy[idx];
            console.log(`Deployed ${scName} on ${networkName} at ${sc.address}`);
        });

Where scNamesToDeploy is an array of strings, initialised elsewhere, containing the smart contract names that I'd like to deploy.

Worth noting that if I change the code within the first loop to do

await sc.deployed()

... and therefore skip the 2nd loop, this error stops occurring. Therefore I think it has something to do with the 2nd deployment transaction occurring too quickly after the 1st one. But, technically, this should be possible, I should be able to submit both transactions within the same "block" even. ("block" is in quotes because Hedera does not have blocks, AFAICT.)


Details

Versions

I'm using hardhat ([email protected]) + ethers.js ([email protected] via @nomicfoundation/[email protected]).

Error details

This is the full error message:

nonce has already been used [ See:  ] (error={"name":"ProviderError","_stack":"ProviderError: [Request ID: 356c0cee-949f-4f52-b936-25bbba9ef603] Nonce too low\n    at HttpProvider.request (/Users/user/code/hedera/hedera-scratch/chain/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at async EthersProviderWrapper.send (/Users/user/code/hedera/hedera-scratch/chain/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)","code":32001,"_isProviderError":true}, method="sendTransaction", transaction=undefined, code=NONCE_EXPIRED, version=providers/5.7.2)\

Network config

This is from the networks section of hardhat.config.js:

    hederatestnet: {
      chainId: 296,
      url: '',
      // gasPrice: Math.floor(rskTestnetMinimumGasPrice * 1.1),
      gasMultiplier: hederaTestnetGasMultiplier,
      accounts,
    },
    hederatestnetrelay: {
      chainId: 296,
      url: 'http://localhost:7546',
      // gasPrice: Math.floor(rskTestnetMinimumGasPrice * 1.1),
      gasMultiplier: hederaTestnetGasMultiplier,
      accounts,
    },

Note that I'm using hederatestnetrelay which is a local instance of hedera-json-rpc-relay connected to the Hedera Testnet, as that seems to be less flaky than connecting to the public RPC endpoint directly, where I get different intermittent errors ("Unknown error invoking RPC").

When I deploy 2 smart contracts at the same time to Hedera Testnet, I get the following error:

nonce has already been used [ See: https://links.ethers/v5-errors-NONCE_EXPIRED ]

This occurs intermittently, roughly about 20% of the time. Their occurrence appears to be non-deterministic, although I am unable confirm this.

My deployment code is:

        const deploymentSigner: Signer = (await hre.ethers.getSigners())[0];
        // console.log('Deployment signer address', (await deploymentSigner.getAddress()));
        const scDeploymentPromises: Promise<Contract>[] = [];
        for (let idx = 0; idx < scNamesToDeploy.length; ++idx) {
            const scName = scNamesToDeploy[idx];
            const scFactory: ContractFactory =
                await hre.ethers.getContractFactory(scName);
            console.log(`Deploying ${scName} on ${networkName} ...`);
            const sc: Contract = await scFactory.deploy();
            // NOTE deployment with constructor params not needed for these particular SCs
            const scDeploymentPromise: Promise<Contract> = sc.deployed();
            scDeploymentPromises.push(scDeploymentPromise);
        }

        // NOTE collect deployment promises without `await`-ing them,
        // so as to be able to run them in parallel.
        const deployedScs: Contract[] = await Promise.all(scDeploymentPromises);
        deployedScs.forEach((sc, idx) => {
            const scName = scNamesToDeploy[idx];
            console.log(`Deployed ${scName} on ${networkName} at ${sc.address}`);
        });

Where scNamesToDeploy is an array of strings, initialised elsewhere, containing the smart contract names that I'd like to deploy.

Worth noting that if I change the code within the first loop to do

await sc.deployed()

... and therefore skip the 2nd loop, this error stops occurring. Therefore I think it has something to do with the 2nd deployment transaction occurring too quickly after the 1st one. But, technically, this should be possible, I should be able to submit both transactions within the same "block" even. ("block" is in quotes because Hedera does not have blocks, AFAICT.)


Details

Versions

I'm using hardhat ([email protected]) + ethers.js ([email protected] via @nomicfoundation/[email protected]).

Error details

This is the full error message:

nonce has already been used [ See: https://links.ethers/v5-errors-NONCE_EXPIRED ] (error={"name":"ProviderError","_stack":"ProviderError: [Request ID: 356c0cee-949f-4f52-b936-25bbba9ef603] Nonce too low\n    at HttpProvider.request (/Users/user/code/hedera/hedera-scratch/chain/node_modules/hardhat/src/internal/core/providers/http.ts:88:21)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at async EthersProviderWrapper.send (/Users/user/code/hedera/hedera-scratch/chain/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)","code":32001,"_isProviderError":true}, method="sendTransaction", transaction=undefined, code=NONCE_EXPIRED, version=providers/5.7.2)\

Network config

This is from the networks section of hardhat.config.js:

    hederatestnet: {
      chainId: 296,
      url: 'https://testnet.hashio.io/api',
      // gasPrice: Math.floor(rskTestnetMinimumGasPrice * 1.1),
      gasMultiplier: hederaTestnetGasMultiplier,
      accounts,
    },
    hederatestnetrelay: {
      chainId: 296,
      url: 'http://localhost:7546',
      // gasPrice: Math.floor(rskTestnetMinimumGasPrice * 1.1),
      gasMultiplier: hederaTestnetGasMultiplier,
      accounts,
    },

Note that I'm using hederatestnetrelay which is a local instance of hedera-json-rpc-relay connected to the Hedera Testnet, as that seems to be less flaky than connecting to the public RPC endpoint directly, where I get different intermittent errors ("Unknown error invoking RPC").

Share Improve this question edited Apr 30, 2023 at 12:44 bguiz asked Apr 26, 2023 at 9:41 bguizbguiz 28.4k49 gold badges163 silver badges253 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 5

The nonce has already been used error occurs when a transactions is submitted with a nonce value that is the same as or less that another transaction that it (the same EOA) has submitted in the previously.

The correct behaviour is for the EOA to increment the nonce value by 1 (or more) for each subsequent transaction. However, your seeing this error indicates that this isn't happening, and your own assessment that

Therefore I think it has something to do with the 2nd deployment transaction occurring too quickly after the 1st one.

is in the right direction. It seems that ethers.js is reusing the same nonce value in 2 transactions, simply because they are occurring more or less at the same time. This is possibly because it under the hood, ethers.js would need to use the eth_getTransactionCount RPC endpoint, and that goes over the network, and therefore it may retrieve a "stale" value, especially when making 2 requests milliseconds apart from each other.

try this out manually:

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x...your deployment EOA addres...","latest"],"id":1}'

one way to work around this would be to override the transaction parameters for your deployment transaction to manually specify the nonce:

    const deploymentSigner: Signer = (await hre.ethers.getSigners())[0];
    // console.log('Deployment signer address', (await deploymentSigner.getAddress()));
    const deploymentSignerInitialNonce = await deploymentSigner.getTransactionCount();
    const scDeploymentPromises: Promise<Contract>[] = [];
    for (let idx = 0; idx < scNamesToDeploy.length; ++idx) {
        // NOTE manually override the nonce value to solve for intermittent failures
        // that otherwise occur with concurrent deployment transactions.
        // This is intended to **only** work if the deployment account is **not** submitting
        // any other transactions to the network at the same time.
        const deploymentTxOverrides = {
            nonce: (deploymentSignerInitialNonce + idx),
        };
        // console.log('Deployment Tx Overrides', deploymentTxOverrides);
        const scName = scNamesToDeploy[idx];
        const scFactory: ContractFactory =
            await hre.ethers.getContractFactory(scName);
        console.log(`Deploying ${scName} on ${networkName} ...`);
        const sc: Contract = await scFactory.deploy(deploymentTxOverrides);
        // TODO deployment with constructor params (but not needed in this case)
        const scDeploymentPromise: Promise<Contract> = sc.deployed();
        scDeploymentPromises.push(scDeploymentPromise);
    }

    // NOTE collect deployment promises without `await`-ing them,
    // so as to be able to run them in parallel.
    const deployedScs: Contract[] = await Promise.all(scDeploymentPromises);
    deployedScs.forEach((sc, idx) => {
        const scName = scNamesToDeploy[idx];
        console.log(`Deployed ${scName} on ${networkName} at ${sc.address}`);
        deploymentCacheNetwork[scName] = sc.address;
    });

Specifically:

(1) await deploymentSigner.getTransactionCount(); manually retrieves eth_getTransactionCount for the EOA deploying the SCs

(2) { nonce: (deploymentSignerInitialNonce + idx) }; increments the nonce by 1 each run within the 1st for-loop

(3) await scFactory.deploy(deploymentTxOverrides); performs the deployment transaction with the manually specified nonce value

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745319379a4622371.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信