Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 69 additions & 24 deletions consensus/misc/eip1559/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,19 @@ import (
"github.com/ethereum/go-ethereum/params"
)

const (
// MaxBaseFeeChangePercent limits the maximum base fee change per block to 5% of parent base fee.
// This prevents excessive fee volatility by capping both increases and decreases.
// The 5% limit provides protection against aggressive parameter configurations while
// accommodating the natural behavior of default post-Dandeli parameters (maximum ~1.7% change).
MaxBaseFeeChangePercent = 5
)

// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559,
// - gas limit check
// - basefee check
// - basefee check with different rules pre/post Dandeli:
// - Pre-Dandeli: Strict validation (baseFee must exactly match calculated value)
// - Post-Dandeli: Boundary validation (baseFee change must be within MaxBaseFeeChangePercent)
func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error {
// Verify that the gas limit remains within allowed bounds
parentGasLimit := parent.GasLimit
Expand All @@ -43,7 +53,13 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
if header.BaseFee == nil {
return errors.New("header is missing baseFee")
}
// Verify the baseFee is correct based on the parent header.

// Post-Dandeli: Validate that base fee changes are within allowed boundaries
if config.Bor != nil && config.Bor.IsDandeli(header.Number) {
Comment thread
lucca30 marked this conversation as resolved.
return verifyBaseFeeWithinBoundaries(parent, header)
}

// Pre-Dandeli: Verify the baseFee is correct based on the parent header
expectedBaseFee := CalcBaseFee(config, parent)
if header.BaseFee.Cmp(expectedBaseFee) != 0 {
return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d",
Expand All @@ -53,6 +69,33 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
return nil
}

// verifyBaseFeeWithinBoundaries checks that the base fee change is within the allowed boundary.
// This prevents excessive fee volatility while allowing dynamic fee adjustment post-Dandeli.
// The boundary limit is defined by MaxBaseFeeChangePercent constant.
func verifyBaseFeeWithinBoundaries(parent, header *types.Header) error {
// Calculate the maximum allowed change (MaxBaseFeeChangePercent of parent base fee)
maxAllowedChange := new(big.Int).Mul(parent.BaseFee, big.NewInt(MaxBaseFeeChangePercent))
maxAllowedChange.Div(maxAllowedChange, big.NewInt(100))

// Calculate the actual change in base fee
actualChange := new(big.Int)
if header.BaseFee.Cmp(parent.BaseFee) >= 0 {
// Base fee increased or stayed the same
actualChange.Sub(header.BaseFee, parent.BaseFee)
} else {
// Base fee decreased
actualChange.Sub(parent.BaseFee, header.BaseFee)
}

// Verify the change is within the allowed boundary
if actualChange.Cmp(maxAllowedChange) > 0 {
return fmt.Errorf("baseFee change exceeds %d%% limit: change=%s, maxAllowed=%s, parentBaseFee=%s, headerBaseFee=%s",
MaxBaseFeeChangePercent, actualChange, maxAllowedChange, parent.BaseFee, header.BaseFee)
}

return nil
}

// CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
Expand All @@ -70,16 +113,30 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
var (
num = new(big.Int)
denom = new(big.Int)
baseFeeChangeDenominatorUint64 = baseFeeChangeDenominator(config, parent.Number)
baseFeeChangeDenominatorUint64 = params.BaseFeeChangeDenominator(config.Bor, parent.Number)
)

// Calculate maximum allowed change (only applies post-Dandeli)
var maxAllowedChange *big.Int
applyBoundaryCap := config.Bor != nil && config.Bor.IsDandeli(parent.Number)
if applyBoundaryCap {
maxAllowedChange = new(big.Int).Mul(parent.BaseFee, big.NewInt(MaxBaseFeeChangePercent))
maxAllowedChange.Div(maxAllowedChange, big.NewInt(100))
}

if parent.GasUsed > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(baseFeeChangeDenominatorUint64))

// Cap the increase to MaxBaseFeeChangePercent post-Dandeli
if applyBoundaryCap && num.Cmp(maxAllowedChange) > 0 {
num.Set(maxAllowedChange)
}

if num.Cmp(common.Big1) < 0 {
return num.Add(parent.BaseFee, common.Big1)
}
Expand All @@ -92,6 +149,11 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(baseFeeChangeDenominatorUint64))

// Cap the decrease to MaxBaseFeeChangePercent post-Dandeli
if applyBoundaryCap && num.Cmp(maxAllowedChange) > 0 {
num.Set(maxAllowedChange)
}

baseFee := num.Sub(parent.BaseFee, num)
if baseFee.Cmp(common.Big0) < 0 {
baseFee = common.Big0
Expand All @@ -100,31 +162,14 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
}
}

// baseFeeChangeDenominator returns the denominator used to bound the amount the base fee can change between blocks.
// The value varies based on the hard fork:
// - Pre-Delhi: 8 (default)
// - Post-Delhi: 16
// - Post-Bhilai: 64
// If borConfig is nil, returns the default value of 8.
func baseFeeChangeDenominator(config *params.ChainConfig, number *big.Int) uint64 {
if config.Bor == nil {
return params.DefaultBaseFeeChangeDenominator
}
if config.Bor.IsBhilai(number) {
return params.BaseFeeChangeDenominatorPostBhilai
} else if config.Bor.IsDelhi(number) {
return params.BaseFeeChangeDenominatorPostDelhi
} else {
return params.DefaultBaseFeeChangeDenominator
}
}

// calcParentGasTarget calculates the target gas based on parent block gas limit. Earlier
// it was derived by `ElasticityMultiplier` as it had an integer multiplier value. Post
// dandeli HF, a percentage value will be used to calculate the gas target.
// Dandeli HF, a percentage value is used to calculate the gas target (validated with fallback to default).
func calcParentGasTarget(config *params.ChainConfig, parent *types.Header) uint64 {
if config.Bor != nil && config.Bor.IsDandeli(parent.Number) {
return parent.GasLimit * params.TargetGasPercentagePostDandeli / 100
// Use helper function which validates and provides defaults
targetPercentage := config.Bor.GetTargetGasPercentage(parent.Number)
return parent.GasLimit * targetPercentage / 100
}
return parent.GasLimit / config.ElasticityMultiplier()
}
Loading
Loading