Skip to content
Draft
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
11 changes: 11 additions & 0 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,12 @@ type UpgradeScheduleDeployConfig struct {
// Set it to 0 to activate at genesis. Nil to disable the PectraBlobSchedule fix.
L2GenesisPectraBlobScheduleTimeOffset *hexutil.Uint64 `json:"l2GenesisPectraBlobScheduleTimeOffset,omitempty"`

// L2GenesisUpgrade18TimeOffset is the number of seconds after genesis block that the
// Upgrade 18 (CGT v2) migration activates. Nil to disable Upgrade 18. Activation at genesis
// is meaningless: the migration only happens at the boundary between a pre-fork and a
// post-fork block.
L2GenesisUpgrade18TimeOffset *hexutil.Uint64 `json:"l2GenesisUpgrade18TimeOffset,omitempty"`

// When Cancun activates. Relative to L1 genesis.
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
// When Prague activates. Relative to L1 genesis.
Expand Down Expand Up @@ -556,6 +562,10 @@ func (d *UpgradeScheduleDeployConfig) PectraBlobScheduleTime(genesisTime uint64)
return offsetToUpgradeTime(d.L2GenesisPectraBlobScheduleTimeOffset, genesisTime)
}

func (d *UpgradeScheduleDeployConfig) Upgrade18Time(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisUpgrade18TimeOffset, genesisTime)
}

func (d *UpgradeScheduleDeployConfig) IsthmusTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisIsthmusTimeOffset, genesisTime)
}
Expand Down Expand Up @@ -1145,6 +1155,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa
PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime),
IsthmusTime: d.IsthmusTime(l1StartTime),
JovianTime: d.JovianTime(l1StartTime),
Upgrade18Time: d.Upgrade18Time(l1StartTime),
KarstTime: d.KarstTime(l1StartTime),
InteropTime: d.InteropTime(l1StartTime),
AltDAConfig: altDA,
Expand Down
13 changes: 13 additions & 0 deletions op-chain-ops/genesis/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ func TestCanyonTimeOffset(t *testing.T) {
require.Equal(t, uint64(1234+1500), *config.CanyonTime(1234))
}

func TestUpgrade18TimeOffset(t *testing.T) {
upgrade18Offset := hexutil.Uint64(1500)
config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisUpgrade18TimeOffset: &upgrade18Offset,
},
},
}
require.Equal(t, uint64(1234+1500), *config.Upgrade18Time(1234))
require.Nil(t, (&DeployConfig{}).Upgrade18Time(1234))
}

func TestForksCantActivateAtSamePostGenesisBlock(t *testing.T) {
postGenesisOffset := uint64(1500)
config := &UpgradeScheduleDeployConfig{}
Expand Down
5 changes: 5 additions & 0 deletions op-core/forks/forks.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const (
// Optional Forks - not part of mainline
PectraBlobSchedule Name = "pectrablobschedule"

// Celo: Upgrade 18 (CGT v2) is a one-shot migration trigger for the live Celo networks;
// fresh CGT chains start from a v2 genesis and never activate it.
Upgrade18 Name = "upgrade18"

None Name = ""
)

Expand All @@ -46,6 +50,7 @@ var All = []Name{
// AllOpt lists all optional forks in chronological order.
var AllOpt = []Name{
PectraBlobSchedule,
Upgrade18,
// ADD NEW OPTIONAL FORKS HERE!
}

Expand Down
30 changes: 30 additions & 0 deletions op-node/rollup/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ type Config struct {
// BatchAuthenticatorAddress is the L1 address of the BatchAuthenticator contract whose
// BatchInfoAuthenticated(bytes32,address) events the derivation pipeline scans post-Espresso.
BatchAuthenticatorAddress common.Address `json:"batch_authenticator_address,omitempty,omitzero"`

// Celo: Upgrade18Time sets the activation time of the Upgrade 18 (CGT v2) migration, an
// irregular state transition performed by the execution layer; the op-node has no boundary
// behavior of its own. The JSON key must stay identical to celo-kona's `CeloRollupConfig`,
// which parses the same rollup.json. The field name is provisional until the final
// activation trigger is decided (celo-blockchain-planning#1407).
Upgrade18Time *uint64 `json:"upgrade18_time,omitempty"`
}

// ValidateL1Config checks L1 config variables for errors.
Expand Down Expand Up @@ -537,6 +544,12 @@ func (c *Config) IsCel2(timestamp uint64) bool {
return c.Cel2Time != nil && timestamp >= *c.Cel2Time
}

// IsUpgrade18 returns true if the Upgrade 18 (CGT v2) migration is active at or past the given
// timestamp.
func (c *Config) IsUpgrade18(timestamp uint64) bool {
return c.IsForkActive(forks.Upgrade18, timestamp)
}

func (c *Config) IsRegolithActivationBlock(l2BlockTime uint64) bool {
return c.IsRegolith(l2BlockTime) &&
l2BlockTime >= c.BlockTime &&
Expand Down Expand Up @@ -617,6 +630,15 @@ func (c *Config) IsInteropActivationBlock(l2BlockTime uint64) bool {
!c.IsInterop(l2BlockTime-c.BlockTime)
}

// IsUpgrade18ActivationBlock returns whether the specified block is the first block subject to
// the Upgrade 18 (CGT v2) migration. Activation at genesis does not count: the migration only
// exists as a boundary between a pre-fork and a post-fork block.
func (c *Config) IsUpgrade18ActivationBlock(l2BlockTime uint64) bool {
return c.IsUpgrade18(l2BlockTime) &&
l2BlockTime >= c.BlockTime &&
!c.IsUpgrade18(l2BlockTime-c.BlockTime)
}

func (c *Config) ActivationTime(fork ForkName) *uint64 {
// NEW FORKS MUST BE ADDED HERE
switch fork {
Expand Down Expand Up @@ -646,6 +668,8 @@ func (c *Config) ActivationTime(fork ForkName) *uint64 {
// Optional forks
case forks.PectraBlobSchedule:
return c.PectraBlobScheduleTime
case forks.Upgrade18:
return c.Upgrade18Time

default:
panic(fmt.Sprintf("unknown fork: %v", fork))
Expand Down Expand Up @@ -681,6 +705,8 @@ func (c *Config) SetActivationTime(fork ForkName, timestamp *uint64) {
// Optional forks
case forks.PectraBlobSchedule:
c.PectraBlobScheduleTime = timestamp
case forks.Upgrade18:
c.Upgrade18Time = timestamp

default:
panic(fmt.Sprintf("unknown fork: %v", fork))
Expand Down Expand Up @@ -903,6 +929,10 @@ func (c *Config) forEachFork(callback func(name string, logName string, time *ui
}
callback("Isthmus", "isthmus_time", c.IsthmusTime)
callback("Jovian", "jovian_time", c.JovianTime)
if c.Upgrade18Time != nil {
// only report if config is set
callback("Upgrade 18 (CGT v2)", "upgrade18_time", c.Upgrade18Time)
}
callback("Karst", "karst_time", c.KarstTime)
callback("Interop", "interop_time", c.InteropTime)
if c.EspressoTime != nil {
Expand Down
53 changes: 53 additions & 0 deletions op-node/rollup/types_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rollup

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -1148,3 +1149,55 @@ func TestConfig_ActivateAtGenesis(t *testing.T) {
require.Zero(t, cfg)
})
}

// TestConfig_Upgrade18 covers the optional Celo Upgrade 18 (CGT v2) migration fork: boundary
// semantics of the predicates and the ActivationTime/SetActivationTime wiring (which also backs
// the --override.upgrade18 CLI flag).
func TestConfig_Upgrade18(t *testing.T) {
cfg := Config{BlockTime: 2}

// Unset: never active.
require.False(t, cfg.IsUpgrade18(^uint64(0)))
require.Nil(t, cfg.ActivationTime(forks.Upgrade18))

ts := uint64(1000)
cfg.SetActivationTime(forks.Upgrade18, &ts)
require.Equal(t, &ts, cfg.Upgrade18Time)
require.Equal(t, &ts, cfg.ActivationTime(forks.Upgrade18))
require.False(t, cfg.IsUpgrade18(999))
require.True(t, cfg.IsUpgrade18(1000))
require.True(t, cfg.IsForkActive(forks.Upgrade18, 1000))

// The activation block is the first post-fork block, and only that one.
require.False(t, cfg.IsUpgrade18ActivationBlock(998))
require.True(t, cfg.IsUpgrade18ActivationBlock(1000))
require.False(t, cfg.IsUpgrade18ActivationBlock(1002))

// Activation at genesis is not an activation block: the migration only exists as a
// boundary between a pre-fork and a post-fork block.
genesisActive := Config{BlockTime: 2, Upgrade18Time: ptr.Zero64}
require.False(t, genesisActive.IsUpgrade18ActivationBlock(0))
require.False(t, genesisActive.IsUpgrade18ActivationBlock(2))
}

// TestConfig_Upgrade18JSON pins the exact JSON key of Upgrade18Time: celo-kona's
// `CeloRollupConfig` keys the migration off `upgrade18_time` in the same rollup.json, so the two
// sides must stay key-identical. ParseRollupConfig rejects unknown fields, which makes this the
// cross-client contract test — before the field existed, op-node refused a rollup.json
// carrying it.
func TestConfig_Upgrade18JSON(t *testing.T) {
config := randConfig()
ts := uint64(4242)
config.Upgrade18Time = &ts

data, err := json.Marshal(config)
require.NoError(t, err)

var keys map[string]json.RawMessage
require.NoError(t, json.Unmarshal(data, &keys))
require.Contains(t, keys, "upgrade18_time")

var roundTripped Config
require.NoError(t, roundTripped.ParseRollupConfig(bytes.NewReader(data)))
require.Equal(t, config, &roundTripped)
}