Skip to content

Commit 36501a7

Browse files
authored
core,params: Check implied fork times during genesis setup (#587)
The chain config must have consistent fork times set when OP Stack forks are active that imply Ethereum forks. This is already guaranteed for chains in the superchain-registry which are loaded via the network flag, or when using the override flags. But checks were missing when a custom genesis.json file is used. So it could happen that a chain operator misconfigures to only set e.g. Isthmus but not the Prague time. Fixes #586
1 parent d1b66c4 commit 36501a7

File tree

3 files changed

+179
-0
lines changed

3 files changed

+179
-0
lines changed

core/genesis.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,13 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
375375
cfg.InteropTime = o.OverrideOptimismInterop
376376
}
377377

378+
// We check for validity after applying the overrides, even if there weren't any.
379+
// This has the added benefit that the check always happens when
380+
// applying overrides, which is at the right places during genesis setup.
381+
if verr := cfg.CheckOptimismValidity(); verr != nil {
382+
return fmt.Errorf("OP-Stack invalidity after applying overrides: %w", verr)
383+
}
384+
378385
return cfg.CheckConfigForkOrder()
379386
}
380387

params/config.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,3 +1306,46 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
13061306
func (c *ChainConfig) HasOptimismWithdrawalsRoot(blockTime uint64) bool {
13071307
return c.IsOptimismIsthmus(blockTime)
13081308
}
1309+
1310+
// CheckOptimismValidity checks for OP Stack chains:
1311+
// - the EIP159 params are set
1312+
// - the Ethereum forks are set to the same time as the OP Stack forks that imply them
1313+
func (c *ChainConfig) CheckOptimismValidity() error {
1314+
if c.Optimism == nil {
1315+
return nil
1316+
}
1317+
1318+
if c.Optimism.EIP1559Denominator == 0 {
1319+
return errors.New("zero EIP1559Denominator")
1320+
}
1321+
if c.Optimism.EIP1559Elasticity == 0 {
1322+
return errors.New("zero EIP1559Elasticity")
1323+
}
1324+
if c.CanyonTime != nil && (c.Optimism.EIP1559DenominatorCanyon == nil || *c.Optimism.EIP1559DenominatorCanyon == 0) {
1325+
return errors.New("missing or zero EIP1559DenominatorCanyon")
1326+
}
1327+
1328+
if !equalPtrValues(c.ShanghaiTime, c.CanyonTime) {
1329+
return fmt.Errorf("ShanghaiTime (%s) must equal CanyonTime (%s)", ptrValueString(c.ShanghaiTime), ptrValueString(c.CanyonTime))
1330+
}
1331+
if !equalPtrValues(c.CancunTime, c.EcotoneTime) {
1332+
return fmt.Errorf("CancunTime (%s) must equal EcotoneTime (%s)", ptrValueString(c.CancunTime), ptrValueString(c.EcotoneTime))
1333+
}
1334+
if !equalPtrValues(c.PragueTime, c.IsthmusTime) {
1335+
return fmt.Errorf("PragueTime (%s) must equal IsthmusTime (%s)", ptrValueString(c.PragueTime), ptrValueString(c.IsthmusTime))
1336+
}
1337+
1338+
return nil
1339+
}
1340+
1341+
func equalPtrValues[T comparable](a, b *T) bool {
1342+
// also captures nil == nil
1343+
return a == b || (a != nil && b != nil && *a == *b)
1344+
}
1345+
1346+
func ptrValueString[T any](t *T) string {
1347+
if t == nil {
1348+
return "<nil>"
1349+
}
1350+
return fmt.Sprintf("%v", *t)
1351+
}

params/config_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,132 @@ func TestConfigRulesRegolith(t *testing.T) {
235235
t.Errorf("expected %v to be regolith", stamp)
236236
}
237237
}
238+
239+
func TestCheckOptimismValidity(t *testing.T) {
240+
validOpConfig := &OptimismConfig{
241+
EIP1559Denominator: 10,
242+
EIP1559Elasticity: 50,
243+
EIP1559DenominatorCanyon: newUint64(250),
244+
}
245+
246+
tests := []struct {
247+
name string
248+
config *ChainConfig
249+
wantErr *string
250+
}{
251+
{
252+
name: "valid",
253+
config: &ChainConfig{
254+
Optimism: validOpConfig,
255+
CanyonTime: newUint64(100),
256+
ShanghaiTime: newUint64(100),
257+
CancunTime: newUint64(200),
258+
EcotoneTime: newUint64(200),
259+
PragueTime: newUint64(300),
260+
IsthmusTime: newUint64(300),
261+
},
262+
wantErr: nil,
263+
},
264+
{
265+
name: "zero EIP1559Denominator",
266+
config: &ChainConfig{
267+
Optimism: &OptimismConfig{
268+
EIP1559Denominator: 0,
269+
EIP1559Elasticity: 50,
270+
},
271+
},
272+
wantErr: ptr("zero EIP1559Denominator"),
273+
},
274+
{
275+
name: "zero EIP1559Elasticity",
276+
config: &ChainConfig{
277+
Optimism: &OptimismConfig{
278+
EIP1559Denominator: 10,
279+
EIP1559Elasticity: 0,
280+
},
281+
},
282+
wantErr: ptr("zero EIP1559Elasticity"),
283+
},
284+
{
285+
name: "missing EIP1559DenominatorCanyon",
286+
config: &ChainConfig{
287+
Optimism: &OptimismConfig{
288+
EIP1559Denominator: 10,
289+
EIP1559Elasticity: 50,
290+
},
291+
CanyonTime: newUint64(100),
292+
},
293+
wantErr: ptr("missing or zero EIP1559DenominatorCanyon"),
294+
},
295+
{
296+
name: "ShanghaiTime not equal to CanyonTime",
297+
config: &ChainConfig{
298+
Optimism: validOpConfig,
299+
ShanghaiTime: newUint64(100),
300+
CanyonTime: newUint64(200),
301+
},
302+
wantErr: ptr("ShanghaiTime (100) must equal CanyonTime (200)"),
303+
},
304+
{
305+
name: "CancunTime not equal to EcotoneTime",
306+
config: &ChainConfig{
307+
Optimism: validOpConfig,
308+
CancunTime: newUint64(200),
309+
EcotoneTime: newUint64(300),
310+
},
311+
wantErr: ptr("CancunTime (200) must equal EcotoneTime (300)"),
312+
},
313+
{
314+
name: "PragueTime not equal to IsthmusTime",
315+
config: &ChainConfig{
316+
Optimism: validOpConfig,
317+
PragueTime: newUint64(300),
318+
IsthmusTime: newUint64(400),
319+
},
320+
wantErr: ptr("PragueTime (300) must equal IsthmusTime (400)"),
321+
},
322+
{
323+
name: "nil ShanghaiTime",
324+
config: &ChainConfig{
325+
Optimism: validOpConfig,
326+
CanyonTime: newUint64(200),
327+
},
328+
wantErr: ptr("ShanghaiTime (<nil>) must equal CanyonTime (200)"),
329+
},
330+
{
331+
name: "nil CancunTime",
332+
config: &ChainConfig{
333+
Optimism: validOpConfig,
334+
EcotoneTime: newUint64(300),
335+
},
336+
wantErr: ptr("CancunTime (<nil>) must equal EcotoneTime (300)"),
337+
},
338+
{
339+
name: "nil PragueTime",
340+
config: &ChainConfig{
341+
Optimism: &OptimismConfig{
342+
EIP1559Denominator: 10,
343+
EIP1559Elasticity: 50,
344+
EIP1559DenominatorCanyon: newUint64(250),
345+
},
346+
IsthmusTime: newUint64(400),
347+
},
348+
wantErr: ptr("PragueTime (<nil>) must equal IsthmusTime (400)"),
349+
},
350+
}
351+
352+
for _, tt := range tests {
353+
t.Run(tt.name, func(t *testing.T) {
354+
err := tt.config.CheckOptimismValidity()
355+
if tt.wantErr != nil {
356+
require.EqualError(t, err, *tt.wantErr)
357+
} else {
358+
require.NoError(t, err)
359+
}
360+
})
361+
}
362+
}
363+
364+
func ptr[T any](t T) *T {
365+
return &t
366+
}

0 commit comments

Comments
 (0)