Skip to content

fix(rpc): report base fee as the gas price of derived txs - #37

Merged
0xNilesh merged 1 commit into
developfrom
fix/negative-block-reward
Aug 3, 2026
Merged

fix(rpc): report base fee as the gas price of derived txs#37
0xNilesh merged 1 commit into
developfrom
fix/negative-block-reward

Conversation

@AryaLanjewar3005

Copy link
Copy Markdown
Collaborator

Description

Summary

Blocks on donut.push.network whose only content is derived transactions
(protocol-internal, not user-signed) render a negative block reward.

Derived txs are reconstructed for JSON-RPC from ABCI events, which do not carry fee data, so they are
rebuilt with GasFeeCap = GasTipCap = 0. The generic EIP-1559 formulas then resolve their price to 0
in both the transaction object and the receipt, while the receipt still reports gasUsed > 0.

Any consumer that models every transaction as burning base_fee × gas_used therefore reads such a block
as burning more than it collected. Blockscout computes a block's reward as:

Σ(gas_used × gas_price) − base_fee_per_gas × Σ(gas_used)

which for a derived-only block is 0 − base_fee × gas_used → negative.

This PR reports the block base fee — the effective price of a transaction that adds no priority tip —
as a derived tx's gasPrice and receipt effectiveGasPrice. That makes the arithmetic net to exactly
zero, which matches the chain's actual economics: a derived tx pays nothing to the proposer and burns
nothing, so it should move the block reward by nothing.

Chain economics are unchanged. This is a JSON-RPC reporting fix only — no consensus, no state, no
upgrade handler.

Evidence

Live donut RPC, block 0x1357085, tx 0xc8b5ce78d703dbca59db6461510d1a30949f642f78de936cdf20a00d3eb84513:

field before after
block baseFeePerGas 0x3b9aca00 (1 gwei) unchanged
tx gasPrice 0x0 0x3b9aca00
receipt effectiveGasPrice 0x0 0x3b9aca00
receipt gasUsed 0x17fa4 (98,212) unchanged
Blockscout reward −98,212 gwei 0

Root cause

Backend.parseDerivedTxFromAdditionalFields (rpc/backend/comet_to_eth.go) rebuilds a derived tx as a
DynamicFeeTx with zero fee caps, because the emitted events carry no fee fields. Downstream:

  • NewRPCTransactionFromIncompleteMsg set GasPrice: tx.GasPrice()GasFeeCap0.
  • ReceiptsFromCometBlock used EffectiveGasPrice(tx, baseFee) = min(tip + baseFee, feeCap)0.

Both are correct for a real transaction and wrong for a synthetic one that is exempt from fees.

Note: a fix for this existed on feat/derived-tx-evm-0.4.0 (5ee581ef"fix: negative block
reward"
) but is not an ancestor of main, evm-upgrade-0.6.0, or any commit push-chain has ever
pinned, so it never shipped. This PR restores that approach and extends it to the receipt.

Changes

File Change
rpc/types/utils.go New exported DerivedTxGasPrice(tx, baseFee). NewRPCTransactionFromIncompleteMsg now reports the base fee as gasPrice for mined derived txs.
rpc/backend/comet_to_eth.go ReceiptsFromCometBlock uses the same helper for derived txs, so the receipt's effectiveGasPrice agrees with the served gasPrice.
rpc/types/utils_test.go Regression test for the four gasPrice cases (mined, unknown base fee, unmined, no aliasing of the caller's baseFee).
rpc/backend/derived_gas_price_test.go New. Regression test asserting the receipt reports the base fee and agrees with eth_getTransactionByHash.
CHANGELOG.md Entry under v0.6.0 → BUG FIXES.

Fallback behaviour: when the base fee is unavailable (pruned node) or the tx is not yet mined, the
reconstructed tx's own price is returned rather than inventing one — i.e. the previous behaviour.

Affected endpoints — all three tx-serving paths already threaded baseFee through, so no signature
changes were needed:

  • eth_getTransactionByHash
  • eth_getTransactionByBlockHashAndIndex / eth_getTransactionByBlockNumberAndIndex
  • eth_getBlockByNumber / eth_getBlockByHash (with fullTx = true)
  • eth_getTransactionReceipt, eth_getBlockReceipts

Deliberately out of scope

parseDerivedTxFromAdditionalFields still reconstructs with zero fee caps. Setting a non-zero
GasFeeCap there would fix both fields in one place, but that same struct is fed to the tracers
(rpc/backend/tracing.go), where a non-zero price would deduct fees from the sender during trace replay
and diverge from actual execution. This PR changes the reporting layer only.

maxFeePerGas / maxPriorityFeePerGas remain absent on derived txs, as before. They are
user-declared caps and a protocol-internal tx has none; fabricating them would invent data. Consumers
fall back to gasPrice, which is now correct.

Receipt consistency. 5ee581ef fixed only the transaction object and left the receipt at 0. This
PR fixes both, because recent Blockscout versions override the stored gas_price with the receipt's
effectiveGasPrice when it is present — leaving them inconsistent would make the fix depend on the
indexer version. It is also simply correct for the two fields to agree.

Testing

go build ./...                       # clean
go test -tags=test ./rpc/...         # all green

Both new tests were verified to fail with the fix reverted and pass with it applied.

Risk and compatibility

  • No consensus, state-machine, or storage changes — RPC serialization only.
  • No breaking API changes; DerivedTxGasPrice is additive.
  • Affects only txs reconstructed from derived-tx events (additional != nil). Standard user-signed
    transactions take the unchanged code path.
  • eth_feeHistory is unaffected: it iterates decoded MsgEthereumTx only, so derived txs never enter
    its reward percentiles.

Deployment notes

  1. RPC-only, so no upgrade handler is required — nodes only need to be restarted on the new binary.
  2. Values are recomputed from events on every request, so all blocks are served correctly immediately,
    including historical ones.
  3. Blockscout has already persisted gas_price = 0 for derived txs it indexed before this fix.
    Affected blocks must be re-indexed for their displayed rewards to correct.

Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • tackled an existing issue or discussed with a team member
  • left instructions on how to review the changes
  • targeted the main branch

@0xNilesh
0xNilesh changed the base branch from evm-upgrade-0.6.0 to develop August 3, 2026 07:23
@0xNilesh
0xNilesh force-pushed the fix/negative-block-reward branch from 18238cb to d4143b0 Compare August 3, 2026 07:23
@0xNilesh
0xNilesh merged commit 2d248e1 into develop Aug 3, 2026
18 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants