diff --git a/op-simulator/op_simulator.go b/op-simulator/op_simulator.go new file mode 100644 index 00000000..49a20fdb --- /dev/null +++ b/op-simulator/op_simulator.go @@ -0,0 +1,91 @@ +package op_simulator + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "sync/atomic" + + ophttp "github.com/ethereum-optimism/optimism/op-service/httputil" + "github.com/ethereum-optimism/supersim/anvil" + "github.com/ethereum/go-ethereum/log" +) + +type Config struct { + Port uint64 +} + +type OpSimulator struct { + Log log.Logger + Anvil *anvil.Anvil + HttpServer *ophttp.HTTPServer + + stopped atomic.Bool + + cfg *Config +} + +const ( + host = "127.0.0.1" +) + +func New(log log.Logger, cfg *Config, anvil *anvil.Anvil) *OpSimulator { + return &OpSimulator{ + Log: log, + cfg: cfg, + Anvil: anvil, + } +} + +func (opSim *OpSimulator) Start(ctx context.Context) error { + proxy, err := opSim.createReverseProxy() + if err != nil { + return fmt.Errorf("Error creating reverse proxy: %w", err) + } + mux := http.NewServeMux() + mux.Handle("/", proxy) + endpoint := net.JoinHostPort(host, strconv.Itoa(int(opSim.cfg.Port))) + + hs, err := ophttp.StartHTTPServer(endpoint, mux) + if err != nil { + return fmt.Errorf("failed to start HTTP RPC server: %w", err) + } else { + opSim.Log.Info(fmt.Sprintf("Listening on %v", endpoint), "chain.id", opSim.Anvil.ChainId()) + } + opSim.HttpServer = hs + + return nil +} + +func (opSim *OpSimulator) Stop(ctx context.Context) error { + if opSim.stopped.Load() { + return errors.New("already stopped") + } + if !opSim.stopped.CompareAndSwap(false, true) { + return nil // someone else stopped + } + + return opSim.HttpServer.Stop(ctx) +} + +func (a *OpSimulator) Stopped() bool { + return a.stopped.Load() +} + +func (opSim *OpSimulator) createReverseProxy() (*httputil.ReverseProxy, error) { + targetURL, err := url.Parse(opSim.Anvil.Endpoint()) + if err != nil { + return nil, fmt.Errorf("failed to parse target URL: %w", err) + } + proxy := &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(targetURL) + }, + } + return proxy, nil +} diff --git a/supersim.go b/supersim.go index ab5f6f0f..ee7d5ba0 100644 --- a/supersim.go +++ b/supersim.go @@ -9,13 +9,19 @@ import ( "context" "github.com/ethereum-optimism/supersim/anvil" + op_simulator "github.com/ethereum-optimism/supersim/op-simulator" "github.com/ethereum/go-ethereum/log" ) type Config struct { - l1Chain anvil.Config - l2Chains []anvil.Config + l1Chain Chain + l2Chains []Chain +} + +type Chain struct { + anvilConfig anvil.Config + opSimConfig op_simulator.Config } //go:embed genesis/genesis-l1.json @@ -25,44 +31,57 @@ var genesisL1JSON []byte var genesisL2JSON []byte var DefaultConfig = Config{ - l1Chain: anvil.Config{ChainId: 1, Port: 8545, Genesis: genesisL1JSON}, - l2Chains: []anvil.Config{ - {ChainId: 10, Port: 9545, Genesis: genesisL2JSON}, - {ChainId: 30, Port: 9555, Genesis: genesisL2JSON}, - }, + l1Chain: Chain{anvilConfig: anvil.Config{ChainId: 1, Port: 8545, Genesis: genesisL1JSON}, opSimConfig: op_simulator.Config{Port: 8546}}, + l2Chains: []Chain{{anvilConfig: anvil.Config{ChainId: 10, Port: 9545, Genesis: genesisL2JSON}, opSimConfig: op_simulator.Config{Port: 9546}}, {anvilConfig: anvil.Config{ChainId: 30, Port: 9555, Genesis: genesisL2JSON}, opSimConfig: op_simulator.Config{Port: 9556}}}, } type Supersim struct { log log.Logger - l1Chain *anvil.Anvil - l2Chains map[uint64]*anvil.Anvil + l1Anvil *anvil.Anvil + l2Anvils map[uint64]*anvil.Anvil + l1OpSim *op_simulator.OpSimulator + l2OpSims map[uint64]*op_simulator.OpSimulator } func NewSupersim(log log.Logger, config *Config) *Supersim { - l1Chain := anvil.New(log, &config.l1Chain) + l1Anvil := anvil.New(log, &config.l1Chain.anvilConfig) + l1OpSim := op_simulator.New(log, &config.l1Chain.opSimConfig, l1Anvil) - l2Chains := make(map[uint64]*anvil.Anvil) + l2Anvils := make(map[uint64]*anvil.Anvil) + l2OpSims := make(map[uint64]*op_simulator.OpSimulator) for _, l2ChainConfig := range config.l2Chains { - l2Chains[l2ChainConfig.ChainId] = anvil.New(log, &l2ChainConfig) + l2Anvil := anvil.New(log, &l2ChainConfig.anvilConfig) + l2Anvils[l2ChainConfig.anvilConfig.ChainId] = l2Anvil + l2OpSims[l2ChainConfig.anvilConfig.ChainId] = op_simulator.New(log, &l2ChainConfig.opSimConfig, l2Anvil) } - return &Supersim{log, l1Chain, l2Chains} + return &Supersim{log, l1Anvil, l2Anvils, l1OpSim, l2OpSims} } func (s *Supersim) Start(ctx context.Context) error { s.log.Info("starting supersim") - if err := s.l1Chain.Start(ctx); err != nil { + if err := s.l1Anvil.Start(ctx); err != nil { return fmt.Errorf("l1 chain failed to start: %w", err) } - for _, l2Chain := range s.l2Chains { - if err := l2Chain.Start(ctx); err != nil { + for _, l2Anvil := range s.l2Anvils { + if err := l2Anvil.Start(ctx); err != nil { return fmt.Errorf("l2 chain failed to start: %w", err) } } + if err := s.l1OpSim.Start(ctx); err != nil { + return fmt.Errorf("l1 op sim failed to start: %w", err) + } + + for _, l2OpSim := range s.l2OpSims { + if err := l2OpSim.Start(ctx); err != nil { + return fmt.Errorf("l2 op sim failed to start: %w", err) + } + } + if err := s.WaitUntilReady(); err != nil { return fmt.Errorf("supersim failed to get ready: %w", err) } @@ -73,24 +92,58 @@ func (s *Supersim) Start(ctx context.Context) error { return nil } -func (s *Supersim) Stop(_ context.Context) error { +func (s *Supersim) Stop(ctx context.Context) error { s.log.Info("stopping supersim") - for _, l2Chain := range s.l2Chains { - if err := l2Chain.Stop(); err != nil { - return fmt.Errorf("l2 chain failed to stop: %w", err) + for _, l2Anvil := range s.l2Anvils { + if err := l2Anvil.Stop(); err != nil { + return fmt.Errorf("l2 anvil failed to stop: %w", err) } } - if err := s.l1Chain.Stop(); err != nil { - return fmt.Errorf("l1 chain 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.l1OpSim.Stop(ctx); err != nil { + return fmt.Errorf("l1 op simulator failed to stop: %w", err) + } else { + s.l1OpSim.Log.Info("Stopped op simulator", "endpoint", s.l1OpSim.HttpServer.Addr().String()) + } + + for _, l2OpSim := range s.l2OpSims { + if err := l2OpSim.Stop(ctx); err != nil { + return fmt.Errorf("l2 op simulator failed to stop: %w", err) + } else { + l2OpSim.Log.Info("Stopped op simulator", "endpoint", l2OpSim.HttpServer.Addr().String()) + } } return nil } func (s *Supersim) Stopped() bool { - return s.l1Chain.Stopped() + for _, l2Anvil := range s.l2Anvils { + if stopped := l2Anvil.Stopped(); !stopped { + return stopped + } + } + + if stopped := s.l1Anvil.Stopped(); !stopped { + return stopped + } + + for _, l2OpSim := range s.l2OpSims { + if stopped := l2OpSim.Stopped(); !stopped { + return stopped + } + } + + if stopped := s.l1OpSim.Stopped(); !stopped { + return stopped + } + + return true } func (s *Supersim) WaitUntilReady() error { @@ -115,11 +168,11 @@ func (s *Supersim) WaitUntilReady() error { } wg.Add(1) - go waitForAnvil(s.l1Chain) + go waitForAnvil(s.l1Anvil) - for _, l2Chain := range s.l2Chains { + for _, l2Anvil := range s.l2Anvils { wg.Add(1) - go waitForAnvil(l2Chain) + go waitForAnvil(l2Anvil) } wg.Wait() @@ -132,11 +185,11 @@ func (s *Supersim) ConfigAsString() string { fmt.Fprintf(&b, "\nSupersim Config:\n") fmt.Fprintf(&b, "L1:\n") - fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", s.l1Chain.ChainId(), s.l1Chain.Endpoint()) + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", s.l1Anvil.ChainId(), s.l1Anvil.Endpoint()) fmt.Fprintf(&b, "L2:\n") - for _, l2Chain := range s.l2Chains { - fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", l2Chain.ChainId(), l2Chain.Endpoint()) + for _, l2Anvil := range s.l2Anvils { + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", l2Anvil.ChainId(), l2Anvil.Endpoint()) } return b.String() diff --git a/supersim_test.go b/supersim_test.go index defaa78e..6f44c2c7 100644 --- a/supersim_test.go +++ b/supersim_test.go @@ -38,7 +38,7 @@ func TestGenesisState(t *testing.T) { } for _, l2ChainConfig := range DefaultConfig.l2Chains { - rpcUrl := fmt.Sprintf("http://127.0.0.1:%d", l2ChainConfig.Port) + rpcUrl := fmt.Sprintf("http://127.0.0.1:%d", l2ChainConfig.opSimConfig.Port) client, clientCreateErr := rpc.Dial(rpcUrl) if clientCreateErr != nil {