Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create op-simulator as basic proxy #33

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
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()
}
113 changes: 82 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

In order to simplify the changes in this file, I wonder if we should simply have OpSimulator manage the lifecycle of the the underlying chain, in this case anvil. So that we dont need reference to each individual pieces here

Not worth addressing in the PR. Just some food for thought for ways to clean up this code in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I wont address in this PR, since I want to keep this change small.

If we want to stick to the original design, we should create an Orchestrator service that handles spinning up all the anvil instances and OpSimulator instances.

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,56 @@ 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)
}
s.log.Info("Stopped op simulator", "chain.id", s.l1OpSim.ChainId())

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())
}

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 +166,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 +183,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
Loading