eth/gasprice: fix fee history percentile weighting under eip-8037

Weight reward percentiles by cumulative receipt gas instead of block
header gas used, which reflects max(regular, state) and can exceed the
sum of per-tx receipt gas.
This commit is contained in:
Weixie Cui 2026-06-28 20:00:00 +08:00
parent 7e625dd548
commit fbe9b59279
2 changed files with 48 additions and 3 deletions

View file

@ -140,17 +140,29 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
return a.reward.Cmp(b.reward)
})
// Percentile rewards are weighted by per-tx receipt gas. Under EIP-8037
// block gas used is max(cumulative regular, cumulative state) and can exceed
// the sum of receipt gas, so base thresholds on the receipt total.
totalGasUsed := bf.receipts[len(bf.receipts)-1].CumulativeGasUsed
bf.results.reward = percentileRewards(sorter, totalGasUsed, percentiles)
}
// percentileRewards returns the effective priority-fee reward at each requested
// percentile, with transactions sorted by ascending tip and weighted by gas used.
func percentileRewards(sorter []txGasAndReward, totalGasUsed uint64, percentiles []float64) []*big.Int {
reward := make([]*big.Int, len(percentiles))
var txIndex int
sumGasUsed := sorter[0].gasUsed
for i, p := range percentiles {
thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100)
for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 {
thresholdGasUsed := uint64(float64(totalGasUsed) * p / 100)
for sumGasUsed < thresholdGasUsed && txIndex < len(sorter)-1 {
txIndex++
sumGasUsed += sorter[txIndex].gasUsed
}
bf.results.reward[i] = sorter[txIndex].reward
reward[i] = sorter[txIndex].reward
}
return reward
}
// resolveBlockRange resolves the specified block range to absolute block numbers while also

View file

@ -22,9 +22,42 @@ import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
// TestPercentileRewardsReceiptGasEIP8037 checks that reward percentiles are
// weighted by the sum of per-tx receipt gas, not the block header gas used.
// Under EIP-8037 the header reflects max(cumulative regular, cumulative state)
// and can exceed receipt totals; using it as the threshold skews percentiles.
func TestPercentileRewardsReceiptGasEIP8037(t *testing.T) {
lowTip := big.NewInt(1)
highTip := big.NewInt(100 * params.GWei)
const (
lowGas = 100_000
highGas = 21_000
receiptTotal = lowGas + highGas
headerGasUsed = 500_000 // state bottleneck, well above receiptTotal
)
sorter := []txGasAndReward{
{gasUsed: lowGas, reward: lowTip},
{gasUsed: highGas, reward: highTip},
}
got := percentileRewards(sorter, receiptTotal, []float64{50})
if got[0].Cmp(lowTip) != 0 {
t.Fatalf("50th percentile reward = %s, want low-tip %s (receipt gas %d)",
got[0], lowTip, receiptTotal)
}
// Using block header gas used as the weight base selects the wrong tx.
gotBroken := percentileRewards(sorter, headerGasUsed, []float64{50})
if gotBroken[0].Cmp(highTip) != 0 {
t.Fatalf("sanity check: header-based weighting = %s, want high-tip %s", gotBroken[0], highTip)
}
}
func TestFeeHistory(t *testing.T) {
var cases = []struct {
pending bool