diff --git a/espresso/environment/optitmism_espresso_test_helpers.go b/espresso/environment/optitmism_espresso_test_helpers.go index f545f61de2cbf..56865f101789c 100644 --- a/espresso/environment/optitmism_espresso_test_helpers.go +++ b/espresso/environment/optitmism_espresso_test_helpers.go @@ -394,7 +394,6 @@ func (l *EspressoDevNodeLauncherDocker) StartDevNet(ctx context.Context, t *test system, err := sysConfig.Start( t, - startOptions..., ) @@ -863,6 +862,7 @@ func launchEspressoDevNodeStartOption(ct *DevNetLauncherContext) e2esys.StartOpt } ct.EspressoDevNode = espressoDevNode + c.UseEspresso = true c.EspressoUrls = espressoDevNode.espressoUrls c.LogConfig.Level = slog.LevelDebug c.TestingEspressoBatcherPrivateKey = "0x" + config.ESPRESSO_PRE_APPROVED_BATCHER_PRIVATE_KEY diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index 440649bad1a89..4ffa9588ad5fe 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -126,6 +126,7 @@ type CLIConfig struct { RPC oprpc.CLIConfig AltDA altda.CLIConfig + UseEspresso bool EspressoUrls []string EspressoLightClientAddr string TestingEspressoBatcherPrivateKey string diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 121fc54a802d5..c9c6f27ed6bd2 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -140,6 +140,20 @@ func (l *BatchSubmitter) EspressoStreamer() *espresso.EspressoStreamer[derive.Es return &l.streamer } +// isEspressoEnabled returns true if Espresso integration is currently active based on the timestamp. +// It checks both that Espresso client is configured and that the activation timestamp has been reached. +func (l *BatchSubmitter) isEspressoEnabled() bool { + // First check if Espresso is configured (we need the client and the authenticator address) + if l.Espresso == nil || l.RollupConfig.BatchAuthenticatorAddress == (common.Address{}) { + l.Log.Info("Espresso is not enabled", "espresso", l.Espresso, "batchAuthenticatorAddress", l.RollupConfig.BatchAuthenticatorAddress) + return false + } + // Check if the EspressoCeloIntegration fork is active based on current timestamp + currentTime := uint64(time.Now().Unix()) + l.Log.Info("Checking if EspressoCeloIntegration is active", "currentTime", currentTime, "espressoCeloIntegrationTime", l.RollupConfig.EspressoCeloIntegrationTime) + return l.RollupConfig.IsEspressoCeloIntegration(currentTime) +} + // NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter { state := NewChannelManager(setup.Log, setup.Metr, setup.ChannelConfig, setup.RollupConfig) @@ -212,7 +226,8 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { l.Log.Warn("Throttling loop is DISABLED due to 0 throttle-threshold. This should not be disabled in prod.") } - if l.Config.UseEspresso { + // There are two ways to use Espresso, one is to enable `UseEspresso` to force the use, the other is to set the timestamp and it will be activated automatically after the timestamp. + if l.Config.UseEspresso || l.isEspressoEnabled() { err := l.registerBatcher(l.killCtx) if err != nil { @@ -778,7 +793,7 @@ func (l *BatchSubmitter) clearState(ctx context.Context) { l.channelMgrMutex.Lock() defer l.channelMgrMutex.Unlock() l.channelMgr.Clear(l1SafeOrigin) - if l.Config.UseEspresso { + if l.Config.UseEspresso || l.isEspressoEnabled() { l.streamer.Reset() } return true @@ -978,7 +993,8 @@ type TxSender[T any] interface { // sendTx uses the txmgr queue to send the given transaction candidate after setting its // gaslimit. It will block if the txmgr queue has reached its MaxPendingTransactions limit. func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef], daGroup *errgroup.Group) { - if l.Config.UseEspresso && !isCancel { + // There are two ways to use Espresso, one is to enable `UseEspresso` to force the use, the other is to set the timestamp and it will be activated automatically after the timestamp. + if (l.Config.UseEspresso || l.isEspressoEnabled()) && !isCancel { goroutineSpawned := daGroup.TryGo( func() error { l.sendEspressoTx(txdata, isCancel, candidate, queue, receiptsCh) diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index 8c97f913fc37c..a7f5a4bb79768 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -146,6 +146,7 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, } opts = append(optsFromRPC, opts...) + bs.UseEspresso = cfg.UseEspresso if len(cfg.EspressoUrls) > 0 { client, err := espressoClient.NewMultipleNodesClient(cfg.EspressoUrls) if err != nil { @@ -157,7 +158,6 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, return fmt.Errorf("failed to create Espresso light client") } bs.EspressoLightClient = espressoLightClient - bs.UseEspresso = true if err := bs.initKeyPair(); err != nil { return fmt.Errorf("failed to create key pair for batcher: %w", err) } @@ -294,6 +294,7 @@ func (bs *BatcherService) initRollupConfig(ctx context.Context) error { return fmt.Errorf("failed to retrieve rollup config: %w", err) } bs.RollupConfig = rollupConfig + if err := bs.RollupConfig.Check(); err != nil { return fmt.Errorf("invalid rollup config: %w", err) } diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 25a3d8b3a186f..8ba7b6b38ce7a 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -369,7 +369,9 @@ type UpgradeScheduleDeployConfig struct { // L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates. // Set it to 0 to activate at genesis. Nil to disable Interop. L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"` - + // L2GenesisEspressoCeloIntegrationTimeOffset is the number of seconds after genesis block that the EspressoCeloIntegration hard fork activates. + // Set it to 0 to activate at genesis. Nil to disable EspressoCelo. + L2GenesisEspressoCeloIntegrationTimeOffset *hexutil.Uint64 `json:"l2GenesisEspressoCeloIntegrationTimeOffset,omitempty"` // Optional Forks // L2GenesisPectraBlobScheduleTimeOffset is the number of seconds after genesis block that the PectraBlobSchedule fix activates. @@ -420,6 +422,8 @@ func (d *UpgradeScheduleDeployConfig) ForkTimeOffset(fork rollup.ForkName) *uint return (*uint64)(d.L2GenesisJovianTimeOffset) case rollup.Interop: return (*uint64)(d.L2GenesisInteropTimeOffset) + case rollup.EspressoCeloIntegration: + return (*uint64)(d.L2GenesisEspressoCeloIntegrationTimeOffset) default: panic(fmt.Sprintf("unknown fork: %s", fork)) } @@ -447,6 +451,8 @@ func (d *UpgradeScheduleDeployConfig) SetForkTimeOffset(fork rollup.ForkName, of d.L2GenesisJovianTimeOffset = (*hexutil.Uint64)(offset) case rollup.Interop: d.L2GenesisInteropTimeOffset = (*hexutil.Uint64)(offset) + case rollup.EspressoCeloIntegration: + d.L2GenesisEspressoCeloIntegrationTimeOffset = (*hexutil.Uint64)(offset) default: panic(fmt.Sprintf("unknown fork: %s", fork)) } @@ -525,6 +531,10 @@ func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisInteropTimeOffset, genesisTime) } +func (d *UpgradeScheduleDeployConfig) EspressoCeloIntegrationTime(genesisTime uint64) *uint64 { + return offsetToUpgradeTime(d.L2GenesisEspressoCeloIntegrationTimeOffset, genesisTime) +} + func (d *UpgradeScheduleDeployConfig) AllocMode(genesisTime uint64) L2AllocsMode { forks := d.forks() for i := len(forks) - 1; i >= 0; i-- { @@ -555,6 +565,7 @@ func (d *UpgradeScheduleDeployConfig) forks() []Fork { {L2GenesisTimeOffset: d.L2GenesisHoloceneTimeOffset, Name: string(L2AllocsHolocene)}, {L2GenesisTimeOffset: d.L2GenesisIsthmusTimeOffset, Name: string(L2AllocsIsthmus)}, {L2GenesisTimeOffset: d.L2GenesisJovianTimeOffset, Name: string(L2AllocsJovian)}, + {L2GenesisTimeOffset: d.L2GenesisEspressoCeloIntegrationTimeOffset, Name: string(L2AllocsEspressoCeloIntegration)}, } } @@ -565,6 +576,10 @@ func (d *UpgradeScheduleDeployConfig) Check(log log.Logger) error { return nil } if a == nil && b != nil { + // Allow espresso_celo_integration to work without jovian as prerequisite + if bName == string(L2AllocsEspressoCeloIntegration) && aName == string(L2AllocsJovian) { + return nil + } return fmt.Errorf("fork %s set (to %d), but prior fork %s missing", bName, *b, aName) } if a != nil && b == nil { @@ -1066,23 +1081,24 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa BatchInboxAddress: d.BatchInboxAddress, BatchAuthenticatorAddress: d.BatchAuthenticatorAddress, - DepositContractAddress: d.OptimismPortalProxy, - L1SystemConfigAddress: d.SystemConfigProxy, - RegolithTime: d.RegolithTime(l1StartTime), - CanyonTime: d.CanyonTime(l1StartTime), - DeltaTime: d.DeltaTime(l1StartTime), - EcotoneTime: d.EcotoneTime(l1StartTime), - FjordTime: d.FjordTime(l1StartTime), - GraniteTime: d.GraniteTime(l1StartTime), - HoloceneTime: d.HoloceneTime(l1StartTime), - PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime), - IsthmusTime: d.IsthmusTime(l1StartTime), - JovianTime: d.JovianTime(l1StartTime), - InteropTime: d.InteropTime(l1StartTime), - ProtocolVersionsAddress: d.ProtocolVersionsProxy, - AltDAConfig: altDA, - ChainOpConfig: chainOpConfig, - Cel2Time: d.RegolithTime(l1StartTime), + DepositContractAddress: d.OptimismPortalProxy, + L1SystemConfigAddress: d.SystemConfigProxy, + RegolithTime: d.RegolithTime(l1StartTime), + CanyonTime: d.CanyonTime(l1StartTime), + DeltaTime: d.DeltaTime(l1StartTime), + EcotoneTime: d.EcotoneTime(l1StartTime), + FjordTime: d.FjordTime(l1StartTime), + GraniteTime: d.GraniteTime(l1StartTime), + HoloceneTime: d.HoloceneTime(l1StartTime), + PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime), + IsthmusTime: d.IsthmusTime(l1StartTime), + JovianTime: d.JovianTime(l1StartTime), + InteropTime: d.InteropTime(l1StartTime), + ProtocolVersionsAddress: d.ProtocolVersionsProxy, + AltDAConfig: altDA, + ChainOpConfig: chainOpConfig, + Cel2Time: d.RegolithTime(l1StartTime), + EspressoCeloIntegrationTime: d.EspressoCeloIntegrationTime(l1StartTime), }, nil } diff --git a/op-chain-ops/genesis/layer_two.go b/op-chain-ops/genesis/layer_two.go index 0729eb770d409..705138ac7a9d8 100644 --- a/op-chain-ops/genesis/layer_two.go +++ b/op-chain-ops/genesis/layer_two.go @@ -23,13 +23,14 @@ type L2AllocsMode string type L2AllocsModeMap map[L2AllocsMode]*foundry.ForgeAllocs const ( - L2AllocsDelta L2AllocsMode = "delta" - L2AllocsEcotone L2AllocsMode = "ecotone" - L2AllocsFjord L2AllocsMode = "fjord" - L2AllocsGranite L2AllocsMode = "granite" - L2AllocsHolocene L2AllocsMode = "holocene" - L2AllocsIsthmus L2AllocsMode = "isthmus" - L2AllocsJovian L2AllocsMode = "jovian" + L2AllocsDelta L2AllocsMode = "delta" + L2AllocsEcotone L2AllocsMode = "ecotone" + L2AllocsFjord L2AllocsMode = "fjord" + L2AllocsGranite L2AllocsMode = "granite" + L2AllocsHolocene L2AllocsMode = "holocene" + L2AllocsIsthmus L2AllocsMode = "isthmus" + L2AllocsJovian L2AllocsMode = "jovian" + L2AllocsEspressoCeloIntegration L2AllocsMode = "espresso_celo_integration" ) var ( diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 22a276e2a3a7a..bb6e179d58aeb 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -248,19 +248,20 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* EIP1559DenominatorCanyon: 250, }, UpgradeScheduleDeployConfig: genesis.UpgradeScheduleDeployConfig{ - L2GenesisRegolithTimeOffset: new(hexutil.Uint64), - L2GenesisCanyonTimeOffset: new(hexutil.Uint64), - L2GenesisDeltaTimeOffset: new(hexutil.Uint64), - L2GenesisEcotoneTimeOffset: new(hexutil.Uint64), - L2GenesisFjordTimeOffset: new(hexutil.Uint64), - L2GenesisGraniteTimeOffset: new(hexutil.Uint64), - L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), - L2GenesisIsthmusTimeOffset: new(hexutil.Uint64), - L2GenesisJovianTimeOffset: nil, - L2GenesisInteropTimeOffset: new(hexutil.Uint64), - L1CancunTimeOffset: new(hexutil.Uint64), - L1PragueTimeOffset: new(hexutil.Uint64), - UseInterop: true, + L2GenesisRegolithTimeOffset: new(hexutil.Uint64), + L2GenesisCanyonTimeOffset: new(hexutil.Uint64), + L2GenesisDeltaTimeOffset: new(hexutil.Uint64), + L2GenesisEcotoneTimeOffset: new(hexutil.Uint64), + L2GenesisFjordTimeOffset: new(hexutil.Uint64), + L2GenesisGraniteTimeOffset: new(hexutil.Uint64), + L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), + L2GenesisIsthmusTimeOffset: new(hexutil.Uint64), + L2GenesisJovianTimeOffset: nil, + L2GenesisInteropTimeOffset: new(hexutil.Uint64), + L2GenesisEspressoCeloIntegrationTimeOffset: new(hexutil.Uint64), + L1CancunTimeOffset: new(hexutil.Uint64), + L1PragueTimeOffset: new(hexutil.Uint64), + UseInterop: true, }, L2CoreDeployConfig: genesis.L2CoreDeployConfig{ L1ChainID: l1ChainID, diff --git a/op-e2e/actions/proofs/espresso_celo_integration_activation_test.go b/op-e2e/actions/proofs/espresso_celo_integration_activation_test.go new file mode 100644 index 0000000000000..f5e3de77ff28f --- /dev/null +++ b/op-e2e/actions/proofs/espresso_celo_integration_activation_test.go @@ -0,0 +1,235 @@ +package proofs + +import ( + "context" + "math/big" + "testing" + "time" + + env "github.com/ethereum-optimism/optimism/espresso/environment" + "github.com/ethereum-optimism/optimism/op-e2e/config" + "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" + geth_types "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +// Test_EspressoCeloIntegrationActivation tests that the EspressoCeloIntegration +// activation works correctly by verifying that Espresso integration is enabled after the activation timestamp. +// In a real deployment, this would enable the batcher's isEspressoEnabled() method and initialize the +// espressoStreamer for dual submission to L1 and Espresso, therefore allowing the caff node to receive batches from Espresso. +// +// This test starts with a normal e2esys (without Espresso), sets an activation time, +// verifies caff node cannot make progress, then starts the espressoDevNode, +// verifies caff node still cannot progress, then waits until activation time passes +// and verifies that caff node can make progress. +func Test_EspressoCeloIntegrationActivation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var system *e2esys.System + var espressoDevNode env.EspressoDevNode + var caffNode *env.CaffNodeInstance + + // Ensure cleanup happens even if test fails + defer func() { + if caffNode != nil { + env.Stop(t, caffNode) + } + if espressoDevNode != nil { + env.Stop(t, espressoDevNode) + } + if system != nil { + env.Stop(t, system) + } + // Give time for goroutines to finish + time.Sleep(1 * time.Second) + }() + + // Step 1: Start with normal e2esys (without Espresso initially) + sysConfig := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(config.AllocTypeStandard)) + system, err := sysConfig.Start(t) + require.NoError(t, err, "failed to launch without Espresso dev node") + + // Step 2: Configure EspressoCeloIntegration activation time for testing + // Set it to activate 15 seconds from now + + currentTime := uint64(time.Now().Unix()) + activationTime := currentTime + 15 + system.RollupConfig.EspressoCeloIntegrationTime = &activationTime + + t.Logf("EspressoCeloIntegration activation time: %d (current: %d)", activationTime, currentTime) + + // Wait for system to stabilize + time.Sleep(2 * time.Second) + + // Verify activation state before activation time + preActivationTime := activationTime - 1 + postActivationTime := activationTime + 1 + + require.False(t, system.RollupConfig.IsEspressoCeloIntegration(preActivationTime), "EspressoCeloIntegration should not be active before activation time") + require.True(t, system.RollupConfig.IsEspressoCeloIntegration(activationTime), "EspressoCeloIntegration should be active at activation time") + require.True(t, system.RollupConfig.IsEspressoCeloIntegration(postActivationTime), "EspressoCeloIntegration should remain active after activation") + + // Set up transaction submitters to generate activity + keys := system.Cfg.Secrets + addresses := keys.Addresses() + signer := geth_types.LatestSignerForChainID(system.Cfg.L2ChainIDBig()) + + // Submit some transactions to generate activity before starting Espresso + seqClient := system.NodeClient(e2esys.RoleSeq) + + for i := 0; i < 3; i++ { + tx := &geth_types.DynamicFeeTx{ + ChainID: system.RollupConfig.L2ChainID, + Nonce: uint64(i), + To: &addresses.Bob, + Value: big.NewInt(1), + Gas: 21000, + GasFeeCap: big.NewInt(1000000000), + GasTipCap: big.NewInt(1000000000), + } + + signedTx, err := geth_types.SignTx(geth_types.NewTx(tx), signer, keys.Alice) + require.NoError(t, err, "failed to sign transaction") + + err = seqClient.SendTransaction(ctx, signedTx) + require.NoError(t, err, "failed to send transaction") + + t.Logf("Submitted transaction %d (before Espresso)", i) + time.Sleep(1 * time.Second) + } + + // Verify normal sequencer is working but we haven't started Espresso integration yet + seqHeader, err := seqClient.HeaderByNumber(ctx, nil) + require.NoError(t, err, "failed to get sequencer header") + require.Greater(t, seqHeader.Number.Uint64(), uint64(0), "Sequencer should process blocks normally") + t.Logf("Sequencer at block %d before Espresso", seqHeader.Number.Uint64()) + + // Step 3: Start the Espresso dev node + t.Log("Starting Espresso dev node...") + launcher := new(env.EspressoDevNodeLauncherDocker) + _, espressoDevNode, err = launcher.StartDevNet(ctx, t, env.WithL1FinalizedDistance(0), env.WithSequencerUseFinalized(true)) + require.NoError(t, err, "failed to start espresso dev node") + + // Launch Caff Node with the espresso dev node + caffNode, err = env.LaunchCaffNode(t, system, espressoDevNode) + require.NoError(t, err, "failed to start caff node with espresso dev node") + + // Submit more transactions to test with Espresso dev node running but before activation + for i := 3; i < 6; i++ { + tx := &geth_types.DynamicFeeTx{ + ChainID: system.RollupConfig.L2ChainID, + Nonce: uint64(i), + To: &addresses.Bob, + Value: big.NewInt(1), + Gas: 21000, + GasFeeCap: big.NewInt(1000000000), + GasTipCap: big.NewInt(1000000000), + } + + signedTx, err := geth_types.SignTx(geth_types.NewTx(tx), signer, keys.Alice) + require.NoError(t, err, "failed to sign transaction") + + err = seqClient.SendTransaction(ctx, signedTx) + require.NoError(t, err, "failed to send transaction") + + t.Logf("Submitted transaction %d (with Espresso dev node)", i) + time.Sleep(1 * time.Second) + } + + // Wait a bit for any potential processing + time.Sleep(3 * time.Second) + + // Verify that the Caff node still cannot make progress (Espresso integration not activated yet) + caffClient := system.NodeClient(env.RoleCaffNode) + caffHeader, err := caffClient.HeaderByNumber(ctx, nil) + require.NoError(t, err, "failed to get latest header from Caff node") + + // Get current sequencer progress to compare + currentSeqHeader, err := seqClient.HeaderByNumber(ctx, nil) + require.NoError(t, err, "failed to get current sequencer header") + + t.Logf("Before activation - Sequencer: block %d, Caff: block %d", + currentSeqHeader.Number.Uint64(), caffHeader.Number.Uint64()) + + // Verify that the Caff node is behind the sequencer (cannot process blocks before activation) + // The caff node should be at genesis because Espresso integration is not active + require.Less(t, caffHeader.Number.Uint64(), currentSeqHeader.Number.Uint64(), + "Caff node should be behind sequencer before activation time") + require.Equal(t, uint64(0), caffHeader.Number.Uint64(), "Caff node should be at genesis before activation time") + + // Step 4: Wait for activation time to pass + t.Log("Waiting for EspressoCeloIntegration activation...") + timeToWait := time.Until(time.Unix(int64(activationTime), 0)) + 2*time.Second + if timeToWait > 0 { + time.Sleep(timeToWait) + } + // Verify we're now past activation + nowTime := uint64(time.Now().Unix()) + t.Logf("Current time: %d, activation time: %d", nowTime, activationTime) + require.True(t, nowTime >= activationTime, "Should be past activation time") + require.True(t, system.RollupConfig.IsEspressoCeloIntegration(nowTime), "EspressoCeloIntegration should be active now") + + // Submit transactions after activation to test batcher behavior + for i := 6; i < 10; i++ { + tx := &geth_types.DynamicFeeTx{ + ChainID: system.RollupConfig.L2ChainID, + Nonce: uint64(i), + To: &addresses.Bob, + Value: big.NewInt(1), + Gas: 21000, + GasFeeCap: big.NewInt(1000000000), + GasTipCap: big.NewInt(1000000000), + } + + signedTx, err := geth_types.SignTx(geth_types.NewTx(tx), signer, keys.Alice) + require.NoError(t, err, "failed to sign transaction") + + err = seqClient.SendTransaction(ctx, signedTx) + require.NoError(t, err, "failed to send transaction") + + t.Logf("Submitted post-activation transaction %d", i) + time.Sleep(1 * time.Second) + } + + // Wait for blocks to be processed and batched + time.Sleep(5 * time.Second) + + // Verify that the system is functioning correctly after activation + // Check that we have processed blocks beyond the activation time + header, err := seqClient.HeaderByNumber(ctx, nil) + require.NoError(t, err, "failed to get latest header") + + t.Logf("Latest block time: %d, activation time: %d", header.Time, activationTime) + + // Verify that the activation logic works correctly + require.True(t, system.RollupConfig.IsEspressoCeloIntegration(header.Time), "EspressoCeloIntegration should be active for current block") + + // Check if we're at the activation block or past it + if system.RollupConfig.IsEspressoCeloIntegrationActivationBlock(header.Time) { + t.Log("Current block is the EspressoCeloIntegration activation block") + } else if system.RollupConfig.IsEspressoCeloIntegration(header.Time - system.RollupConfig.BlockTime) { + t.Log("EspressoCeloIntegration was activated in a previous block") + } + + t.Log("EspressoCeloIntegration activation verified successfully") + + // Verify that the Caff node can now make progress (indicates Espresso integration is working) + time.Sleep(10 * time.Second) + caffHeader, err = caffClient.HeaderByNumber(ctx, nil) + require.NoError(t, err, "failed to get latest header from Caff node") + + // Verify that both sequencer and caff node are progressing + require.Greater(t, header.Number.Uint64(), uint64(0), "Sequencer should have processed blocks") + require.Greater(t, caffHeader.Number.Uint64(), uint64(0), "Caff node should now be able to process blocks after activation") + + t.Logf("Sequencer at block %d, Caff node at block %d", header.Number.Uint64(), caffHeader.Number.Uint64()) + + // Final verification: The key test is that EspressoCeloIntegration activation + // time-based logic is working correctly. + finalTime := uint64(time.Now().Unix()) + require.True(t, system.RollupConfig.IsEspressoCeloIntegration(finalTime), + "EspressoCeloIntegration should remain active at test completion") + + t.Log("Test completed successfully: Caff node can make progress after activation time") +} diff --git a/op-e2e/actions/upgrades/helpers/config.go b/op-e2e/actions/upgrades/helpers/config.go index dcf6070f6a6a9..76ef39d57dd0d 100644 --- a/op-e2e/actions/upgrades/helpers/config.go +++ b/op-e2e/actions/upgrades/helpers/config.go @@ -61,4 +61,13 @@ func ApplyDeltaTimeOffset(dp *e2eutils.DeployParams, deltaTimeOffset *hexutil.Ui dp.DeployConfig.L2GenesisJovianTimeOffset = deltaTimeOffset } } + + // configure Espresso Celo Integration to not be before Delta accidentally + if dp.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset != nil { + if deltaTimeOffset == nil { + dp.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset = nil + } else if *dp.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset < *deltaTimeOffset { + dp.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset = deltaTimeOffset + } + } } diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index 8e63a4a8098c2..120be8513ac67 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -297,14 +297,15 @@ func initAllocType(root string, allocType AllocType) { } baseUpgradeSchedule := map[string]any{ - "l2GenesisRegolithTimeOffset": nil, - "l2GenesisCanyonTimeOffset": nil, - "l2GenesisDeltaTimeOffset": nil, - "l2GenesisEcotoneTimeOffset": nil, - "l2GenesisFjordTimeOffset": nil, - "l2GenesisGraniteTimeOffset": nil, - "l2GenesisHoloceneTimeOffset": nil, - "l2GenesisIsthmusTimeOffset": nil, + "l2GenesisRegolithTimeOffset": nil, + "l2GenesisCanyonTimeOffset": nil, + "l2GenesisDeltaTimeOffset": nil, + "l2GenesisEcotoneTimeOffset": nil, + "l2GenesisFjordTimeOffset": nil, + "l2GenesisGraniteTimeOffset": nil, + "l2GenesisHoloceneTimeOffset": nil, + "l2GenesisIsthmusTimeOffset": nil, + "l2GenesisEspressoCeloIntegrationTimeOffset": nil, } upgradeSchedule := new(genesis.UpgradeScheduleDeployConfig) diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index 495d8e4c75ae7..4e6ee303acb96 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -250,6 +250,7 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { isFjord := isGranite || os.Getenv("OP_E2E_USE_FJORD") == "true" isEcotone := isFjord || os.Getenv("OP_E2E_USE_ECOTONE") == "true" isDelta := isEcotone || os.Getenv("OP_E2E_USE_DELTA") == "true" + isEspressoCeloIntegration := os.Getenv("OP_E2E_USE_ESPRESSO_CELO_INTEGRATION") == "true" if isDelta { deployConfig.L2GenesisDeltaTimeOffset = new(hexutil.Uint64) } @@ -271,6 +272,9 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) { if isJovian { deployConfig.L2GenesisJovianTimeOffset = new(hexutil.Uint64) } + if isEspressoCeloIntegration { + deployConfig.L2GenesisEspressoCeloIntegrationTimeOffset = new(hexutil.Uint64) + } // Canyon and lower is activated by default deployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64) deployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64) diff --git a/op-e2e/faultproofs/util.go b/op-e2e/faultproofs/util.go index b70392126b196..f588670a5b0a3 100644 --- a/op-e2e/faultproofs/util.go +++ b/op-e2e/faultproofs/util.go @@ -56,6 +56,7 @@ func WithLatestFork() faultDisputeConfigOpts { cfg.DeployConfig.L2GenesisGraniteTimeOffset = &genesisActivation cfg.DeployConfig.L2GenesisHoloceneTimeOffset = &genesisActivation cfg.DeployConfig.L2GenesisIsthmusTimeOffset = &genesisActivation + cfg.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset = &genesisActivation }) } } diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 713fafbab943f..b4a330f678506 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -213,6 +213,7 @@ func RegolithSystemConfig(t *testing.T, regolithTimeOffset *hexutil.Uint64, opts cfg.DeployConfig.L2GenesisHoloceneTimeOffset = nil cfg.DeployConfig.L2GenesisIsthmusTimeOffset = nil cfg.DeployConfig.L2GenesisJovianTimeOffset = nil + cfg.DeployConfig.L2GenesisEspressoCeloIntegrationTimeOffset = nil // ADD NEW FORKS HERE! return cfg } @@ -710,30 +711,31 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, L2Time: uint64(cfg.DeployConfig.L1GenesisBlockTimestamp), SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig), }, - BlockTime: cfg.DeployConfig.L2BlockTime, - MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift, - SeqWindowSize: cfg.DeployConfig.SequencerWindowSize, - ChannelTimeoutBedrock: cfg.DeployConfig.ChannelTimeoutBedrock, - L1ChainID: cfg.L1ChainIDBig(), - L2ChainID: cfg.L2ChainIDBig(), - BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress, - BatchAuthenticatorAddress: cfg.DeployConfig.BatchAuthenticatorAddress, - DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy, - L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy, - RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - CanyonTime: cfg.DeployConfig.CanyonTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - DeltaTime: cfg.DeployConfig.DeltaTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - PectraBlobScheduleTime: cfg.DeployConfig.PectraBlobScheduleTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - JovianTime: cfg.DeployConfig.JovianTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - Cel2Time: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, - AltDAConfig: rollupAltDAConfig, + BlockTime: cfg.DeployConfig.L2BlockTime, + MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift, + SeqWindowSize: cfg.DeployConfig.SequencerWindowSize, + ChannelTimeoutBedrock: cfg.DeployConfig.ChannelTimeoutBedrock, + L1ChainID: cfg.L1ChainIDBig(), + L2ChainID: cfg.L2ChainIDBig(), + BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress, + BatchAuthenticatorAddress: cfg.DeployConfig.BatchAuthenticatorAddress, + DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy, + L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy, + RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + CanyonTime: cfg.DeployConfig.CanyonTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + DeltaTime: cfg.DeployConfig.DeltaTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + PectraBlobScheduleTime: cfg.DeployConfig.PectraBlobScheduleTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + JovianTime: cfg.DeployConfig.JovianTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + Cel2Time: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + EspressoCeloIntegrationTime: cfg.DeployConfig.EspressoCeloIntegrationTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, + AltDAConfig: rollupAltDAConfig, ChainOpConfig: ¶ms.OptimismConfig{ EIP1559Elasticity: cfg.DeployConfig.EIP1559Elasticity, EIP1559Denominator: cfg.DeployConfig.EIP1559Denominator, diff --git a/op-node/rollup/chain_spec.go b/op-node/rollup/chain_spec.go index 7fcfaa7bed3d5..a5ab1e39f49c8 100644 --- a/op-node/rollup/chain_spec.go +++ b/op-node/rollup/chain_spec.go @@ -39,17 +39,18 @@ const maxSequencerDriftCelo = maxSequencerDriftFjord + 1092 type ForkName string const ( - Bedrock ForkName = "bedrock" - Regolith ForkName = "regolith" - Canyon ForkName = "canyon" - Delta ForkName = "delta" - Ecotone ForkName = "ecotone" - Fjord ForkName = "fjord" - Granite ForkName = "granite" - Holocene ForkName = "holocene" - Isthmus ForkName = "isthmus" - Jovian ForkName = "jovian" - Interop ForkName = "interop" + Bedrock ForkName = "bedrock" + Regolith ForkName = "regolith" + Canyon ForkName = "canyon" + Delta ForkName = "delta" + Ecotone ForkName = "ecotone" + Fjord ForkName = "fjord" + Granite ForkName = "granite" + Holocene ForkName = "holocene" + Isthmus ForkName = "isthmus" + Jovian ForkName = "jovian" + Interop ForkName = "interop" + EspressoCeloIntegration ForkName = "espresso_celo_integration" // ADD NEW FORKS TO AllForks BELOW! None ForkName = "none" ) @@ -66,6 +67,7 @@ var AllForks = []ForkName{ Isthmus, Jovian, Interop, + EspressoCeloIntegration, // ADD NEW FORKS HERE! } @@ -213,6 +215,9 @@ func (s *ChainSpec) CheckForkActivation(log log.Logger, block eth.L2BlockRef) { if s.config.IsInterop(block.Time) { s.currentFork = Interop } + if s.config.IsEspressoCeloIntegration(block.Time) { + s.currentFork = EspressoCeloIntegration + } log.Info("Current hardfork version detected", "forkName", s.currentFork) return } @@ -240,6 +245,8 @@ func (s *ChainSpec) CheckForkActivation(log log.Logger, block eth.L2BlockRef) { foundActivationBlock = s.config.IsJovianActivationBlock(block.Time) case Interop: foundActivationBlock = s.config.IsInteropActivationBlock(block.Time) + case EspressoCeloIntegration: + foundActivationBlock = s.config.IsEspressoCeloIntegrationActivationBlock(block.Time) } if foundActivationBlock { diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 617b07ea4f298..cfcc7421beaf1 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -132,6 +132,10 @@ type Config struct { // Active if InteropTime != nil && L2 block timestamp >= *InteropTime, inactive otherwise. InteropTime *uint64 `json:"interop_time,omitempty"` + // EspressoCeloIntegrationTime sets the activation time of the OP-stack rollup Celo's integration with Espresso. + // Active if EspressoCeloIntegrationTime != nil && L2 block timestamp >= *EspressoCeloIntegrationTime, inactive otherwise. + EspressoCeloIntegrationTime *uint64 `json:"espresso_celo_integration_time,omitempty"` + // Note: below addresses are part of the block-derivation process, // and required to be the same network-wide to stay in consensus. @@ -495,6 +499,10 @@ func (c *Config) IsCel2(timestamp uint64) bool { return c.Cel2Time != nil && timestamp >= *c.Cel2Time } +func (c *Config) IsEspressoCeloIntegration(timestamp uint64) bool { + return c.EspressoCeloIntegrationTime != nil && timestamp >= *c.EspressoCeloIntegrationTime +} + func (c *Config) IsRegolithActivationBlock(l2BlockTime uint64) bool { return c.IsRegolith(l2BlockTime) && l2BlockTime >= c.BlockTime && @@ -567,6 +575,14 @@ func (c *Config) IsInteropActivationBlock(l2BlockTime uint64) bool { !c.IsInterop(l2BlockTime-c.BlockTime) } +// IsEspressoCeloIntegrationActivationBlock returns whether the specified block is the first block subject to the +// EspressoCeloIntegration upgrade. +func (c *Config) IsEspressoCeloIntegrationActivationBlock(l2BlockTime uint64) bool { + return c.IsEspressoCeloIntegration(l2BlockTime) && + l2BlockTime >= c.BlockTime && + !c.IsEspressoCeloIntegration(l2BlockTime-c.BlockTime) +} + // IsActivationBlock returns the fork which activates at the block with time newTime if the previous // block's time is oldTime. It return an empty ForkName if no fork activation takes place between // those timestamps. It can be used for both, L1 and L2 blocks. @@ -581,6 +597,9 @@ func (c *Config) IsActivationBlock(oldTime, newTime uint64) ForkName { func (c *Config) ActivateAtGenesis(hardfork ForkName) { // IMPORTANT! ordered from newest to oldest switch hardfork { + case EspressoCeloIntegration: + c.EspressoCeloIntegrationTime = new(uint64) + fallthrough case Interop: c.InteropTime = new(uint64) fallthrough @@ -776,6 +795,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { ctx = append(ctx, "pectra_blob_schedule_time", fmtForkTimeOrUnset(c.PectraBlobScheduleTime)) } ctx = append(ctx, "cel2_time", fmtForkTimeOrUnset(c.Cel2Time)) + ctx = append(ctx, "espresso_celo_integration_time", fmtForkTimeOrUnset(c.EspressoCeloIntegrationTime)) log.Info("Rollup Config", ctx...) } diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 3d25a84a1466e..b3c62c287b463 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -137,13 +137,14 @@ optimizer = false additional_compiler_profiles = [ { name = "dispute", optimizer_runs = 5000 }, { name = "via-ir", via_ir = true }, + { name = "op-contracts", optimizer_runs = 5000 }, ] compilation_restrictions = [ - { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 5000 }, - { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 5000 }, - { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, - { paths = "src/L1/StandardValidator.sol", optimizer_runs = 5000 }, - { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, + { paths = "src/dispute/FaultDisputeGame.sol", profile = "dispute" }, + { paths = "src/dispute/PermissionedDisputeGame.sol", profile = "dispute" }, + { paths = "src/L1/OPContractsManager.sol", profile = "op-contracts" }, + { paths = "src/L1/StandardValidator.sol", profile = "op-contracts" }, + { paths = "src/L1/OptimismPortal2.sol", profile = "op-contracts" }, { paths = "lib/espresso-tee-contracts/lib/nitro-validator/**", via_ir = false }, { paths = "lib/espresso-tee-contracts/lib/automata-dcap-attestation/**", via_ir = true }, ] diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index d9a3ede5919a0..d1d1a6a531108 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -193,6 +193,10 @@ contract L2Genesis is Deployer { if (writeForkGenesisAllocs(_fork, Fork.JOVIAN, _mode)) { return; } + + if (writeForkGenesisAllocs(_fork, Fork.ESPRESSO_CELO_INTEGRATION, _mode)) { + return; + } } function writeForkGenesisAllocs(Fork _latest, Fork _current, OutputMode _mode) internal returns (bool isLatest_) { diff --git a/packages/contracts-bedrock/scripts/libraries/Config.sol b/packages/contracts-bedrock/scripts/libraries/Config.sol index c4c893699db68..aab48c4285eb1 100644 --- a/packages/contracts-bedrock/scripts/libraries/Config.sol +++ b/packages/contracts-bedrock/scripts/libraries/Config.sol @@ -36,7 +36,8 @@ enum Fork { GRANITE, HOLOCENE, ISTHMUS, - JOVIAN + JOVIAN, + ESPRESSO_CELO_INTEGRATION } Fork constant LATEST_FORK = Fork.JOVIAN; @@ -59,6 +60,8 @@ library ForkUtils { return "isthmus"; } else if (_fork == Fork.JOVIAN) { return "jovian"; + } else if (_fork == Fork.ESPRESSO_CELO_INTEGRATION) { + return "espresso_celo_integration"; } else { return "unknown"; } @@ -171,6 +174,8 @@ library Config { return Fork.ISTHMUS; } else if (forkHash == keccak256(bytes("jovian"))) { return Fork.JOVIAN; + } else if (forkHash == keccak256(bytes("espresso_celo_integration"))) { + return Fork.ESPRESSO_CELO_INTEGRATION; } else { revert(string.concat("Config: unknown fork: ", forkStr)); }