Skip to content

Commit dacb243

Browse files
authored
Dynamic EIP-1559 params setup (#1989)
* remove base fee validation * moving flags to miner setup * set values properly * proper format * claude feedback and coverage * minor missing cover scenario * lint fix * remove claude ignore * remove duplicate and fix tests * increase coverage * lint * Including integration tests for config changes and params divergence * boundary limits * race condition fix
1 parent a395327 commit dacb243

File tree

13 files changed

+2337
-86
lines changed

13 files changed

+2337
-86
lines changed

consensus/misc/eip1559/eip1559.go

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,19 @@ import (
2727
"github.com/ethereum/go-ethereum/params"
2828
)
2929

30+
const (
31+
// MaxBaseFeeChangePercent limits the maximum base fee change per block to 5% of parent base fee.
32+
// This prevents excessive fee volatility by capping both increases and decreases.
33+
// The 5% limit provides protection against aggressive parameter configurations while
34+
// accommodating the natural behavior of default post-Dandeli parameters (maximum ~1.7% change).
35+
MaxBaseFeeChangePercent = 5
36+
)
37+
3038
// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559,
3139
// - gas limit check
32-
// - basefee check
40+
// - basefee check with different rules pre/post Dandeli:
41+
// - Pre-Dandeli: Strict validation (baseFee must exactly match calculated value)
42+
// - Post-Dandeli: Boundary validation (baseFee change must be within MaxBaseFeeChangePercent)
3343
func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error {
3444
// Verify that the gas limit remains within allowed bounds
3545
parentGasLimit := parent.GasLimit
@@ -43,7 +53,13 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
4353
if header.BaseFee == nil {
4454
return errors.New("header is missing baseFee")
4555
}
46-
// Verify the baseFee is correct based on the parent header.
56+
57+
// Post-Dandeli: Validate that base fee changes are within allowed boundaries
58+
if config.Bor != nil && config.Bor.IsDandeli(header.Number) {
59+
return verifyBaseFeeWithinBoundaries(parent, header)
60+
}
61+
62+
// Pre-Dandeli: Verify the baseFee is correct based on the parent header
4763
expectedBaseFee := CalcBaseFee(config, parent)
4864
if header.BaseFee.Cmp(expectedBaseFee) != 0 {
4965
return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d",
@@ -53,6 +69,33 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
5369
return nil
5470
}
5571

72+
// verifyBaseFeeWithinBoundaries checks that the base fee change is within the allowed boundary.
73+
// This prevents excessive fee volatility while allowing dynamic fee adjustment post-Dandeli.
74+
// The boundary limit is defined by MaxBaseFeeChangePercent constant.
75+
func verifyBaseFeeWithinBoundaries(parent, header *types.Header) error {
76+
// Calculate the maximum allowed change (MaxBaseFeeChangePercent of parent base fee)
77+
maxAllowedChange := new(big.Int).Mul(parent.BaseFee, big.NewInt(MaxBaseFeeChangePercent))
78+
maxAllowedChange.Div(maxAllowedChange, big.NewInt(100))
79+
80+
// Calculate the actual change in base fee
81+
actualChange := new(big.Int)
82+
if header.BaseFee.Cmp(parent.BaseFee) >= 0 {
83+
// Base fee increased or stayed the same
84+
actualChange.Sub(header.BaseFee, parent.BaseFee)
85+
} else {
86+
// Base fee decreased
87+
actualChange.Sub(parent.BaseFee, header.BaseFee)
88+
}
89+
90+
// Verify the change is within the allowed boundary
91+
if actualChange.Cmp(maxAllowedChange) > 0 {
92+
return fmt.Errorf("baseFee change exceeds %d%% limit: change=%s, maxAllowed=%s, parentBaseFee=%s, headerBaseFee=%s",
93+
MaxBaseFeeChangePercent, actualChange, maxAllowedChange, parent.BaseFee, header.BaseFee)
94+
}
95+
96+
return nil
97+
}
98+
5699
// CalcBaseFee calculates the basefee of the header.
57100
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
58101
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
@@ -70,16 +113,30 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
70113
var (
71114
num = new(big.Int)
72115
denom = new(big.Int)
73-
baseFeeChangeDenominatorUint64 = baseFeeChangeDenominator(config, parent.Number)
116+
baseFeeChangeDenominatorUint64 = params.BaseFeeChangeDenominator(config.Bor, parent.Number)
74117
)
75118

119+
// Calculate maximum allowed change (only applies post-Dandeli)
120+
var maxAllowedChange *big.Int
121+
applyBoundaryCap := config.Bor != nil && config.Bor.IsDandeli(parent.Number)
122+
if applyBoundaryCap {
123+
maxAllowedChange = new(big.Int).Mul(parent.BaseFee, big.NewInt(MaxBaseFeeChangePercent))
124+
maxAllowedChange.Div(maxAllowedChange, big.NewInt(100))
125+
}
126+
76127
if parent.GasUsed > parentGasTarget {
77128
// If the parent block used more gas than its target, the baseFee should increase.
78129
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
79130
num.SetUint64(parent.GasUsed - parentGasTarget)
80131
num.Mul(num, parent.BaseFee)
81132
num.Div(num, denom.SetUint64(parentGasTarget))
82133
num.Div(num, denom.SetUint64(baseFeeChangeDenominatorUint64))
134+
135+
// Cap the increase to MaxBaseFeeChangePercent post-Dandeli
136+
if applyBoundaryCap && num.Cmp(maxAllowedChange) > 0 {
137+
num.Set(maxAllowedChange)
138+
}
139+
83140
if num.Cmp(common.Big1) < 0 {
84141
return num.Add(parent.BaseFee, common.Big1)
85142
}
@@ -92,6 +149,11 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
92149
num.Div(num, denom.SetUint64(parentGasTarget))
93150
num.Div(num, denom.SetUint64(baseFeeChangeDenominatorUint64))
94151

152+
// Cap the decrease to MaxBaseFeeChangePercent post-Dandeli
153+
if applyBoundaryCap && num.Cmp(maxAllowedChange) > 0 {
154+
num.Set(maxAllowedChange)
155+
}
156+
95157
baseFee := num.Sub(parent.BaseFee, num)
96158
if baseFee.Cmp(common.Big0) < 0 {
97159
baseFee = common.Big0
@@ -100,31 +162,14 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
100162
}
101163
}
102164

103-
// baseFeeChangeDenominator returns the denominator used to bound the amount the base fee can change between blocks.
104-
// The value varies based on the hard fork:
105-
// - Pre-Delhi: 8 (default)
106-
// - Post-Delhi: 16
107-
// - Post-Bhilai: 64
108-
// If borConfig is nil, returns the default value of 8.
109-
func baseFeeChangeDenominator(config *params.ChainConfig, number *big.Int) uint64 {
110-
if config.Bor == nil {
111-
return params.DefaultBaseFeeChangeDenominator
112-
}
113-
if config.Bor.IsBhilai(number) {
114-
return params.BaseFeeChangeDenominatorPostBhilai
115-
} else if config.Bor.IsDelhi(number) {
116-
return params.BaseFeeChangeDenominatorPostDelhi
117-
} else {
118-
return params.DefaultBaseFeeChangeDenominator
119-
}
120-
}
121-
122165
// calcParentGasTarget calculates the target gas based on parent block gas limit. Earlier
123166
// it was derived by `ElasticityMultiplier` as it had an integer multiplier value. Post
124-
// dandeli HF, a percentage value will be used to calculate the gas target.
167+
// Dandeli HF, a percentage value is used to calculate the gas target (validated with fallback to default).
125168
func calcParentGasTarget(config *params.ChainConfig, parent *types.Header) uint64 {
126169
if config.Bor != nil && config.Bor.IsDandeli(parent.Number) {
127-
return parent.GasLimit * params.TargetGasPercentagePostDandeli / 100
170+
// Use helper function which validates and provides defaults
171+
targetPercentage := config.Bor.GetTargetGasPercentage(parent.Number)
172+
return parent.GasLimit * targetPercentage / 100
128173
}
129174
return parent.GasLimit / config.ElasticityMultiplier()
130175
}

0 commit comments

Comments
 (0)