mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-14 12:06:40 +00:00
consensus/misc/eip4844: use blob parameters of current header (#32424)
This changes the implementation to resolve the blob parameters according to the current header timestamp. This matters for EIP-7918, where we would previously resolve the UpdateFraction according to the parent header fork, leading to a confusing situation at the fork transition block. --------- Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
This commit is contained in:
parent
88922d2bf5
commit
1d29e3ec0e
2 changed files with 161 additions and 94 deletions
|
|
@ -19,6 +19,7 @@ package eip4844
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -29,6 +30,66 @@ var (
|
||||||
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
|
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BlobConfig contains the parameters for blob-related formulas.
|
||||||
|
// These can be adjusted in a fork.
|
||||||
|
type BlobConfig struct {
|
||||||
|
Target int
|
||||||
|
Max int
|
||||||
|
UpdateFraction uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *BlobConfig) maxBlobGas() uint64 {
|
||||||
|
return uint64(bc.Max) * params.BlobTxBlobGasPerBlob
|
||||||
|
}
|
||||||
|
|
||||||
|
// blobBaseFee computes the blob fee.
|
||||||
|
func (bc *BlobConfig) blobBaseFee(excessBlobGas uint64) *big.Int {
|
||||||
|
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), new(big.Int).SetUint64(bc.UpdateFraction))
|
||||||
|
}
|
||||||
|
|
||||||
|
// blobPrice returns the price of one blob in Wei.
|
||||||
|
func (bc *BlobConfig) blobPrice(excessBlobGas uint64) *big.Int {
|
||||||
|
f := bc.blobBaseFee(excessBlobGas)
|
||||||
|
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
|
||||||
|
}
|
||||||
|
|
||||||
|
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
|
||||||
|
if cfg.BlobScheduleConfig == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
london = cfg.LondonBlock
|
||||||
|
s = cfg.BlobScheduleConfig
|
||||||
|
bc *params.BlobConfig
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
|
||||||
|
bc = s.BPO5
|
||||||
|
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
|
||||||
|
bc = s.BPO4
|
||||||
|
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
|
||||||
|
bc = s.BPO3
|
||||||
|
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
|
||||||
|
bc = s.BPO2
|
||||||
|
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
||||||
|
bc = s.BPO1
|
||||||
|
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
||||||
|
bc = s.Osaka
|
||||||
|
case cfg.IsPrague(london, time) && s.Prague != nil:
|
||||||
|
bc = s.Prague
|
||||||
|
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
||||||
|
bc = s.Cancun
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BlobConfig{
|
||||||
|
Target: bc.Target,
|
||||||
|
Max: bc.Max,
|
||||||
|
UpdateFraction: bc.UpdateFraction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
||||||
// if the current block contains no transactions, the excessBlobGas is updated
|
// if the current block contains no transactions, the excessBlobGas is updated
|
||||||
// accordingly.
|
// accordingly.
|
||||||
|
|
@ -36,21 +97,27 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
||||||
if header.Number.Uint64() != parent.Number.Uint64()+1 {
|
if header.Number.Uint64() != parent.Number.Uint64()+1 {
|
||||||
panic("bad header pair")
|
panic("bad header pair")
|
||||||
}
|
}
|
||||||
// Verify the header is not malformed
|
|
||||||
|
bcfg := latestBlobConfig(config, header.Time)
|
||||||
|
if bcfg == nil {
|
||||||
|
panic("called before EIP-4844 is active")
|
||||||
|
}
|
||||||
|
|
||||||
if header.ExcessBlobGas == nil {
|
if header.ExcessBlobGas == nil {
|
||||||
return errors.New("header is missing excessBlobGas")
|
return errors.New("header is missing excessBlobGas")
|
||||||
}
|
}
|
||||||
if header.BlobGasUsed == nil {
|
if header.BlobGasUsed == nil {
|
||||||
return errors.New("header is missing blobGasUsed")
|
return errors.New("header is missing blobGasUsed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the blob gas used remains within reasonable limits.
|
// Verify that the blob gas used remains within reasonable limits.
|
||||||
maxBlobGas := MaxBlobGasPerBlock(config, header.Time)
|
if *header.BlobGasUsed > bcfg.maxBlobGas() {
|
||||||
if *header.BlobGasUsed > maxBlobGas {
|
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
|
||||||
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas)
|
|
||||||
}
|
}
|
||||||
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
|
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
|
||||||
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
|
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the excessBlobGas is correct based on the parent header
|
// Verify the excessBlobGas is correct based on the parent header
|
||||||
expectedExcessBlobGas := CalcExcessBlobGas(config, parent, header.Time)
|
expectedExcessBlobGas := CalcExcessBlobGas(config, parent, header.Time)
|
||||||
if *header.ExcessBlobGas != expectedExcessBlobGas {
|
if *header.ExcessBlobGas != expectedExcessBlobGas {
|
||||||
|
|
@ -62,38 +129,41 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
||||||
// CalcExcessBlobGas calculates the excess blob gas after applying the set of
|
// CalcExcessBlobGas calculates the excess blob gas after applying the set of
|
||||||
// blobs on top of the excess blob gas.
|
// blobs on top of the excess blob gas.
|
||||||
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
|
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
|
||||||
var (
|
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
|
||||||
parentExcessBlobGas uint64
|
bcfg := latestBlobConfig(config, headTimestamp)
|
||||||
parentBlobGasUsed uint64
|
return calcExcessBlobGas(isOsaka, bcfg, parent)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uint64 {
|
||||||
|
var parentExcessBlobGas, parentBlobGasUsed uint64
|
||||||
if parent.ExcessBlobGas != nil {
|
if parent.ExcessBlobGas != nil {
|
||||||
parentExcessBlobGas = *parent.ExcessBlobGas
|
parentExcessBlobGas = *parent.ExcessBlobGas
|
||||||
parentBlobGasUsed = *parent.BlobGasUsed
|
parentBlobGasUsed = *parent.BlobGasUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
excessBlobGas = parentExcessBlobGas + parentBlobGasUsed
|
excessBlobGas = parentExcessBlobGas + parentBlobGasUsed
|
||||||
target = targetBlobsPerBlock(config, headTimestamp)
|
targetGas = uint64(bcfg.Target) * params.BlobTxBlobGasPerBlob
|
||||||
targetGas = uint64(target) * params.BlobTxBlobGasPerBlob
|
|
||||||
)
|
)
|
||||||
if excessBlobGas < targetGas {
|
if excessBlobGas < targetGas {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if !config.IsOsaka(config.LondonBlock, headTimestamp) {
|
|
||||||
// Pre-Osaka, we use the formula defined by EIP-4844.
|
// EIP-7918 (post-Osaka) introduces a different formula for computing excess,
|
||||||
return excessBlobGas - targetGas
|
// in cases where the price is lower than a 'reserve price'.
|
||||||
|
if isOsaka {
|
||||||
|
var (
|
||||||
|
baseCost = big.NewInt(params.BlobBaseCost)
|
||||||
|
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
|
||||||
|
blobPrice = bcfg.blobPrice(parentExcessBlobGas)
|
||||||
|
)
|
||||||
|
if reservePrice.Cmp(blobPrice) > 0 {
|
||||||
|
scaledExcess := parentBlobGasUsed * uint64(bcfg.Max-bcfg.Target) / uint64(bcfg.Max)
|
||||||
|
return parentExcessBlobGas + scaledExcess
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EIP-7918 (post-Osaka) introduces a different formula for computing excess.
|
// Original EIP-4844 formula.
|
||||||
var (
|
|
||||||
baseCost = big.NewInt(params.BlobBaseCost)
|
|
||||||
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
|
|
||||||
blobPrice = calcBlobPrice(config, parent)
|
|
||||||
)
|
|
||||||
if reservePrice.Cmp(blobPrice) > 0 {
|
|
||||||
max := MaxBlobsPerBlock(config, headTimestamp)
|
|
||||||
scaledExcess := parentBlobGasUsed * uint64(max-target) / uint64(max)
|
|
||||||
return parentExcessBlobGas + scaledExcess
|
|
||||||
}
|
|
||||||
return excessBlobGas - targetGas
|
return excessBlobGas - targetGas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +173,7 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
||||||
if blobConfig == nil {
|
if blobConfig == nil {
|
||||||
panic("calculating blob fee on unsupported fork")
|
panic("calculating blob fee on unsupported fork")
|
||||||
}
|
}
|
||||||
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(blobConfig.UpdateFraction))
|
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
||||||
|
|
@ -115,36 +185,6 @@ func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
||||||
return blobConfig.Max
|
return blobConfig.Max
|
||||||
}
|
}
|
||||||
|
|
||||||
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *params.BlobConfig {
|
|
||||||
if cfg.BlobScheduleConfig == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
london = cfg.LondonBlock
|
|
||||||
s = cfg.BlobScheduleConfig
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
|
|
||||||
return s.BPO5
|
|
||||||
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
|
|
||||||
return s.BPO4
|
|
||||||
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
|
|
||||||
return s.BPO3
|
|
||||||
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
|
|
||||||
return s.BPO2
|
|
||||||
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
|
||||||
return s.BPO1
|
|
||||||
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
|
||||||
return s.Osaka
|
|
||||||
case cfg.IsPrague(london, time) && s.Prague != nil:
|
|
||||||
return s.Prague
|
|
||||||
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
|
||||||
return s.Cancun
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxBlobGasPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp.
|
// MaxBlobGasPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp.
|
||||||
func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
||||||
return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob
|
return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob
|
||||||
|
|
@ -153,39 +193,11 @@ func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
||||||
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
||||||
// configuration, regardless of the currently active fork.
|
// configuration, regardless of the currently active fork.
|
||||||
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
||||||
s := cfg.BlobScheduleConfig
|
bcfg := latestBlobConfig(cfg, math.MaxUint64)
|
||||||
if s == nil {
|
if bcfg == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
switch {
|
return bcfg.Max
|
||||||
case s.BPO5 != nil:
|
|
||||||
return s.BPO5.Max
|
|
||||||
case s.BPO4 != nil:
|
|
||||||
return s.BPO4.Max
|
|
||||||
case s.BPO3 != nil:
|
|
||||||
return s.BPO3.Max
|
|
||||||
case s.BPO2 != nil:
|
|
||||||
return s.BPO2.Max
|
|
||||||
case s.BPO1 != nil:
|
|
||||||
return s.BPO1.Max
|
|
||||||
case s.Osaka != nil:
|
|
||||||
return s.Osaka.Max
|
|
||||||
case s.Prague != nil:
|
|
||||||
return s.Prague.Max
|
|
||||||
case s.Cancun != nil:
|
|
||||||
return s.Cancun.Max
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp.
|
|
||||||
func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
|
||||||
blobConfig := latestBlobConfig(cfg, time)
|
|
||||||
if blobConfig == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return blobConfig.Target
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
||||||
|
|
@ -204,9 +216,3 @@ func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
|
||||||
}
|
}
|
||||||
return output.Div(output, denominator)
|
return output.Div(output, denominator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcBlobPrice calculates the blob price for a block.
|
|
||||||
func calcBlobPrice(config *params.ChainConfig, header *types.Header) *big.Int {
|
|
||||||
blobBaseFee := CalcBlobFee(config, header)
|
|
||||||
return new(big.Int).Mul(blobBaseFee, big.NewInt(params.BlobTxBlobGasPerBlob))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,10 @@ import (
|
||||||
func TestCalcExcessBlobGas(t *testing.T) {
|
func TestCalcExcessBlobGas(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
config = params.MainnetChainConfig
|
config = params.MainnetChainConfig
|
||||||
targetBlobs = targetBlobsPerBlock(config, *config.CancunTime)
|
targetBlobs = config.BlobScheduleConfig.Cancun.Target
|
||||||
targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
||||||
)
|
)
|
||||||
|
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
excess uint64
|
excess uint64
|
||||||
blobs int
|
blobs int
|
||||||
|
|
@ -90,6 +91,65 @@ func TestCalcBlobFee(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCalcBlobFeePostOsaka(t *testing.T) {
|
||||||
|
zero := uint64(0)
|
||||||
|
bpo1 := uint64(1754836608)
|
||||||
|
bpo2 := uint64(1754934912)
|
||||||
|
bpo3 := uint64(1755033216)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
excessBlobGas uint64
|
||||||
|
blobGasUsed uint64
|
||||||
|
blobfee uint64
|
||||||
|
basefee uint64
|
||||||
|
parenttime uint64
|
||||||
|
headertime uint64
|
||||||
|
}{
|
||||||
|
{5149252, 1310720, 5617366, 30, 1754904516, 1754904528},
|
||||||
|
{19251039, 2490368, 20107103, 50, 1755033204, 1755033216},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
config := ¶ms.ChainConfig{
|
||||||
|
LondonBlock: big.NewInt(0),
|
||||||
|
CancunTime: &zero,
|
||||||
|
PragueTime: &zero,
|
||||||
|
OsakaTime: &zero,
|
||||||
|
BPO1Time: &bpo1,
|
||||||
|
BPO2Time: &bpo2,
|
||||||
|
BPO3Time: &bpo3,
|
||||||
|
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||||
|
Cancun: params.DefaultCancunBlobConfig,
|
||||||
|
Prague: params.DefaultPragueBlobConfig,
|
||||||
|
Osaka: params.DefaultOsakaBlobConfig,
|
||||||
|
BPO1: ¶ms.BlobConfig{
|
||||||
|
Target: 9,
|
||||||
|
Max: 14,
|
||||||
|
UpdateFraction: 8832827,
|
||||||
|
},
|
||||||
|
BPO2: ¶ms.BlobConfig{
|
||||||
|
Target: 14,
|
||||||
|
Max: 21,
|
||||||
|
UpdateFraction: 13739630,
|
||||||
|
},
|
||||||
|
BPO3: ¶ms.BlobConfig{
|
||||||
|
Target: 21,
|
||||||
|
Max: 32,
|
||||||
|
UpdateFraction: 20609697,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
parent := &types.Header{
|
||||||
|
ExcessBlobGas: &tt.excessBlobGas,
|
||||||
|
BlobGasUsed: &tt.blobGasUsed,
|
||||||
|
BaseFee: big.NewInt(int64(tt.basefee)),
|
||||||
|
Time: tt.parenttime,
|
||||||
|
}
|
||||||
|
have := CalcExcessBlobGas(config, parent, tt.headertime)
|
||||||
|
if have != tt.blobfee {
|
||||||
|
t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFakeExponential(t *testing.T) {
|
func TestFakeExponential(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
factor int64
|
factor int64
|
||||||
|
|
@ -131,9 +191,10 @@ func TestFakeExponential(t *testing.T) {
|
||||||
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
|
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cfg = params.MergedTestChainConfig
|
cfg = params.MergedTestChainConfig
|
||||||
targetBlobs = targetBlobsPerBlock(cfg, *cfg.CancunTime)
|
targetBlobs = cfg.BlobScheduleConfig.Osaka.Target
|
||||||
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
||||||
)
|
)
|
||||||
|
|
||||||
makeHeader := func(parentExcess, parentBaseFee uint64, blobsUsed int) *types.Header {
|
makeHeader := func(parentExcess, parentBaseFee uint64, blobsUsed int) *types.Header {
|
||||||
blobGasUsed := uint64(blobsUsed) * params.BlobTxBlobGasPerBlob
|
blobGasUsed := uint64(blobsUsed) * params.BlobTxBlobGasPerBlob
|
||||||
return &types.Header{
|
return &types.Header{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue