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: starting with port 0 #36

Merged
merged 4 commits into from
Jul 10, 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
77 changes: 49 additions & 28 deletions anvil/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"sync/atomic"
"time"
Expand Down Expand Up @@ -38,7 +39,8 @@ type Anvil struct {
}

const (
host = "127.0.0.1"
host = "127.0.0.1"
anvilListeningLogStr = "Listening on"
)

func New(log log.Logger, cfg *Config) *Anvil {
Expand All @@ -57,23 +59,22 @@ func (a *Anvil) Start(ctx context.Context) error {
return errors.New("anvil already started")
}

tempFile, err := os.CreateTemp("", "genesis-*.json")
if err != nil {
return fmt.Errorf("error creating temporary genesis file: %w", err)
}

_, err = tempFile.Write(a.cfg.Genesis)
if err != nil {
return fmt.Errorf("error writing to genesis file: %w", err)
}

// Prep args
args := []string{
"--silent",
"--host", host,
"--chain-id", fmt.Sprintf("%d", a.cfg.ChainId),
"--port", fmt.Sprintf("%d", a.cfg.Port),
"--init", tempFile.Name(),
}

if len(a.cfg.Genesis) > 0 {
tempFile, err := os.CreateTemp("", "genesis-*.json")
if err != nil {
return fmt.Errorf("error creating temporary genesis file: %w", err)
}
if _, err = tempFile.Write(a.cfg.Genesis); err != nil {
return fmt.Errorf("error writing to genesis file: %w", err)
}

args = append(args, "--init", tempFile.Name())
}

anvilLog := a.log.New("role", "anvil", "chain.id", a.cfg.ChainId)
Expand All @@ -84,6 +85,10 @@ func (a *Anvil) Start(ctx context.Context) error {
a.resourceCancel()
}()

// In the event anvil is started with port 0, we'll need to block
// and see what port anvil eventually binds to when started
anvilPortCh := make(chan uint64)

// Handle stdout/stderr
// - TODO: Figure out best way to dump into logger. Some hex isn't showing appropriately
stdout, err := a.cmd.StdoutPipe()
Expand All @@ -97,7 +102,17 @@ func (a *Anvil) Start(ctx context.Context) error {
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
anvilLog.Info(scanner.Text())
txt := scanner.Text()
anvilLog.Info(txt)

// scan for port if applicable
if a.cfg.Port == 0 && strings.HasPrefix(txt, anvilListeningLogStr) {
port, err := strconv.ParseInt(strings.Split(txt, ":")[1], 10, 64)
if err != nil {
panic(fmt.Errorf("unexpected anvil listening port log: %w", err))
}
anvilPortCh <- uint64(port)
}
}
}()
go func() {
Expand All @@ -112,16 +127,7 @@ func (a *Anvil) Start(ctx context.Context) error {
return fmt.Errorf("failed to start anvil: %w", err)
}

rpcClient, err := rpc.Dial(a.Endpoint())
if err != nil {
return fmt.Errorf("failed to create RPC client: %w", err)
}
a.rpcClient = rpcClient

go func() {
defer os.Remove(tempFile.Name())
defer a.rpcClient.Close()

if err := a.cmd.Wait(); err != nil {
anvilLog.Error("anvil terminated with an error", "error", err)
} else {
Expand All @@ -131,6 +137,23 @@ func (a *Anvil) Start(ctx context.Context) error {
a.stoppedCh <- struct{}{}
}()

// wait & update the port if applicable. Since we're in the same routine to which `Start`
// is called, we're safe to overrwrite the `Port` field which the caller can observe
if a.cfg.Port == 0 {
done := ctx.Done()
select {
case a.cfg.Port = <-anvilPortCh:
case <-done:
return ctx.Err()
}
}

rpcClient, err := rpc.Dial(a.Endpoint())
if err != nil {
return fmt.Errorf("failed to create RPC client: %w", err)
}
a.rpcClient = rpcClient

return nil
}

Expand All @@ -142,6 +165,7 @@ func (a *Anvil) Stop() error {
return nil // someone else stopped
}

a.rpcClient.Close()
a.resourceCancel()
<-a.stoppedCh
return nil
Expand Down Expand Up @@ -181,12 +205,9 @@ func (a *Anvil) WaitUntilReady(ctx context.Context) error {
return fmt.Errorf("timed out waiting for response from client")
case <-ticker.C:
var result string
callErr := a.rpcClient.Call(&result, "web3_clientVersion")

if callErr != nil {
if err := a.rpcClient.Call(&result, "web3_clientVersion"); err != nil {
continue
}

if strings.HasPrefix(result, "anvil") {
return nil
}
Expand Down
32 changes: 32 additions & 0 deletions anvil/anvil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package anvil

import (
"context"
"testing"

"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)

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

require.NoError(t, anvil.Start(context.Background()))

// port overridden on startup
require.NotEqual(t, cfg.Port, 0)

client, err := rpc.Dial(anvil.Endpoint())
require.NoError(t, err)

// query chainId
var chainId math.HexOrDecimal64
require.NoError(t, client.CallContext(context.Background(), &chainId, "eth_chainId"))
require.Equal(t, uint64(chainId), cfg.ChainId)
}
24 changes: 17 additions & 7 deletions op-simulator/op_simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import (
"net/http/httputil"
"net/url"
"strconv"
"strings"
"sync/atomic"

ophttp "github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/supersim/anvil"
"github.com/ethereum/go-ethereum/log"
)

const (
host = "127.0.0.1"
)

type Config struct {
Port uint64
}
Expand All @@ -30,10 +35,6 @@ type OpSimulator struct {
cfg *Config
}

const (
host = "127.0.0.1"
)

func New(log log.Logger, cfg *Config, anvil *anvil.Anvil) *OpSimulator {
return &OpSimulator{
log: log,
Expand All @@ -47,18 +48,27 @@ func (opSim *OpSimulator) Start(ctx context.Context) error {
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)
hs, err := ophttp.StartHTTPServer(net.JoinHostPort(host, fmt.Sprintf("%d", opSim.cfg.Port)), 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.log.Info("started op-simulator", "chain.id", opSim.ChainId(), "addr", hs.Addr())
opSim.httpServer = hs

if opSim.cfg.Port == 0 {
port, err := strconv.ParseInt(strings.Split(hs.Addr().String(), ":")[1], 10, 64)
if err != nil {
panic(fmt.Errorf("unexpected op-simulator listening port: %w", err))
}

opSim.cfg.Port = uint64(port)
}

return nil
}

Expand Down
12 changes: 6 additions & 6 deletions supersim.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ var genesisL2JSON []byte

var DefaultConfig = Config{
l1Chain: ChainConfig{
anvilConfig: anvil.Config{ChainId: 1, Port: 8545, Genesis: genesisL1JSON},
opSimConfig: opsim.Config{Port: 8546},
anvilConfig: anvil.Config{ChainId: 1, Port: 0, Genesis: genesisL1JSON},
opSimConfig: opsim.Config{Port: 0},
},
l2Chains: []ChainConfig{
{
anvilConfig: anvil.Config{ChainId: 10, Port: 9545, Genesis: genesisL2JSON},
opSimConfig: opsim.Config{Port: 9546},
anvilConfig: anvil.Config{ChainId: 10, Port: 0, Genesis: genesisL2JSON},
opSimConfig: opsim.Config{Port: 0},
},
{
anvilConfig: anvil.Config{ChainId: 30, Port: 9555, Genesis: genesisL2JSON},
opSimConfig: opsim.Config{Port: 9556},
anvilConfig: anvil.Config{ChainId: 30, Port: 0, Genesis: genesisL2JSON},
opSimConfig: opsim.Config{Port: 0},
},
},
}
Expand Down
1 change: 0 additions & 1 deletion supersim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ type TestSuite struct {

func createTestSuite(t *testing.T) *TestSuite {
cfg := &DefaultConfig

testlog := testlog.Logger(t, log.LevelInfo)
supersim := NewSupersim(testlog, cfg)
t.Cleanup(func() {
Expand Down
Loading