Skip to content

Commit

Permalink
feat: create op-simulator
Browse files Browse the repository at this point in the history
  • Loading branch information
tremarkley committed Jul 9, 2024
1 parent ca8aac7 commit 5c0d8af
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 32 deletions.
99 changes: 99 additions & 0 deletions op-simulator/op_simulator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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.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
}

func (opSim *OpSimulator) Endpoint() string {
return fmt.Sprintf("http://%s:%d", host, opSim.cfg.Port)
}

func (opSim *OpSimulator) ChainId() uint64 {
return opSim.anvil.ChainId()
}
115 changes: 84 additions & 31 deletions supersim.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,41 +31,54 @@ 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 {
return fmt.Errorf("l1 chain failed to start: %w", err)
if err := s.l1Anvil.Start(ctx); err != nil {
return fmt.Errorf("l1 anvil failed to start: %w", err)
}

for _, l2Chain := range s.l2Chains {
if err := l2Chain.Start(ctx); err != nil {
return fmt.Errorf("l2 chain 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)
}
}

if err := s.l1OpSim.Start(ctx); err != nil {
return fmt.Errorf("l1 op simulator 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)
}
}

Expand All @@ -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.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 {
s.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 {
Expand All @@ -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()
Expand All @@ -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.l1OpSim.ChainId(), s.l1OpSim.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 _, l2OpSim := range s.l2OpSims {
fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", l2OpSim.ChainId(), l2OpSim.Endpoint())
}

return b.String()
Expand Down
2 changes: 1 addition & 1 deletion supersim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 5c0d8af

Please sign in to comment.