GitHub Logo 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 Accepted
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-02-21

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

  1. 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
  2. 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:

  1. 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.
  2. 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 the gasConsumed 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:

  1. Adding a new gasConsumed field to the Record Stream V6 format. This change would have had an impact on the whole ecosystem.
  2. 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.
  3. 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 all ContractAction sidecars for the transaction and calculate the gasConsumed 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: