HIP-859: Add support for gasConsumed field in REST APIs related to contract results
Author | Ivan Kavaldzhiev, Stoyan Panayotov |
---|---|
Working Group | Steven Sheehy, Nana Essilfie-Conduah, Danno Ferrin, David Bakin |
Requested By | Supra Oracle |
Discussions-To | https://github.com/hashgraph/hedera-improvement-proposal/discussions/860 |
Status | Final ⓘ |
Needs Council Approval | Yes ⓘ |
Review period ends ⓘ | Thu, 08 Feb 2024 07:00:00 +0000 |
Type | Standards Track ⓘ |
Category | Mirror ⓘ |
Created | 2024-01-17 |
Updated | 2024-05-14 |
Release | v0.100.0 |
Table of Contents
Abstract
Currently, consensus nodes export a gasUsed
field for each EVM impacting transaction (ContractCall, ContractCreate and EthereumTransaction) and the mirror node persists and exposes this as gas_used
on the contract result REST APIs. This field value is calculated using the gas refund policy applied in the consensus node.
This means that the value in this field represents how much gas the user was actually charged, not the amount of gas that the EVM consumed during the execution. This value can greatly differ from the actual amount that was used by the EVM.
Let’s say we use a 20% refund policy for the gasLimit
that is sent by the user.
That means if the EVM consumes for example 55,500 gas, but the user is passing 400,000 gas limit, they will be charged the max(55,500, 0.8 * 400000)
, so it would be 320,000. That is the value calculated after we return the 20% of the gasLimit back to the user, which is 80,000.
The logic compares the bigger of the two values - the gas consumed by the EVM and the 80% of the gasLimit
that the user has passed.
In this way we lose track of the actual gas that was consumed by the EVM.
This HIP will add a new field gas_consumed
to the mirror node contract result REST APIs, which will contain the actually consumed gas by the EVM.
Motivation
Adding a gas_consumed
field to the contract result REST APIs would enrich user experience by providing more information about the transaction.
This would allow users to better understand the actual gas consumption of their transactions and the gas refund policy. This would also allow users to understand how heavy their executed transaction was for the EVM.
Rationale
Consensus nodes export ContractActions sidecar records for each transaction after the introduction of HIP-513. Each sidecar record contains a gasUsed
field for the given action that the record represents (different type of call or contract creation).
This value directly represents the gas consumed by the EVM, without taking into account the gas refund policy. This makes it possible to calculate the overall gas consumption for the transaction, during the ContractActions sidecar record importing.
If a transaction has multiple nested operations, it would have sidecar records for all of them, including a record for the top-level transaction. It’s important to note that the gasUsed
field in the top-level transaction sidecar record would represent the gas consumed by the EVM for the whole transaction, including all nested operations.
If we sum up the gas_used
value of the top-level sidecar record on top of the initial intrinsic gas, which is the initial gas charged prior to the transaction execution, then we would get accurate value for the gasConsumed
field.
User stories
- As a user, I want to see how much gas was consumed by the EVM during the execution of my transaction, when I retrieve a transaction info from the mirror-node REST APIs, so that I know if I can optimise my transaction cost
- As a user, I want to see and compare how much gas I was charged for and how much gas was consumed by the EVM, when I inspect my transaction in hashscan
Specification
In order to calculate the gasUsed
field, the mirror node should have CONTRACT_ACTION
and CONTRACT_BYTECODE
sidecars enabled. Then, we would use an enhancement logic in the mirror-node-importer
,
which will calculate the gasConsumed
field for each transaction, during the importing process.
The new logic would have a mechanism to first calculate the intrinsic gas cost for the transaction being imported. We can inspect if the transaction has a ContractBytecode
sidecar
related to it. Then we have the following cases:
- if it has - we have a contract creation transaction and we would calculate the intrinsic gas cost based on the length of the init bytecode on top of the default gas cost of 21,000
- if it doesn’t have - we have a contract call and we would calculate the intrinsic gas cost based on the length of the input parameters on top of the default gas cost of 21,000
The same logic could be applied also to EthereumTransactions
, since they wrap the same two categories - contract create and contract call. If we have a ContractBytecode
sidecar for an EthereumTransaction
, then it wraps a contract create,
if not, it wraps a contract call.
On top of the intrinsic gas cost, we would sum up the gasUsed
field for the top level ContractActions sidecar record. The result would be the gasConsumed
field for the transaction.
The calculation for all cases would be:
gas_consumed = intrinsic_gas + top_level_contract_action_gas_used
For historical contract results we have the following options:
- For the contract transactions that were executed after we have started exporting sidecars from consensus nodes, we can make a DB migration in mirror node and populate the
gasConsumed
field for them. - For the contract transactions that were executed prior to the introduction of sidecars or were executed after this period, but have missing sidecar records, we can put a
null
value for thegasConsumed
field. In this case it would mean the value is not applicable and missing.
The gas_used
field in contract_result
table would still have the same semantics and represent the gas that was charged to the user.
The following REST APIs will be extended with the new field:
1. GET /api/v1/contracts/results
2. GET /api/v1/contracts/results/{transactionIdOrHash}
3. GET /api/v1/contracts/{contractIdOrAddress}/results
4. GET /api/v1/contracts/{contractIdOrAddress}/results/{timestamp}
After the change, the API response will look like this:
{
"error_message": "Out of gas",
"failed_initcode": "0x856739",
"from": "0000000000000000000000000000000000001f41",
"function_parameters": "0xbb9f02dc6f0e3289f57a1f33b71c73aa8548ab8b",
"gas_consumed": 55500,
"gas_limit": 400000,
"gas_price": "0x4a817c800",
"gas_used": 320000,
"hash": "0x3531396130303866616264653464"
}
Some fields in this example are omitted for brevity.
Backwards Compatibility
This HIP we will extend the REST APIs responses related to transaction info with an additional field gas_consumed
. This won’t be a breaking change, but the users should have in mind the introduction of the new field.
Security Implications
None
How to Teach This
Respective documentation will be added.
Reference Implementation
Please follow this issue
Rejected Ideas
While discussing the implementation of this HIP, we considered the following alternatives:
- Adding a new
gasConsumed
field to the Record Stream V6 format. This change would have had an impact on the whole ecosystem. - Changing the semantics of the current
gasUsed
field to start representing the gas consumed by the EVM, instead of the gas charged to the user. This approach would have been confusing for the community. - Calculating the
gasConsumed
field on the fly, when the user requests the transaction info from the mirror-node REST APIs. This would have been a performance issue, as we would have to iterate over allContractAction
sidecars for the transaction and calculate thegasConsumed
field every time the user requests the transaction info.
References
- HIP-226 - Mirror Node Contract Execution Results REST API
- HIP-410 - Wrapping Ethereum Transaction Bytes in a Hedera Transaction
- HIP-513 - Smart Contract Traceability Extension
Copyright/license
This document is licensed under the Apache License, Version 2.0 – see LICENSE or (https://www.apache.org/licenses/LICENSE-2.0)
Citation
Please cite this document as: