From 42706a016d8e70ff390ab5e26c43c6b26d3148c3 Mon Sep 17 00:00:00 2001 From: tre Date: Wed, 10 Jul 2024 13:51:35 -0700 Subject: [PATCH] feat(orchestrator): create orchestrator package --- cmd/main.go | 7 +- op-simulator/op_simulator.go | 7 +- .../genesisstates}/genesis-l1.json | 0 .../genesisstates}/genesis-l2.json | 0 orchestrator/orchestrator.go | 213 ++++++++++++++++++ supersim.go | 176 ++------------- supersim_test.go | 25 +- 7 files changed, 253 insertions(+), 175 deletions(-) rename {genesis => orchestrator/genesisstates}/genesis-l1.json (100%) rename {genesis => orchestrator/genesisstates}/genesis-l2.json (100%) create mode 100644 orchestrator/orchestrator.go diff --git a/cmd/main.go b/cmd/main.go index 5dda1c92..37843200 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -52,5 +52,10 @@ func SupersimMain(ctx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Li } // use config and setup supersim - return supersim.NewSupersim(log, &supersim.DefaultConfig), nil + s, err := supersim.NewSupersim(log, &supersim.DefaultConfig) + if err != nil { + return nil, fmt.Errorf("failed to create supersim") + } + + return s, nil } diff --git a/op-simulator/op_simulator.go b/op-simulator/op_simulator.go index 54cf11a4..4cdc7286 100644 --- a/op-simulator/op_simulator.go +++ b/op-simulator/op_simulator.go @@ -22,7 +22,8 @@ const ( ) type Config struct { - Port uint64 + Port uint64 + BaseChainId uint64 } type OpSimulator struct { @@ -107,3 +108,7 @@ func (opSim *OpSimulator) Endpoint() string { func (opSim *OpSimulator) ChainId() uint64 { return opSim.anvil.ChainId() } + +func (opSim *OpSimulator) BaseChainId() uint64 { + return opSim.cfg.BaseChainId +} diff --git a/genesis/genesis-l1.json b/orchestrator/genesisstates/genesis-l1.json similarity index 100% rename from genesis/genesis-l1.json rename to orchestrator/genesisstates/genesis-l1.json diff --git a/genesis/genesis-l2.json b/orchestrator/genesisstates/genesis-l2.json similarity index 100% rename from genesis/genesis-l2.json rename to orchestrator/genesisstates/genesis-l2.json diff --git a/orchestrator/orchestrator.go b/orchestrator/orchestrator.go new file mode 100644 index 00000000..738f22e0 --- /dev/null +++ b/orchestrator/orchestrator.go @@ -0,0 +1,213 @@ +package orchestrator + +import ( + "context" + _ "embed" + "fmt" + "strings" + "sync" + + "github.com/ethereum-optimism/supersim/anvil" + op_simulator "github.com/ethereum-optimism/supersim/op-simulator" + + "github.com/ethereum/go-ethereum/log" +) + +type ChainConfig struct { + Port uint64 + ChainId uint64 + // The base chain ID when the chain is a rollup. + // If set to 0, the chain is considered an L1 chain + BaseChainId uint64 +} + +type OrchestratorConfig struct { + ChainConfigs []ChainConfig +} + +type Orchestrator struct { + log log.Logger + + OpSimInstances []*op_simulator.OpSimulator + anvilInstances []*anvil.Anvil +} + +//go:embed genesisstates/genesis-l1.json +var genesisL1JSON []byte + +//go:embed genesisstates/genesis-l2.json +var genesisL2JSON []byte + +func NewOrchestrator(log log.Logger, config *OrchestratorConfig) (*Orchestrator, error) { + var opSimInstances []*op_simulator.OpSimulator + var anvilInstances []*anvil.Anvil + + l1Count := 0 + for _, config := range config.ChainConfigs { + if config.BaseChainId == 0 { + l1Count++ + } + } + + if l1Count > 1 { + return nil, fmt.Errorf("supersim does not support more than one l1") + } + + for _, chainConfig := range config.ChainConfigs { + var genesis []byte + if chainConfig.BaseChainId == 0 { + genesis = genesisL1JSON + } else { + genesis = genesisL2JSON + } + anvil := anvil.New(log, &anvil.Config{ChainId: chainConfig.ChainId, Genesis: genesis}) + anvilInstances = append(anvilInstances, anvil) + opSimInstances = append(opSimInstances, op_simulator.New(log, &op_simulator.Config{Port: chainConfig.Port, BaseChainId: chainConfig.BaseChainId}, anvil)) + } + + return &Orchestrator{log, opSimInstances, anvilInstances}, nil +} + +func (o *Orchestrator) Start(ctx context.Context) error { + o.log.Info("starting orchestrator") + + for _, anvilInstance := range o.anvilInstances { + if err := anvilInstance.Start(ctx); err != nil { + return fmt.Errorf("anvil instance chain.id=%v failed to start: %w", anvilInstance.ChainId(), err) + } + } + for _, opSimInstance := range o.OpSimInstances { + if err := opSimInstance.Start(ctx); err != nil { + return fmt.Errorf("op simulator instance chain.id=%v failed to start: %w", opSimInstance.ChainId(), err) + } + } + + if err := o.WaitUntilReady(); err != nil { + return fmt.Errorf("orchestrator failed to get ready: %w", err) + } + + o.enableAnvilLogging() + + o.log.Info("orchestrator is ready") + + return nil +} + +func (o *Orchestrator) Stop(ctx context.Context) error { + o.log.Info("stopping orchestrator") + + for _, opSim := range o.OpSimInstances { + if err := opSim.Stop(ctx); err != nil { + return fmt.Errorf("op simulator chain.id=%v failed to stop: %w", opSim.ChainId(), err) + } + o.log.Info("stopped op simulator", "chain.id", opSim.ChainId()) + } + for _, anvil := range o.anvilInstances { + if err := anvil.Stop(); err != nil { + return fmt.Errorf("anvil chain.id=%v failed to stop: %w", anvil.ChainId(), err) + } + o.log.Info("stopped anvil", "chain.id", anvil.ChainId()) + } + + o.log.Info("stopped orchestrator") + + return nil +} + +func (o *Orchestrator) Stopped() bool { + for _, anvil := range o.anvilInstances { + if stopped := anvil.Stopped(); !stopped { + return stopped + } + } + for _, opSim := range o.OpSimInstances { + if stopped := opSim.Stopped(); !stopped { + return stopped + } + } + + return true +} + +func (o *Orchestrator) WaitUntilReady() error { + var once sync.Once + var err error + ctx, cancel := context.WithCancel(context.Background()) + + handleErr := func(e error) { + if e != nil { + once.Do(func() { + err = e + cancel() + }) + } + } + + var wg sync.WaitGroup + + waitForAnvil := func(anvil *anvil.Anvil) { + defer wg.Done() + handleErr(anvil.WaitUntilReady(ctx)) + } + + o.iterateAnvilInstances(func(chain *anvil.Anvil) { + wg.Add(1) + go waitForAnvil(chain) + }) + + wg.Wait() + + return err +} + +func (o *Orchestrator) iterateAnvilInstances(fn func(anvil *anvil.Anvil)) { + for _, anvilInstance := range o.anvilInstances { + fn(anvilInstance) + } +} + +func (o *Orchestrator) enableAnvilLogging() { + o.iterateAnvilInstances(func(anvil *anvil.Anvil) { + anvil.EnableLogging() + }) +} + +func (o *Orchestrator) L1OpSimulator() *op_simulator.OpSimulator { + var result *op_simulator.OpSimulator + for _, opSim := range o.OpSimInstances { + if opSim.BaseChainId() == 0 { + result = opSim + } + } + + return result +} + +func (o *Orchestrator) L2OpSimulators() []*op_simulator.OpSimulator { + var result []*op_simulator.OpSimulator + for _, opSim := range o.OpSimInstances { + if opSim.BaseChainId() != 0 { + result = append(result, opSim) + } + } + return result +} + +func (o *Orchestrator) ConfigAsString() string { + var b strings.Builder + + fmt.Fprintf(&b, "\nSupersim Config:\n") + if o.L1OpSimulator() != nil { + fmt.Fprintf(&b, "L1:\n") + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", o.L1OpSimulator().ChainId(), o.L1OpSimulator().Endpoint()) + } + + if len(o.L2OpSimulators()) > 0 { + fmt.Fprintf(&b, "L2:\n") + for _, opSim := range o.L2OpSimulators() { + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", opSim.ChainId(), opSim.Endpoint()) + } + } + + return b.String() +} diff --git a/supersim.go b/supersim.go index c3db084a..3c3dd79e 100644 --- a/supersim.go +++ b/supersim.go @@ -1,48 +1,26 @@ package supersim import ( - _ "embed" "fmt" "strings" - "sync" "context" - "github.com/ethereum-optimism/supersim/anvil" - opsim "github.com/ethereum-optimism/supersim/op-simulator" + "github.com/ethereum-optimism/supersim/orchestrator" "github.com/ethereum/go-ethereum/log" ) type Config struct { - l1Chain ChainConfig - l2Chains []ChainConfig + orchestratorConfig orchestrator.OrchestratorConfig } -type ChainConfig struct { - anvilConfig anvil.Config - opSimConfig opsim.Config -} - -//go:embed genesis/genesis-l1.json -var genesisL1JSON []byte - -//go:embed genesis/genesis-l2.json -var genesisL2JSON []byte - var DefaultConfig = Config{ - l1Chain: ChainConfig{ - anvilConfig: anvil.Config{ChainId: 1, Port: 0, Genesis: genesisL1JSON}, - opSimConfig: opsim.Config{Port: 0}, - }, - l2Chains: []ChainConfig{ - { - anvilConfig: anvil.Config{ChainId: 10, Port: 0, Genesis: genesisL2JSON}, - opSimConfig: opsim.Config{Port: 0}, - }, - { - anvilConfig: anvil.Config{ChainId: 30, Port: 0, Genesis: genesisL2JSON}, - opSimConfig: opsim.Config{Port: 0}, + orchestratorConfig: orchestrator.OrchestratorConfig{ + ChainConfigs: []orchestrator.ChainConfig{ + {ChainId: 1, Port: 0}, + {ChainId: 10, BaseChainId: 1, Port: 0}, + {ChainId: 30, BaseChainId: 1, Port: 0}, }, }, } @@ -50,56 +28,25 @@ var DefaultConfig = Config{ type Supersim struct { log log.Logger - l1Anvil *anvil.Anvil - l1OpSim *opsim.OpSimulator - - l2Anvils map[uint64]*anvil.Anvil - l2OpSims map[uint64]*opsim.OpSimulator + Orchestrator *orchestrator.Orchestrator } -func NewSupersim(log log.Logger, config *Config) *Supersim { - l1Anvil := anvil.New(log, &config.l1Chain.anvilConfig) - l1OpSim := opsim.New(log, &config.l1Chain.opSimConfig, l1Anvil) - - l2Anvils := make(map[uint64]*anvil.Anvil) - l2OpSims := make(map[uint64]*opsim.OpSimulator) - for i := range config.l2Chains { - l2ChainConfig := config.l2Chains[i] - l2Anvil := anvil.New(log, &l2ChainConfig.anvilConfig) - l2Anvils[l2ChainConfig.anvilConfig.ChainId] = l2Anvil - l2OpSims[l2ChainConfig.anvilConfig.ChainId] = opsim.New(log, &l2ChainConfig.opSimConfig, l2Anvil) +func NewSupersim(log log.Logger, config *Config) (*Supersim, error) { + o, err := orchestrator.NewOrchestrator(log, &DefaultConfig.orchestratorConfig) + if err != nil { + return nil, fmt.Errorf("failed to create orchestrator") } - return &Supersim{log, l1Anvil, l1OpSim, l2Anvils, l2OpSims} + return &Supersim{log, o}, nil } func (s *Supersim) Start(ctx context.Context) error { s.log.Info("starting supersim") - if err := s.l1Anvil.Start(ctx); err != nil { - return fmt.Errorf("l1 anvil failed to start: %w", err) - } - if err := s.l1OpSim.Start(ctx); err != nil { - return fmt.Errorf("l1 op simulator failed to start: %w", err) - } - - for _, l2Anvil := range s.l2Anvils { - if err := l2Anvil.Start(ctx); err != nil { - return fmt.Errorf("l2 anvil failed to start: %w", err) - } - } - for _, l2OpSim := range s.l2OpSims { - if err := l2OpSim.Start(ctx); err != nil { - return fmt.Errorf("l2 op simulator failed to start: %w", err) - } + if err := s.Orchestrator.Start(ctx); err != nil { + return fmt.Errorf("orchestrator failed to start: %w", err) } - if err := s.WaitUntilReady(); err != nil { - return fmt.Errorf("supersim failed to get ready: %w", err) - } - - s.EnableLogging() - s.log.Info("supersim is ready") s.log.Info(s.ConfigAsString()) @@ -109,107 +56,22 @@ func (s *Supersim) Start(ctx context.Context) error { func (s *Supersim) Stop(ctx context.Context) error { s.log.Info("stopping supersim") - for _, l2OpSim := range s.l2OpSims { - if err := l2OpSim.Stop(ctx); err != nil { - return fmt.Errorf("l2 op simulator failed to stop: %w", err) - } - s.log.Info("stopped op simulator", "chain.id", l2OpSim.ChainId()) - } - for _, l2Anvil := range s.l2Anvils { - if err := l2Anvil.Stop(); err != nil { - return fmt.Errorf("l2 anvil failed to stop: %w", err) - } - } - - if err := s.l1OpSim.Stop(ctx); err != nil { - return fmt.Errorf("l1 op simulator failed to stop: %w", err) - } - if err := s.l1Anvil.Stop(); err != nil { - return fmt.Errorf("l1 anvil failed to stop: %w", err) + if err := s.Orchestrator.Stop(ctx); err != nil { + return fmt.Errorf("orchestrator failed to stop: %w", err) } - s.log.Info("stopped op simulator", "chain.id", s.l1OpSim.ChainId()) return nil } func (s *Supersim) Stopped() bool { - for _, l2OpSim := range s.l2OpSims { - if stopped := l2OpSim.Stopped(); !stopped { - return stopped - } - } - for _, l2Anvil := range s.l2Anvils { - if stopped := l2Anvil.Stopped(); !stopped { - return stopped - } - } - - if stopped := s.l1Anvil.Stopped(); !stopped { - return stopped - } - if stopped := s.l1OpSim.Stopped(); !stopped { - return stopped - } - - return true -} - -func (s *Supersim) WaitUntilReady() error { - var once sync.Once - var err error - ctx, cancel := context.WithCancel(context.Background()) - - handleErr := func(e error) { - if e != nil { - once.Do(func() { - err = e - cancel() - }) - } - } - - var wg sync.WaitGroup - - waitForAnvil := func(anvil *anvil.Anvil) { - defer wg.Done() - handleErr(anvil.WaitUntilReady(ctx)) - } - - s.IterateChains(func(chain *anvil.Anvil) { - wg.Add(1) - go waitForAnvil(chain) - }) - - wg.Wait() - - return err -} - -func (s *Supersim) EnableLogging() { - s.IterateChains(func(chain *anvil.Anvil) { - chain.EnableLogging() - }) -} - -func (s *Supersim) IterateChains(fn func(anvil *anvil.Anvil)) { - fn(s.l1Anvil) - - for _, l2Anvil := range s.l2Anvils { - fn(l2Anvil) - } + return s.Orchestrator.Stopped() } func (s *Supersim) ConfigAsString() string { var b strings.Builder fmt.Fprintf(&b, "\nSupersim Config:\n") - fmt.Fprintf(&b, "L1:\n") - fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", s.l1OpSim.ChainId(), s.l1OpSim.Endpoint()) - - fmt.Fprintf(&b, "L2:\n") - for _, l2OpSim := range s.l2OpSims { - fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", l2OpSim.ChainId(), l2OpSim.Endpoint()) - } + fmt.Fprint(&b, s.Orchestrator.ConfigAsString()) return b.String() } diff --git a/supersim_test.go b/supersim_test.go index c738548c..42eb3e2c 100644 --- a/supersim_test.go +++ b/supersim_test.go @@ -31,7 +31,7 @@ type TestSuite struct { func createTestSuite(t *testing.T) *TestSuite { cfg := &DefaultConfig testlog := testlog.Logger(t, log.LevelInfo) - supersim := NewSupersim(testlog, cfg) + supersim, _ := NewSupersim(testlog, cfg) t.Cleanup(func() { if err := supersim.Stop(context.Background()); err != nil { t.Errorf("failed to stop supersim: %s", err) @@ -53,20 +53,12 @@ func createTestSuite(t *testing.T) *TestSuite { func TestStartup(t *testing.T) { testSuite := createTestSuite(t) - var chainId math.HexOrDecimal64 - - // test that all chains can be queried - l1Client, err := rpc.Dial(testSuite.Supersim.l1OpSim.Endpoint()) - require.NoError(t, err) - require.NoError(t, l1Client.CallContext(context.Background(), &chainId, "eth_chainId")) - require.Equal(t, uint64(chainId), testSuite.Supersim.l1OpSim.ChainId()) - l1Client.Close() - - for id, l2Chain := range testSuite.Supersim.l2OpSims { - require.Equal(t, id, l2Chain.ChainId()) - - l2Client, err := rpc.Dial(l2Chain.Endpoint()) + require.True(t, len(testSuite.Supersim.Orchestrator.OpSimInstances) > 0) + // test that all op simulators can be queried + for _, opSim := range testSuite.Supersim.Orchestrator.OpSimInstances { + l2Client, err := rpc.Dial(opSim.Endpoint()) require.NoError(t, err) + var chainId math.HexOrDecimal64 require.NoError(t, l2Client.CallContext(context.Background(), &chainId, "eth_chainId")) // Commented out due to a bug in foundry that sets the chain id to 1 whenever genesis.json file is supplied @@ -79,9 +71,10 @@ func TestStartup(t *testing.T) { func TestGenesisState(t *testing.T) { testSuite := createTestSuite(t) + require.True(t, len(testSuite.Supersim.Orchestrator.L2OpSimulators()) > 0) // assert that the predeploys exists on the l2 anvil instances - for _, l2Chain := range testSuite.Supersim.l2OpSims { - client, err := rpc.Dial(l2Chain.Endpoint()) + for _, l2OpSim := range testSuite.Supersim.Orchestrator.L2OpSimulators() { + client, err := rpc.Dial(l2OpSim.Endpoint()) require.NoError(t, err) defer client.Close()