diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index e5a197f640..92cc76d4cc 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -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 diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index d220027e69..2c8b346d7e 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -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