Skip to content

Commit

Permalink
op-node/rollup/derive: Implement pipeline stage multiplexing
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianst committed Oct 22, 2024
1 parent 5808985 commit 7f8b473
Show file tree
Hide file tree
Showing 23 changed files with 857 additions and 119 deletions.
76 changes: 75 additions & 1 deletion op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,81 @@ func offsetToUpgradeTime(offset *hexutil.Uint64, genesisTime uint64) *uint64 {
return &v
}

func (d *UpgradeScheduleDeployConfig) ForkTimeOffset(fork rollup.ForkName) *uint64 {
switch fork {
case rollup.Regolith:
return (*uint64)(d.L2GenesisRegolithTimeOffset)
case rollup.Canyon:
return (*uint64)(d.L2GenesisCanyonTimeOffset)
case rollup.Delta:
return (*uint64)(d.L2GenesisDeltaTimeOffset)
case rollup.Ecotone:
return (*uint64)(d.L2GenesisEcotoneTimeOffset)
case rollup.Fjord:
return (*uint64)(d.L2GenesisFjordTimeOffset)
case rollup.Granite:
return (*uint64)(d.L2GenesisGraniteTimeOffset)
case rollup.Holocene:
return (*uint64)(d.L2GenesisHoloceneTimeOffset)
case rollup.Interop:
return (*uint64)(d.L2GenesisInteropTimeOffset)
default:
panic(fmt.Sprintf("unknown fork: %s", fork))
}
}

func (d *UpgradeScheduleDeployConfig) SetForkTimeOffset(fork rollup.ForkName, offset *uint64) {
switch fork {
case rollup.Regolith:
d.L2GenesisRegolithTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Canyon:
d.L2GenesisCanyonTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Delta:
d.L2GenesisDeltaTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Ecotone:
d.L2GenesisEcotoneTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Fjord:
d.L2GenesisFjordTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Granite:
d.L2GenesisGraniteTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Holocene:
d.L2GenesisHoloceneTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Interop:
d.L2GenesisInteropTimeOffset = (*hexutil.Uint64)(offset)
default:
panic(fmt.Sprintf("unknown fork: %s", fork))
}
}

var scheduleableForks = rollup.ForksFrom(rollup.Regolith)

// ActivateForkAtOffset activates the given fork at the given offset. Previous forks are activated
// at genesis and later forks are deactivated.
// If multiple forks should be activated at a later time than genesis, first call
// ActivateForkAtOffset with the earliest fork and then SetForkTimeOffset to individually set later
// forks.
func (d *UpgradeScheduleDeployConfig) ActivateForkAtOffset(fork rollup.ForkName, offset *uint64) {
if !rollup.IsValidFork(fork) || fork == rollup.Bedrock {
panic(fmt.Sprintf("invalid fork: %s", fork))
}
ts := new(uint64)
for i, f := range scheduleableForks {
if f == fork {
d.SetForkTimeOffset(fork, offset)
ts = nil
} else {
d.SetForkTimeOffset(scheduleableForks[i], ts)
}
}
}

// ActivateForkAtOffset activates the given fork, and all previous forks, at genesis.
// Later forks are deactivated.
// See also [ActivateForkAtOffset].
func (d *UpgradeScheduleDeployConfig) ActivateForkAtGenesis(fork rollup.ForkName) {
d.ActivateForkAtOffset(fork, new(uint64))
}

func (d *UpgradeScheduleDeployConfig) RegolithTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisRegolithTimeOffset, genesisTime)
}
Expand Down Expand Up @@ -402,7 +477,6 @@ func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 {
}

func (d *UpgradeScheduleDeployConfig) AllocMode(genesisTime uint64) L2AllocsMode {

forks := d.forks()
for i := len(forks) - 1; i >= 0; i-- {
if forkTime := offsetToUpgradeTime(forks[i].L2GenesisTimeOffset, genesisTime); forkTime != nil && *forkTime == 0 {
Expand Down
59 changes: 55 additions & 4 deletions op-chain-ops/genesis/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)

Expand Down Expand Up @@ -45,7 +46,10 @@ func TestRegolithTimeZero(t *testing.T) {
config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisRegolithTimeOffset: &regolithOffset}}}
L2GenesisRegolithTimeOffset: &regolithOffset,
},
},
}
require.Equal(t, uint64(0), *config.RegolithTime(1234))
}

Expand All @@ -54,7 +58,10 @@ func TestRegolithTimeAsOffset(t *testing.T) {
config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisRegolithTimeOffset: &regolithOffset}}}
L2GenesisRegolithTimeOffset: &regolithOffset,
},
},
}
require.Equal(t, uint64(1500+5000), *config.RegolithTime(5000))
}

Expand All @@ -63,7 +70,10 @@ func TestCanyonTimeZero(t *testing.T) {
config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisCanyonTimeOffset: &canyonOffset}}}
L2GenesisCanyonTimeOffset: &canyonOffset,
},
},
}
require.Equal(t, uint64(0), *config.CanyonTime(1234))
}

Expand All @@ -72,7 +82,10 @@ func TestCanyonTimeOffset(t *testing.T) {
config := &DeployConfig{
L2InitializationConfig: L2InitializationConfig{
UpgradeScheduleDeployConfig: UpgradeScheduleDeployConfig{
L2GenesisCanyonTimeOffset: &canyonOffset}}}
L2GenesisCanyonTimeOffset: &canyonOffset,
},
},
}
require.Equal(t, uint64(1234+1500), *config.CanyonTime(1234))
}

Expand Down Expand Up @@ -124,3 +137,41 @@ func TestL1Deployments(t *testing.T) {
// One that doesn't exist returns empty string
require.Equal(t, "", deployments.GetName(common.Address{19: 0xff}))
}

// This test guarantees that getters and setters for all forks are present.
func TestUpgradeScheduleDeployConfig_ForkGettersAndSetters(t *testing.T) {
var d UpgradeScheduleDeployConfig
for i, fork := range rollup.ForksFrom(rollup.Regolith) {
require.Nil(t, d.ForkTimeOffset(fork))
offset := uint64(i * 42)
d.SetForkTimeOffset(fork, &offset)
require.Equal(t, offset, *d.ForkTimeOffset(fork))
}
}

func TestUpgradeScheduleDeployConfig_ActivateForkAtOffset(t *testing.T) {
var d UpgradeScheduleDeployConfig
ts := uint64(42)
t.Run("invalid", func(t *testing.T) {
require.Panics(t, func() { d.ActivateForkAtOffset(rollup.Bedrock, &ts) })
})

t.Run("regolith", func(t *testing.T) {
d.ActivateForkAtOffset(rollup.Regolith, &ts)
require.EqualValues(t, &ts, d.L2GenesisRegolithTimeOffset)
for _, fork := range scheduleableForks[1:] {
require.Nil(t, d.ForkTimeOffset(fork))
}
})

t.Run("ecotone", func(t *testing.T) {
d.ActivateForkAtOffset(rollup.Ecotone, &ts)
require.EqualValues(t, &ts, d.L2GenesisEcotoneTimeOffset)
for _, fork := range scheduleableForks[:3] {
require.Zero(t, *d.ForkTimeOffset(fork))
}
for _, fork := range scheduleableForks[4:] {
require.Nil(t, d.ForkTimeOffset(fork))
}
})
}
76 changes: 76 additions & 0 deletions op-e2e/actions/helpers/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package helpers

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"

"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)

type Env struct {
Log log.Logger
Logs *testlog.CapturingHandler

SetupData *e2eutils.SetupData

Miner *L1Miner
Seq *L2Sequencer
SeqEngine *L2Engine
Verifier *L2Verifier
VerifEngine *L2Engine
Batcher *L2Batcher
}

type EnvOpt struct {
DeployConfigMod func(*genesis.DeployConfig)
}

func WithActiveFork(fork rollup.ForkName, offset *uint64) EnvOpt {
return EnvOpt{
DeployConfigMod: func(d *genesis.DeployConfig) {
d.ActivateForkAtOffset(fork, offset)
},
}
}

func WithActiveGenesisFork(fork rollup.ForkName) EnvOpt {
return WithActiveFork(fork, new(uint64))
}

func SetupEnv(t StatefulTesting, opts ...EnvOpt) (env Env) {
dp := e2eutils.MakeDeployParams(t, DefaultRollupTestParams())

log, logs := testlog.CaptureLogger(t, log.LevelDebug)
env.Log, env.Logs = log, logs

dp.DeployConfig.ActivateForkAtGenesis(rollup.LatestFork)
for _, opt := range opts {
if dcMod := opt.DeployConfigMod; dcMod != nil {
dcMod(dp.DeployConfig)
}
}

sd := e2eutils.Setup(t, dp, DefaultAlloc)
env.SetupData = sd
env.Miner, env.SeqEngine, env.Seq = SetupSequencerTest(t, sd, log)
env.Miner.ActL1SetFeeRecipient(common.Address{'A'})
env.VerifEngine, env.Verifier = SetupVerifier(t, sd, log, env.Miner.L1Client(t, sd.RollupCfg), env.Miner.BlobStore(), &sync.Config{})
rollupSeqCl := env.Seq.RollupClient()
env.Batcher = NewL2Batcher(log, sd.RollupCfg, DefaultBatcherCfg(dp),
rollupSeqCl, env.Miner.EthClient(), env.SeqEngine.EthClient(), env.SeqEngine.EngineClient(t, sd.RollupCfg))

return
}

func (env Env) ActBatchSubmitAllAndMine(t Testing) (l1InclusionBlock *types.Block) {
env.Batcher.ActSubmitAll(t)
batchTx := env.Batcher.LastSubmitted
env.Miner.ActL1StartBlock(12)(t)
env.Miner.ActL1IncludeTxByHash(batchTx.Hash())(t)
return env.Miner.ActL1EndBlock(t)
}
9 changes: 5 additions & 4 deletions op-e2e/actions/helpers/l1_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,10 @@ func (s *L1Miner) ActL1SetFeeRecipient(coinbase common.Address) {
}

// ActL1EndBlock finishes the new L1 block, and applies it to the chain as unsafe block
func (s *L1Miner) ActL1EndBlock(t Testing) {
func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block {
if !s.l1Building {
t.InvalidAction("cannot end L1 block when not building block")
return
return nil
}

s.l1Building = false
Expand Down Expand Up @@ -253,11 +253,12 @@ func (s *L1Miner) ActL1EndBlock(t Testing) {
if err != nil {
t.Fatalf("failed to insert block into l1 chain")
}
return block
}

func (s *L1Miner) ActEmptyBlock(t Testing) {
func (s *L1Miner) ActEmptyBlock(t Testing) *types.Block {
s.ActL1StartBlock(12)(t)
s.ActL1EndBlock(t)
return s.ActL1EndBlock(t)
}

func (s *L1Miner) Close() error {
Expand Down
37 changes: 18 additions & 19 deletions op-e2e/actions/helpers/l2_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ type L2Sequencer struct {

func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, blobSrc derive.L1BlobsFetcher,
altDASrc driver.AltDAIface, eng L2API, cfg *rollup.Config, seqConfDepth uint64,
interopBackend interop.InteropBackend) *L2Sequencer {
interopBackend interop.InteropBackend,
) *L2Sequencer {
ver := NewL2Verifier(t, log, l1, blobSrc, altDASrc, eng, cfg, &sync.Config{}, safedb.Disabled, interopBackend)
attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng)
seqConfDepthL1 := confdepth.NewConfDepth(seqConfDepth, ver.syncStatus.L1Head, l1)
Expand Down Expand Up @@ -130,6 +131,11 @@ func (s *L2Sequencer) ActL2EndBlock(t Testing) {
"sync status must be accurate after block building")
}

func (s *L2Sequencer) ActL2EmptyBlock(t Testing) {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
}

// ActL2KeepL1Origin makes the sequencer use the current L1 origin, even if the next origin is available.
func (s *L2Sequencer) ActL2KeepL1Origin(t Testing) {
parent := s.engine.UnsafeL2Head()
Expand All @@ -143,17 +149,15 @@ func (s *L2Sequencer) ActL2KeepL1Origin(t Testing) {
func (s *L2Sequencer) ActBuildToL1Head(t Testing) {
for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number {
s.ActL2PipelineFull(t)
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

// ActBuildToL1HeadUnsafe builds empty blocks until (incl.) the L1 head becomes the L1 origin of the L2 head
func (s *L2Sequencer) ActBuildToL1HeadUnsafe(t Testing) {
for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number {
// Note: the derivation pipeline does not run, we are just sequencing a block on top of the existing L2 chain.
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

Expand All @@ -166,8 +170,7 @@ func (s *L2Sequencer) ActBuildToL1HeadExcl(t Testing) {
if nextOrigin.Number >= s.syncStatus.L1Head().Number {
break
}
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

Expand All @@ -180,44 +183,40 @@ func (s *L2Sequencer) ActBuildToL1HeadExclUnsafe(t Testing) {
if nextOrigin.Number >= s.syncStatus.L1Head().Number {
break
}
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

func (s *L2Sequencer) ActBuildL2ToTime(t Testing, target uint64) {
for s.L2Unsafe().Time < target {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

func (s *L2Sequencer) ActBuildL2ToEcotone(t Testing) {
require.NotNil(t, s.RollupCfg.EcotoneTime, "cannot activate Ecotone when it is not scheduled")
for s.L2Unsafe().Time < *s.RollupCfg.EcotoneTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

func (s *L2Sequencer) ActBuildL2ToFjord(t Testing) {
require.NotNil(t, s.RollupCfg.FjordTime, "cannot activate FjordTime when it is not scheduled")
for s.L2Unsafe().Time < *s.RollupCfg.FjordTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

func (s *L2Sequencer) ActBuildL2ToGranite(t Testing) {
require.NotNil(t, s.RollupCfg.GraniteTime, "cannot activate GraniteTime when it is not scheduled")
for s.L2Unsafe().Time < *s.RollupCfg.GraniteTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}

func (s *L2Sequencer) ActBuildL2ToHolocene(t Testing) {
require.NotNil(t, s.RollupCfg.HoloceneTime, "cannot activate HoloceneTime when it is not scheduled")
for s.L2Unsafe().Time < *s.RollupCfg.HoloceneTime {
s.ActL2StartBlock(t)
s.ActL2EndBlock(t)
s.ActL2EmptyBlock(t)
}
}
Loading

0 comments on commit 7f8b473

Please sign in to comment.