From d2443fcd161b064ff4ab51152bcf1e932dd716de Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Tue, 9 Jul 2024 14:17:28 -0400 Subject: [PATCH 1/4] starting anvil with port 0 --- anvil/anvil.go | 78 +++++++++++++++++++++++------------- anvil/anvil_test.go | 32 +++++++++++++++ op-simulator/op_simulator.go | 24 +++++++---- supersim.go | 12 +++--- supersim_test.go | 1 - 5 files changed, 105 insertions(+), 42 deletions(-) create mode 100644 anvil/anvil_test.go diff --git a/anvil/anvil.go b/anvil/anvil.go index aec8b47f..0294d926 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "strconv" "strings" "sync/atomic" "time" @@ -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 { @@ -57,23 +59,23 @@ 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", + //"--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) @@ -84,6 +86,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() @@ -97,7 +103,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() { @@ -112,16 +128,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 { @@ -131,6 +138,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 } @@ -142,6 +166,7 @@ func (a *Anvil) Stop() error { return nil // someone else stopped } + a.rpcClient.Close() a.resourceCancel() <-a.stoppedCh return nil @@ -181,12 +206,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 } diff --git a/anvil/anvil_test.go b/anvil/anvil_test.go new file mode 100644 index 00000000..90e213cf --- /dev/null +++ b/anvil/anvil_test.go @@ -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 overriden 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) +} diff --git a/op-simulator/op_simulator.go b/op-simulator/op_simulator.go index bab439a1..54cf11a4 100644 --- a/op-simulator/op_simulator.go +++ b/op-simulator/op_simulator.go @@ -9,6 +9,7 @@ import ( "net/http/httputil" "net/url" "strconv" + "strings" "sync/atomic" ophttp "github.com/ethereum-optimism/optimism/op-service/httputil" @@ -16,6 +17,10 @@ import ( "github.com/ethereum/go-ethereum/log" ) +const ( + host = "127.0.0.1" +) + type Config struct { Port uint64 } @@ -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, @@ -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 } diff --git a/supersim.go b/supersim.go index bed26b2a..c3db084a 100644 --- a/supersim.go +++ b/supersim.go @@ -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}, }, }, } diff --git a/supersim_test.go b/supersim_test.go index 2be05b72..c738548c 100644 --- a/supersim_test.go +++ b/supersim_test.go @@ -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() { From 0c5712a74fa363cb42350ff3890c5029ca8dc37f Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 10 Jul 2024 11:50:15 -0400 Subject: [PATCH 2/4] nit --- anvil/anvil.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil/anvil.go b/anvil/anvil.go index 0294d926..77fba880 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -60,7 +60,7 @@ func (a *Anvil) Start(ctx context.Context) error { } args := []string{ - //"--silent", + "--silent", "--host", host, "--chain-id", fmt.Sprintf("%d", a.cfg.ChainId), "--port", fmt.Sprintf("%d", a.cfg.Port), From d121ffe70a5a11fb044aa8cb2f1873bd0a7fe6c9 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 10 Jul 2024 11:54:17 -0400 Subject: [PATCH 3/4] remove silent mode --- anvil/anvil.go | 1 - 1 file changed, 1 deletion(-) diff --git a/anvil/anvil.go b/anvil/anvil.go index 77fba880..73a0bd9f 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -60,7 +60,6 @@ func (a *Anvil) Start(ctx context.Context) error { } args := []string{ - "--silent", "--host", host, "--chain-id", fmt.Sprintf("%d", a.cfg.ChainId), "--port", fmt.Sprintf("%d", a.cfg.Port), From 0ce9a925f7f7a198923f03958d7af3eef6f8e281 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 10 Jul 2024 11:56:40 -0400 Subject: [PATCH 4/4] typo --- anvil/anvil_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anvil/anvil_test.go b/anvil/anvil_test.go index 90e213cf..a19ef1ed 100644 --- a/anvil/anvil_test.go +++ b/anvil/anvil_test.go @@ -19,7 +19,7 @@ func TestAnvil(t *testing.T) { require.NoError(t, anvil.Start(context.Background())) - // port overriden on startup + // port overridden on startup require.NotEqual(t, cfg.Port, 0) client, err := rpc.Dial(anvil.Endpoint())