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(orchestrator): create orchestrator package #43

Merged
merged 1 commit into from
Jul 11, 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
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())
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if we should consider adding a timeout at all https://pkg.go.dev/context#WithTimeout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nitaliano I think for now we dont need one since this is only waiting on anvil instances to be ready, and that wait function already has a timeout in it:

timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
. In the future it could be necessary if there are other services we need to wait on. I'll leave it out for now since we don't currently need it. Let me know if you think otherwise!


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
Loading