为什么在Hedera上使用`eth_estimateGas`返回一个意外高的值?

huangapple go评论66阅读模式
英文:

Why does `eth_estimateGas` on Hedera return an unexpectedly high value?

问题

我知道eth_estimateGas不是精确值,但目前我得到的实际gasUsed值约为eth_estimateGas返回值的6%。

在下面的示例中,我两次使用完全相同的输入调用相同的智能合约,唯一的区别是:

  • 在第一次调用中,gasLimit = eth_estimateGas
  • 在第二次调用中,gasLimit = eth_estimateGas * 0.064
    • 这个值是估算值的一个非常小的分数
    • 这个值是通过反复试验得到的...而不是通过任何计算得出的
    // 使用精确的估计气体量
    const estimatedGas2 = (await expendGasSc.estimateGas.updateState(1_000_000_123n)).toBigInt();
    console.log('estimatedGas2', estimatedGas2);
    const gasLimit2 = estimatedGas2 * 1n;
    console.log('gasLimit2', gasLimit2);
    const txResponse2 = await (await expendGasSc
        .updateState(
            1_000_000_123n,
            { gasLimit: gasLimit2 },
        ))
        .wait();
    const gasUsed2 = txResponse2.gasUsed.toBigInt();
    console.log('gasUsed2', gasUsed2);

    // 使用估算气体量的小部分
    const estimatedGas4 = (await expendGasSc.estimateGas.updateState(1_000_000_123n)).toBigInt();
    console.log('estimatedGas4', estimatedGas4);
    const gasLimit4 = estimatedGas4 * 64n / 1000n; // <--- 6.4%
    console.log('gasLimit4', gasLimit4);
    const txResponse4 = await (await expendGasSc
        .updateState(
            1_000_000_123n,
            { gasLimit: gasLimit4 },
        ))
        .wait();
    console.log('txResponse4', txResponse4);
    const gasUsed4 = txResponse4.gasUsed.toBigInt();
    console.log('gasUsed4', gasUsed4);

以下是结果:

  • gasLimit为400,000时,gasUsed为320,000
    • 这正好是指定的gasLimit的80%,表明HIP-185的气体过度保留罚款可能已经生效。
    • 有关背景信息,请参见我以前相关问题的答案
  • gasLimit为25,600时,gasUsed为23,816
    • 这大于指定的gasLimit的80%,表明HIP-185的气体过度保留罚款尚未生效
estimatedGas2 400000n
gasLimit2 400000n
gasUsed2 320000n
estimatedGas4 400000n
gasLimit4 25600n
gasUsed4 23816n

因此,我期望eth_estimateGas返回的值要比400,000接近23,816。为什么它返回比实际预期的要高得多的估算值呢?

这是智能合约的代码:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

contract ExpendSomeGasDemo {
    uint256 public state;

    function updateState(
        uint256 newState
    )
        public
        returns (uint256 updatedState)
    {
        state = newState;
        updatedState = newState;
    }
}

请注意,该合约已部署在Hedera Testnet上:
0x9C58D0159495F7a8853A24574f2B8F348a72424c

请注意,上述JavaScript示例正在使用ethers.js。

请注意,此问题是我以前问题的后续问题:
在Hedera上几乎相同的交易中gasUsed值的巨大差异 - 为什么?

英文:

I am aware that eth_estimateGas is not intended to be exact,
but currently, I'm getting actual gasUsed values
that are approximately 6% of the value returned by eth_estimateGas.

In the following example,
I invoke the same smart contract with the exact same inputs twice, with only 1 difference:

  • In the 1st invocation, gasLimit = eth_estimateGas
  • In the 2nd invocation, gasLimit = eth_estimateGas * 0.064
    • This value is a very small fraction of the estimate
    • This value was obtained through trial-and-error ... not through any calculations
    // with exact estimated amount of gas
    const estimatedGas2 = (await expendGasSc.estimateGas.updateState(1_000_000_123n)).toBigInt();
    console.log(&#39;estimatedGas2&#39;, estimatedGas2);
    const gasLimit2 = estimatedGas2 * 1n;
    console.log(&#39;gasLimit2&#39;, gasLimit2);
    const txResponse2 = await (await expendGasSc
        .updateState(
            1_000_000_123n,
            { gasLimit: gasLimit2 },
        ))
        .wait();
    const gasUsed2 = txResponse2.gasUsed.toBigInt();
    console.log(&#39;gasUsed2&#39;, gasUsed2);

    // with small fraction of estimated amount of gas
    const estimatedGas4 = (await expendGasSc.estimateGas.updateState(1_000_000_123n)).toBigInt();
    console.log(&#39;estimatedGas4&#39;, estimatedGas4);
    const gasLimit4 = estimatedGas4 * 64n / 1000n; // &lt;--- &#128680;&#128680;&#128680; 6.4% &#128680;&#128680;&#128680;
    console.log(&#39;gasLimit4&#39;, gasLimit4);
    const txResponse4 = await (await expendGasSc
        .updateState(
            1_000_000_123n,
            { gasLimit: gasLimit4 },
        ))
        .wait();
    console.log(&#39;txResponse4&#39;, txResponse4);
    const gasUsed4 = txResponse4.gasUsed.toBigInt();
    console.log(&#39;gasUsed4&#39;, gasUsed4);

Here are the results:

  • When gasLimit is 400,000, gasUsed is 320,000
  • When gasLimit is 25,600, gasUsed is 23,816
    • This is greater than 80% of the specified gasLimit,
      indicating that HIP-185's gas over-reservation penalty has not kicked in
estimatedGas2 400000n
gasLimit2 400000n
gasUsed2 320000n
estimatedGas4 400000n
gasLimit4 25600n
gasUsed4 23816n

Therefore, I am expecting eth_estimateGas to return a value
that is much closer to 23,816 than 400,000.
Why is it returning such an unexpectedly high estimate
compared to the actual?


Here's the smart contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.18;

contract ExpendSomeGasDemo {
    uint256 public state;

    function updateState(
        uint256 newState
    )
        public
        returns (uint256 updatedState)
    {
        state = newState;
        updatedState = newState;
    }
}

Note that this contract is deployed on Hedera Testnet:
0x9C58D0159495F7a8853A24574f2B8F348a72424c

Note that the Javascript example above is using ethers.js.

Note that this question is a follow up to my previous one:
Large discrepancy in gasUsed values in near-identical transactions on Hedera - why?

答案1

得分: 6

You're querying eth_estimateGas via the Hedera JSON-RPC relay, which currently uses static values in lieu of actual gas estimation, this is what's responsible for the consistent estimations.

Note, that this will soon be replaced with an actual gas estimation algorithm when the implementation of HIP-584: Mirror EVM Archive Node is released. Specifically, you can see packages/relay/src/lib/constants.ts to see the gas-cost-related values defined as constants:

{
    // ...
    TX_BASE_COST: 21,000,
    TX_HOLLOW_ACCOUNT_CREATION_GAS: 587,000,
    TX_DEFAULT_GAS_DEFAULT: 400,000,
    TX_CREATE_EXTRA: 32,000,
    TX_DATA_ZERO_COST: 4,
    // ...
}

These are used by the estimateGas function in packages/relay/src/lib/eth.ts in their return values.

Note that no actual gas estimation algorithm is being run at the moment. This line - return this.defaultGas;, which maps to TX_DEFAULT_GAS_DEFAULT above - is also why you will get the same value of 400,000 anytime you perform eth_estimateGas for a smart contract invocation. Here's the code snippet below:

async estimateGas(transaction: any, _blockParam: string | null, requestId?: string) {
    // ...
}
英文:

You're querying eth_estimateGas via the Hedera JSON-RPC relay,
which currently uses static values in lieu of actual gas estimation, this is what's responsible for the consistent estimations.

Note, that this will soon be replaced with an actual gas estimation algorithm,
when the implementation
of HIP-584: Mirror EVM Archive Node is released.

Specifically, you can see packages/relay/src/lib/constants.ts,
to see the the gas-cost-related values defined as constants:

{
    // ...
    TX_BASE_COST: 21_000,
    TX_HOLLOW_ACCOUNT_CREATION_GAS: 587_000,
    TX_DEFAULT_GAS_DEFAULT: 400_000,
    TX_CREATE_EXTRA: 32_000,
    TX_DATA_ZERO_COST: 4,
    // ...
}


These are used by the estimateGas function in packages/relay/src/lib/eth.ts
in their return values.

Note, that no actual gas estimation algorithm is being run at the moment.
This line - return this.defaultGas;, which maps to TX_DEFAULT_GAS_DEFAULT above -
is also why you will get the same value of 400,000
anytime you perform eth_estimateGas for a smart contract invocation.
Here's the code snippet below

  async estimateGas(transaction: any, _blockParam: string | null, requestId?: string) {
    const requestIdPrefix = formatRequestIdMessage(requestId);
    this.logger.trace(`${requestIdPrefix} estimateGas(transaction=${JSON.stringify(transaction)}, _blockParam=${_blockParam})`);
    // this checks whether this is a transfer transaction and not a contract function execution
    if (transaction &amp;&amp; transaction.to &amp;&amp; (!transaction.data || transaction.data === &#39;0x&#39;)) {
      const value = Number(transaction.value);
      if (value &gt; 0) {
        const accountCacheKey = `${constants.CACHE_KEY.ACCOUNT}_${transaction.to}`;
        let toAccount: object | null = this.cache.get(accountCacheKey);
        if (!toAccount) {
          toAccount = await this.mirrorNodeClient.getAccount(transaction.to, requestId);
        }

        // when account exists return default base gas, otherwise return the minimum amount of gas to create an account entity
        if (toAccount) {
          this.logger.trace(`${requestIdPrefix} caching ${accountCacheKey}:${JSON.stringify(toAccount)} for ${constants.CACHE_TTL.ONE_HOUR} ms`);
          this.cache.set(accountCacheKey, toAccount);

          return EthImpl.gasTxBaseCost;
        }

        return EthImpl.gasTxHollowAccountCreation;
      }

      return predefined.INVALID_PARAMETER(0, `Invalid &#39;value&#39; field in transaction param. Value must be greater than 0`);
    } else {
      return this.defaultGas;
    }
  }

huangapple
  • 本文由 发表于 2023年5月15日 14:17:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76251324.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定