Skip to content

fix: fixed cumulativeGasUsed calculations in eth_getBlockReceipts and eth_getTransactionReceipt (#4921)#4936

Open
BartoszSolkaBD wants to merge 2 commits intohiero-ledger:mainfrom
BartoszSolkaBD:4921-Fix-cumulativeGasUsed--use-per-transaction-cumulative-gas,-not-block-total
Open

fix: fixed cumulativeGasUsed calculations in eth_getBlockReceipts and eth_getTransactionReceipt (#4921)#4936
BartoszSolkaBD wants to merge 2 commits intohiero-ledger:mainfrom
BartoszSolkaBD:4921-Fix-cumulativeGasUsed--use-per-transaction-cumulative-gas,-not-block-total

Conversation

@BartoszSolkaBD
Copy link
Contributor

@BartoszSolkaBD BartoszSolkaBD commented Feb 17, 2026

Description

This PR fixes an issue where cumulativeGasUsed in transaction receipts was incorrectly set to the block’s total gas used (block_gas_used) for every transaction in the block.

It updates the receipt generation logic so that cumulativeGasUsed reflects the cumulative gas consumed in the block up to and including each transaction, matching the Ethereum Yellow Paper semantics.

This change has been implemented for both eth_getBlockReceipts and eth_getTransactionReceipt.

It also updates the receipts root hash computation (and its expected test value) to use the corrected cumulative gas and adds targeted tests to validate multi‑transaction block behavior and OpenRPC contract for these endpoints.

Related issue(s)

Fixes #4921

Testing Guide

  1. Pick a block with multiple receipts, e.g. 0x56e7e72.
  2. Call eth_getBlockReceipts for that block.
  3. Observe that every receipt in the response has the correct cumulativeGasUsed value, which is increasing per transaction as defined in the Ethereum Yellow Paper.

Changes from original design (optional)

N/A

Additional work needed (optional)

N/A

Checklist

  • I've assigned an assignee to this PR and related issue(s) (if applicable)
  • I've assigned a label to this PR and related issue(s) (if applicable)
  • I've assigned a milestone to this PR and related issue(s) (if applicable)
  • I've updated documentation (code comments, README, etc. if applicable)
  • I've done sufficient testing (unit, integration, etc.)

… eth_getTransactionReceipt (hiero-ledger#4921)

Signed-off-by: Bartosz Solka <bartosz.solka@blockydevs.com>
@BartoszSolkaBD BartoszSolkaBD requested review from a team as code owners February 17, 2026 12:59
@BartoszSolkaBD BartoszSolkaBD self-assigned this Feb 17, 2026
@BartoszSolkaBD BartoszSolkaBD added the bug Something isn't working label Feb 17, 2026
@BartoszSolkaBD BartoszSolkaBD added this to the 0.76.0 milestone Feb 17, 2026
Copy link
Contributor

@jasuwienas jasuwienas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall, but I have a few questions.

for (const item of items) {
const { transactionIndex, logsPerTx, crPerTx } = item;

const gasUsed = crPerTx.length && crPerTx[0].gas_used != null ? crPerTx[0].gas_used : 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const gasUsed = crPerTx.length && crPerTx[0].gas_used != null ? crPerTx[0].gas_used : 0;
const gasUsed = crPerTx[0]?.gas_used ?? 0;


const gasUsed = crPerTx.length && crPerTx[0].gas_used != null ? crPerTx[0].gas_used : 0;
cumulativeGas += gasUsed;
const transactionIndexHex = intToHex(transactionIndex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still might have -1 here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use 0 as a fallback value. Do you see any risks in doing that?

const receiptPromises = contractResults.map(async (contractResult) => {
// Ensure contract results are processed in transaction_index (block) order
const sortedContractResults = [...contractResults].sort((a, b) => {
const aIdx = a.transaction_index ?? Number.MAX_SAFE_INTEGER;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should we sort by eventually? In previous method we used:

crPerTx[0].transaction_index

but when it was missing we checked:

logsPerTx[0].transactionIndex

now we are taking only cr, right?

Since we are fetching them all with a single request woulnd't it be posisble to fget them fetched from mirrorndoe right away, instead of sorting them on our own?

Aren;'t they, by any chance already sorted?

Comment on lines +142 to +143
const logsPerTx: Log[] = logs.filter((log) => log.transactionHash === txHash);
const crPerTx: any[] = contractResults.filter((cr) => cr.hash === txHash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Don't we really know what is the type of crPerTx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be able to type it. Maybe we can create another issue to fix it everywhere in the codebase?

Comment on lines +498 to +502
const params: IContractResultsParams = {
blockNumber: receiptResponse.block_number,
};

const blockContractResults = await this.mirrorNodeClient.getContractResults(requestDetails, params);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I really don’t like the idea of fetching heavy block data for every transaction just to retrieve this single value :/ . But I don't know what to do about that, I really think this should be just calcualted in the mirrornode.

q:
Is there any correlation between the transaction index and the timestamp? Since this is the Hashgraph overall, consensus timestamps should matter for ordering, right? Maybe it applies to the on-block orders and we can fetch only the transactions with a timestamp lower than or equal to the one we’re querying for? (or would there be a problem with internal transactions for examlple?)

Also, we’re ignoring the fallback tx index from logs and only using the one from contract results. Wont that create differences for a single transaction when comparing it across these different endpoints?

…iero-ledger#4921)

Signed-off-by: Bartosz Solka <bartosz.solka@blockydevs.com>
@codecov
Copy link

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 71.59091% with 25 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ib/services/ethService/blockService/blockWorker.ts 68.62% 16 Missing ⚠️
...thService/transactionService/TransactionService.ts 73.52% 9 Missing ⚠️

❌ Your patch check has failed because the patch coverage (71.59%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (70.34%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

❗ There is a different number of reports uploaded between BASE (e0cea90) and HEAD (e999c06). Click for more details.

HEAD has 48 uploads less than BASE
Flag BASE (e0cea90) HEAD (e999c06)
config-service 2 0
relay 2 0
server 2 0
ws-server 2 0
42 2
@@             Coverage Diff             @@
##             main    #4936       +/-   ##
===========================================
- Coverage   95.94%   70.34%   -25.60%     
===========================================
  Files         143      143               
  Lines       23701    23839      +138     
  Branches     1877      632     -1245     
===========================================
- Hits        22740    16770     -5970     
- Misses        937     7052     +6115     
+ Partials       24       17        -7     
Flag Coverage Δ
config-service ?
relay ?
server ?
ws-server ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...lay/src/lib/factories/transactionReceiptFactory.ts 79.08% <100.00%> (-20.92%) ⬇️
...thService/transactionService/TransactionService.ts 63.29% <73.52%> (-36.33%) ⬇️
...ib/services/ethService/blockService/blockWorker.ts 67.24% <68.62%> (-31.60%) ⬇️

... and 85 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix cumulativeGasUsed: use per-transaction cumulative gas, not block total

2 participants

Comments