英文:
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('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);
// with small fraction of estimated amount of gas
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);
Here are the results:
- When
gasLimit
is 400,000,gasUsed
is 320,000- This is exactly 80% of the specified
gasLimit
,
indicating that HIP-185's gas over-reservation penalty *is likely to have kicked in. - See this answer to my previous related question for context.
- This is exactly 80% of the specified
- 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
- This is greater than 80% of the specified
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 && transaction.to && (!transaction.data || transaction.data === '0x')) {
const value = Number(transaction.value);
if (value > 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 'value' field in transaction param. Value must be greater than 0`);
} else {
return this.defaultGas;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论