Skip to content
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ jobs:
default: "go-tests-short-ci"
machine: true
resource_class: <<parameters.resource_class>>
circleci_ip_ranges: true
steps:
- checkout-from-workspace
- run:
Expand Down
39 changes: 39 additions & 0 deletions op-acceptance-tests/tests/sync_tester_ext_el/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sync_tester_ext_el

import (
"os"
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/compat"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-service/eth"
)

var (
InitialL2Block = uint64(32012748)
)

func TestMain(m *testing.M) {
L2NetworkName := "op-sepolia"
L1ChainID := eth.ChainIDFromUInt64(11155111)
Comment thread
pcw109550 marked this conversation as resolved.

L2ELEndpoint := "https://ci-sepolia-l2.optimism.io"
L1CLBeaconEndpoint := "https://ci-sepolia-beacon.optimism.io"
L1ELEndpoint := "https://ci-sepolia-l1.optimism.io"

// Endpoints when running with Tailscale networking
if os.Getenv("TAILSCALE_NETWORKING") == "true" {
L2ELEndpoint = "https://proxyd-l2-sepolia.primary.client.dev.oplabs.cloud"
L1CLBeaconEndpoint = "https://beacon-api-proxy-sepolia.primary.client.dev.oplabs.cloud"
L1ELEndpoint = "https://proxyd-l1-sepolia.primary.client.dev.oplabs.cloud"
}

presets.DoMain(m, presets.WithMinimalExternalELWithSuperchainRegistry(L1CLBeaconEndpoint, L1ELEndpoint, L2ELEndpoint, L1ChainID, L2NetworkName, eth.FCUState{
Latest: InitialL2Block,
Safe: InitialL2Block,
Finalized: InitialL2Block,
}),
presets.WithCompatibleTypes(compat.SysGo),
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package sync_tester_ext_el

import (
"testing"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

func TestSyncTesterExtEL(gt *testing.T) {
t := devtest.SerialT(gt)

sys := presets.NewMinimalExternalELWithExternalL1(t)
require := t.Require()

// Test that we can get chain IDs from L2CL node
l2CLChainID := sys.L2CL.ID().ChainID()
require.Equal(eth.ChainIDFromUInt64(11155420), l2CLChainID, "L2CL should be on chain 11155420")

// Test that the network started successfully
require.NotNil(sys.L1EL, "L1 EL node should be available")
require.NotNil(sys.L2CL, "L2 CL node should be available")
require.NotNil(sys.SyncTester, "SyncTester should be available")

// Test that we can get sync status from L2CL node
l2CLSyncStatus := sys.L2CL.SyncStatus()
require.NotNil(l2CLSyncStatus, "L2CL should have sync status")

blocksToSync := uint64(20)
targetBlock := InitialL2Block + blocksToSync
sys.L2CL.Reached(types.LocalUnsafe, targetBlock, 500)

l2CLSyncStatus = sys.L2CL.SyncStatus()
require.NotNil(l2CLSyncStatus, "L2CL should have sync status")

unsafeL2Ref := l2CLSyncStatus.UnsafeL2
blk := sys.L2EL.BlockRefByNumber(unsafeL2Ref.Number)
require.Equal(unsafeL2Ref.Hash, blk.Hash, "L2EL should be on the same block as L2CL")

stSessions := sys.SyncTester.ListSessions()
require.Equal(len(stSessions), 1, "expect exactly one session")

stSession := sys.SyncTester.GetSession(stSessions[0])
require.GreaterOrEqual(stSession.CurrentState.Latest, stSession.InitialState.Latest+blocksToSync, "SyncTester session Latest should be on the same block as L2CL")
require.GreaterOrEqual(stSession.CurrentState.Safe, stSession.InitialState.Safe+blocksToSync, "SyncTester session Safe should be on the same block as L2CL")

t.Logger().Info("SyncTester ExtEL test completed successfully",
"l2cl_chain_id", l2CLChainID,
"l2cl_sync_status", l2CLSyncStatus)
}
Comment thread
nonsense marked this conversation as resolved.
60 changes: 60 additions & 0 deletions op-devstack/presets/minimal_external_el.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package presets

import (
"github.com/ethereum/go-ethereum/log"

"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/shim"
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-devstack/stack/match"
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
"github.com/ethereum-optimism/optimism/op-service/eth"
)

type MinimalExternalEL struct {
Log log.Logger
T devtest.T
ControlPlane stack.ControlPlane

L1Network *dsl.L1Network
L1EL *dsl.L1ELNode

L2Chain *dsl.L2Network
L2CL *dsl.L2CLNode
L2EL *dsl.L2ELNode

SyncTester *dsl.SyncTester
}

func (m *MinimalExternalEL) L2Networks() []*dsl.L2Network {
return []*dsl.L2Network{
m.L2Chain,
}
}

func WithMinimalExternalELWithSuperchainRegistry(l1CLBeaconRPC, l1ELRPC, l2ELRPC string, l1ChainID eth.ChainID, networkName string, fcu eth.FCUState) stack.CommonOption {
return stack.MakeCommon(sysgo.DefaultMinimalExternalELSystemWithEndpointAndSuperchainRegistry(&sysgo.DefaultMinimalExternalELSystemIDs{}, l1CLBeaconRPC, l1ELRPC, l2ELRPC, l1ChainID, networkName, fcu))
}

func NewMinimalExternalELWithExternalL1(t devtest.T) *MinimalExternalEL {
system := shim.NewSystem(t)
orch := Orchestrator()
orch.Hydrate(system)

l2 := system.L2Network(match.Assume(t, match.L2ChainA))
verifierCL := l2.L2CLNode(match.FirstL2CL)
syncTester := l2.SyncTester(match.Assume(t, match.FirstSyncTester))

return &MinimalExternalEL{
Log: t.Logger(),
T: t,
ControlPlane: orch.ControlPlane(),
L1Network: dsl.NewL1Network(system.L1Network(match.FirstL1Network)),
L1EL: dsl.NewL1ELNode(system.L1Network(match.FirstL1Network).L1ELNode(match.FirstL1EL)),
L2Chain: dsl.NewL2Network(l2, orch.ControlPlane()),
L2CL: dsl.NewL2CLNode(verifierCL, orch.ControlPlane()),
L2EL: dsl.NewL2ELNode(l2.L2ELNode(match.FirstL2EL), orch.ControlPlane()),
SyncTester: dsl.NewSyncTester(syncTester),
}
}
4 changes: 4 additions & 0 deletions op-devstack/sysgo/faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ type FaucetService struct {
}

func (n *FaucetService) hydrate(system stack.ExtensibleSystem) {
if n == nil || n.service == nil {
Comment thread
nonsense marked this conversation as resolved.
Outdated
return
}

require := system.T().Require()

for faucetID, chainID := range n.service.Faucets() {
Expand Down
21 changes: 21 additions & 0 deletions op-devstack/sysgo/l1_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,24 @@ func WithL1Nodes(l1ELID stack.L1ELNodeID, l1CLID stack.L1CLNodeID) stack.Option[
require.True(orch.l1CLs.SetIfMissing(l1CLID, l1CLNode), "must not already exist")
})
}

// WithExtL1Nodes initializes L1 EL and CL nodes that connect to external RPC endpoints
func WithExtL1Nodes(l1ELID stack.L1ELNodeID, l1CLID stack.L1CLNodeID, elRPCEndpoint string, clRPCEndpoint string) stack.Option[*Orchestrator] {
return stack.AfterDeploy(func(orch *Orchestrator) {
require := orch.P().Require()

// Create L1 EL node with external RPC
l1ELNode := &L1ELNode{
id: l1ELID,
userRPC: elRPCEndpoint,
}
require.True(orch.l1ELs.SetIfMissing(l1ELID, l1ELNode), "must not already exist")

// Create L1 CL node with external RPC
l1CLNode := &L1CLNode{
id: l1CLID,
beaconHTTPAddr: clRPCEndpoint,
}
require.True(orch.l1CLs.SetIfMissing(l1CLID, l1CLNode), "must not already exist")
})
}
2 changes: 1 addition & 1 deletion op-devstack/sysgo/l2_cl_opnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L
L2EngineJWTSecret: jwtSecret,
},
Beacon: &config.L1BeaconEndpointConfig{
BeaconAddr: l1CL.beacon.BeaconAddr(),
BeaconAddr: l1CL.beaconHTTPAddr,
},
Driver: driver.Config{
SequencerEnabled: cfg.IsSequencer,
Expand Down
86 changes: 86 additions & 0 deletions op-devstack/sysgo/l2_network_superchain_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package sysgo

import (
"fmt"

"github.com/ethereum/go-ethereum/core"

"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/superutil"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
)

// WithL2NetworkFromSuperchainRegistry creates an L2 network using the rollup config from the superchain registry
func WithL2NetworkFromSuperchainRegistry(l2NetworkID stack.L2NetworkID, networkName string) stack.Option[*Orchestrator] {
return stack.BeforeDeploy(func(orch *Orchestrator) {
p := orch.P().WithCtx(stack.ContextWithID(orch.P().Ctx(), l2NetworkID))
require := p.Require()

// Load the rollup config from the superchain registry
rollupCfg, err := chaincfg.GetRollupConfig(networkName)
require.NoError(err, "failed to load rollup config for network %s", networkName)

// Get the chain config from the superchain registry
chainCfg := chaincfg.ChainByName(networkName)
require.NotNil(chainCfg, "chain config not found for network %s", networkName)

// Load the chain config using superutil
paramsChainConfig, err := superutil.LoadOPStackChainConfigFromChainID(chainCfg.ChainID)
require.NoError(err, "failed to load chain config for network %s", networkName)

// Create a genesis config from the chain config
genesis := &core.Genesis{
Config: paramsChainConfig,
}

// Create the L2 network
l2Net := &L2Network{
id: l2NetworkID,
l1ChainID: eth.ChainIDFromBig(rollupCfg.L1ChainID),
genesis: genesis,
rollupCfg: rollupCfg,
keys: orch.keys,
}

require.True(orch.l2Nets.SetIfMissing(l2NetworkID.ChainID(), l2Net),
fmt.Sprintf("must not already exist: %s", l2NetworkID))
})
}

// WithL2NetworkFromSuperchainRegistryWithDependencySet creates an L2 network using the rollup config from the superchain registry
// and also sets up the dependency set for interop support
func WithL2NetworkFromSuperchainRegistryWithDependencySet(l2NetworkID stack.L2NetworkID, networkName string) stack.Option[*Orchestrator] {
return stack.Combine(
WithL2NetworkFromSuperchainRegistry(l2NetworkID, networkName),
stack.BeforeDeploy(func(orch *Orchestrator) {
p := orch.P().WithCtx(stack.ContextWithID(orch.P().Ctx(), l2NetworkID))
require := p.Require()

// Load the dependency set from the superchain registry
chainCfg := chaincfg.ChainByName(networkName)
require.NotNil(chainCfg, "chain config not found for network %s", networkName)

_, err := depset.FromRegistry(eth.ChainIDFromUInt64(chainCfg.ChainID))
if err != nil {
// If dependency set is not available, that's okay - it's optional
p.Logger().Info("No dependency set available for network", "network", networkName, "err", err)
return
}

// Create a cluster to hold the dependency set
clusterID := stack.ClusterID(networkName)

// Create a minimal full config set with just the dependency set
// This is a simplified approach - in a real implementation you might want
// to create a proper FullConfigSetMerged
cluster := &Cluster{
id: clusterID,
cfgset: depset.FullConfigSetMerged{}, // Empty for now
}

orch.clusters.Set(clusterID, cluster)
}),
)
}
41 changes: 41 additions & 0 deletions op-devstack/sysgo/sync_tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/endpoint"
"github.com/ethereum-optimism/optimism/op-service/eth"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-sync-tester/config"

Expand Down Expand Up @@ -88,3 +89,43 @@ func WithSyncTester(syncTesterID stack.SyncTesterID, l2ELs []stack.L2ELNodeID) s
orch.syncTester = &SyncTesterService{id: syncTesterID, service: srv}
})
}

func WithSyncTesterWithExternalEndpoint(syncTesterID stack.SyncTesterID, endpointRPC string, chainID eth.ChainID) stack.Option[*Orchestrator] {
return stack.AfterDeploy(func(orch *Orchestrator) {
p := orch.P().WithCtx(stack.ContextWithID(orch.P().Ctx(), syncTesterID))

require := p.Require()

require.Nil(orch.syncTester, "can only support a single sync-tester-service in sysgo")

syncTesters := make(map[sttypes.SyncTesterID]*stconf.SyncTesterEntry)

// Create a sync tester entry with the external endpoint
id := sttypes.SyncTesterID(fmt.Sprintf("dev-sync-tester-%s", chainID))
syncTesters[id] = &stconf.SyncTesterEntry{
ELRPC: endpoint.MustRPC{Value: endpoint.URL(endpointRPC)},
ChainID: chainID,
}

cfg := &config.Config{
RPC: oprpc.CLIConfig{
ListenAddr: "127.0.0.1",
},
SyncTesters: &stconf.Config{
SyncTesters: syncTesters,
},
}
logger := p.Logger()
srv, err := synctester.FromConfig(p.Ctx(), cfg, logger)
require.NoError(err, "must setup sync tester service")
require.NoError(srv.Start(p.Ctx()))
p.Cleanup(func() {
ctx, cancel := context.WithCancel(context.Background())
cancel() // force-quit
logger.Info("Closing sync tester")
_ = srv.Stop(ctx)
logger.Info("Closed sync tester")
})
orch.syncTester = &SyncTesterService{id: syncTesterID, service: srv}
})
}
Loading