Skip to content

Commit

Permalink
Merge pull request #43 from ethereum-optimism/harry/orchestrator
Browse files Browse the repository at this point in the history
feat(orchestrator): create orchestrator package
  • Loading branch information
tremarkley authored Jul 11, 2024
2 parents 4093e98 + e556e6e commit 93d8c5b
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 180 deletions.
27 changes: 19 additions & 8 deletions anvil/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
)

type Config struct {
ChainId uint64
Port uint64
Genesis []byte
ChainID uint64
SourceChainID uint64
Port uint64
Genesis []byte
}

type Anvil struct {
Expand Down Expand Up @@ -63,7 +64,7 @@ func (a *Anvil) Start(ctx context.Context) error {

args := []string{
"--host", host,
"--chain-id", fmt.Sprintf("%d", a.cfg.ChainId),
"--chain-id", fmt.Sprintf("%d", a.cfg.ChainID),
"--port", fmt.Sprintf("%d", a.cfg.Port),
}

Expand All @@ -79,7 +80,7 @@ func (a *Anvil) Start(ctx context.Context) error {
args = append(args, "--init", tempFile.Name())
}

anvilLog := a.log.New("role", "anvil", "chain.id", a.cfg.ChainId)
anvilLog := a.log.New("role", "anvil", "chain.id", a.cfg.ChainID)
anvilLog.Info("starting anvil", "args", args)
a.cmd = exec.CommandContext(a.resourceCtx, "anvil", args...)
go func() {
Expand All @@ -92,7 +93,7 @@ func (a *Anvil) Start(ctx context.Context) error {
anvilPortCh := make(chan uint64)

// Handle stdout/stderr
logFile, err := os.CreateTemp("", fmt.Sprintf("anvil-chain-%d-", a.cfg.ChainId))
logFile, err := os.CreateTemp("", fmt.Sprintf("anvil-chain-%d-", a.cfg.ChainID))
if err != nil {
return fmt.Errorf("failed to create temp log file: %w", err)
}
Expand Down Expand Up @@ -187,8 +188,12 @@ func (a *Anvil) Endpoint() string {
return fmt.Sprintf("http://%s:%d", host, a.cfg.Port)
}

func (a *Anvil) ChainId() uint64 {
return a.cfg.ChainId
func (a *Anvil) ChainID() uint64 {
return a.cfg.ChainID
}

func (a *Anvil) SourceChainID() uint64 {
return a.cfg.SourceChainID
}

func (a *Anvil) LogPath() string {
Expand Down Expand Up @@ -221,3 +226,9 @@ func (a *Anvil) WaitUntilReady(ctx context.Context) error {
}
}
}

func (a *Anvil) String() string {
var b strings.Builder
fmt.Fprintf(&b, "Chain ID: %d RPC: %s LogPath: %s", a.ChainID(), a.Endpoint(), a.LogPath())
return b.String()
}
4 changes: 2 additions & 2 deletions anvil/anvil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestAnvil(t *testing.T) {
cfg := Config{ChainId: 10, Port: 0}
cfg := Config{ChainID: 10, Port: 0}
testlog := testlog.Logger(t, log.LevelInfo)
anvil := New(testlog, &cfg)

Expand All @@ -28,5 +28,5 @@ func TestAnvil(t *testing.T) {
// query chainId
var chainId math.HexOrDecimal64
require.NoError(t, client.CallContext(context.Background(), &chainId, "eth_chainId"))
require.Equal(t, uint64(chainId), cfg.ChainId)
require.Equal(t, uint64(chainId), cfg.ChainID)
}
7 changes: 6 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
19 changes: 15 additions & 4 deletions op-simulator/op_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const (
)

type Config struct {
Port uint64
Port uint64
SourceChainID uint64
}

type OpSimulator struct {
Expand Down Expand Up @@ -57,7 +58,7 @@ func (opSim *OpSimulator) Start(ctx context.Context) error {
return fmt.Errorf("failed to start HTTP RPC server: %w", err)
}

opSim.log.Info("started op-simulator", "chain.id", opSim.ChainId(), "addr", hs.Addr())
opSim.log.Info("started op-simulator", "chain.id", opSim.ChainID(), "addr", hs.Addr())
opSim.httpServer = hs

if opSim.cfg.Port == 0 {
Expand Down Expand Up @@ -104,6 +105,16 @@ func (opSim *OpSimulator) Endpoint() string {
return fmt.Sprintf("http://%s:%d", host, opSim.cfg.Port)
}

func (opSim *OpSimulator) ChainId() uint64 {
return opSim.anvil.ChainId()
func (opSim *OpSimulator) ChainID() uint64 {
return opSim.anvil.ChainID()
}

func (opSim *OpSimulator) SourceChainID() uint64 {
return opSim.cfg.SourceChainID
}

func (opSim *OpSimulator) String() string {
var b strings.Builder
fmt.Fprintf(&b, "Chain ID: %d RPC: %s LogPath: %s", opSim.ChainID(), opSim.Endpoint(), opSim.anvil.LogPath())
return b.String()
}
File renamed without changes.
File renamed without changes.
198 changes: 198 additions & 0 deletions orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
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
SourceChainID 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.SourceChainID == 0 {
l1Count++
}
}

if l1Count > 1 {
return nil, fmt.Errorf("supersim does not support more than one l1")
}

for _, chainConfig := range config.ChainConfigs {
genesis := genesisL2JSON
if chainConfig.SourceChainID == 0 {
genesis = genesisL1JSON
}
anvil := anvil.New(log, &anvil.Config{ChainID: chainConfig.ChainID, SourceChainID: chainConfig.SourceChainID, Genesis: genesis})
anvilInstances = append(anvilInstances, anvil)
// Only create Op Simulators for L2 chains.
if chainConfig.SourceChainID != 0 {
opSimInstances = append(opSimInstances, op_simulator.New(log, &op_simulator.Config{Port: chainConfig.Port, SourceChainID: chainConfig.SourceChainID}, 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.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 {
return
}

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) L1Anvil() *anvil.Anvil {
var result *anvil.Anvil
for _, anvil := range o.anvilInstances {
if anvil.SourceChainID() == 0 {
result = anvil
}
}

return result
}

func (o *Orchestrator) ConfigAsString() string {
var b strings.Builder

fmt.Fprintf(&b, "\nSupersim Config:\n")
if o.L1Anvil() != nil {
fmt.Fprintf(&b, "L1:\n")
fmt.Fprintf(&b, " %s\n", o.L1Anvil().String())
}

if len(o.OpSimInstances) > 0 {
fmt.Fprintf(&b, "L2:\n")
for _, opSim := range o.OpSimInstances {
fmt.Fprintf(&b, " %s\n", opSim.String())
}
}

return b.String()
}
Loading

0 comments on commit 93d8c5b

Please sign in to comment.