diff --git a/anvil/anvil.go b/anvil/anvil.go index c9870325..0ec90dd4 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -7,7 +7,11 @@ import ( "fmt" "os" "os/exec" + "strings" "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/log" ) @@ -141,6 +145,55 @@ func (a *Anvil) Endpoint() string { return fmt.Sprintf("http://%s:%d", host, a.cfg.Port) } +func (a *Anvil) WaitUntilReady(ctx context.Context) error { + return waitForAnvilEndpointToBeReady(ctx, a.Endpoint(), 10*time.Second) +} + func (a *Anvil) ChainId() uint64 { return a.cfg.ChainId } + +func waitForAnvilEndpointToBeReady(ctx context.Context, endpoint string, timeout time.Duration) error { + client, err := rpc.Dial(endpoint) + if err != nil { + return fmt.Errorf("failed to create client: %v", err) + } + + defer client.Close() + + if err := waitForAnvilClientToBeReady(ctx, client, timeout); err != nil { + return fmt.Errorf("failed to connect to RPC server: %v", err) + } + + return nil +} + +func waitForAnvilClientToBeReady(ctx context.Context, client *rpc.Client, timeout time.Duration) error { + timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context cancelled") + case <-timeoutCtx.Done(): + return fmt.Errorf("timed out waiting for response from client") + case <-ticker.C: + var result string + callErr := client.Call(&result, "web3_clientVersion") + + if callErr != nil { + continue + } + + if strings.HasPrefix(result, "anvil") { + return nil + } + + return fmt.Errorf("unexpected client version: %s", result) + } + } +} diff --git a/supersim.go b/supersim.go index e789210b..ab5f6f0f 100644 --- a/supersim.go +++ b/supersim.go @@ -5,12 +5,10 @@ import ( "fmt" "strings" "sync" - "time" "context" "github.com/ethereum-optimism/supersim/anvil" - "github.com/ethereum-optimism/supersim/utils" "github.com/ethereum/go-ethereum/log" ) @@ -59,25 +57,17 @@ func (s *Supersim) Start(ctx context.Context) error { return fmt.Errorf("l1 chain failed to start: %w", err) } - var wg sync.WaitGroup - waitForAnvil := func(anvil *anvil.Anvil) { - defer wg.Done() - wg.Add(1) - utils.WaitForAnvilEndpointToBeReady(anvil.Endpoint(), 10*time.Second) - } - - go waitForAnvil(s.l1Chain) - for _, l2Chain := range s.l2Chains { if err := l2Chain.Start(ctx); err != nil { return fmt.Errorf("l2 chain failed to start: %w", err) } - go waitForAnvil(l2Chain) } - wg.Wait() + if err := s.WaitUntilReady(); err != nil { + return fmt.Errorf("supersim failed to get ready: %w", err) + } - s.log.Info("Supersim is ready") + s.log.Info("supersim is ready") s.log.Info(s.ConfigAsString()) return nil @@ -103,6 +93,40 @@ func (s *Supersim) Stopped() bool { return s.l1Chain.Stopped() } +func (s *Supersim) WaitUntilReady() error { + var once sync.Once + var err error + ctx, cancel := context.WithCancel(context.Background()) + + handleErr := func(e error) { + if e != nil { + once.Do(func() { + err = e + cancel() + }) + } + } + + var wg sync.WaitGroup + + waitForAnvil := func(anvil *anvil.Anvil) { + defer wg.Done() + handleErr(anvil.WaitUntilReady(ctx)) + } + + wg.Add(1) + go waitForAnvil(s.l1Chain) + + for _, l2Chain := range s.l2Chains { + wg.Add(1) + go waitForAnvil(l2Chain) + } + + wg.Wait() + + return err +} + func (s *Supersim) ConfigAsString() string { var b strings.Builder diff --git a/supersim_test.go b/supersim_test.go index ec5b4b83..1e3e4d1f 100644 --- a/supersim_test.go +++ b/supersim_test.go @@ -9,7 +9,6 @@ import ( "time" oplog "github.com/ethereum-optimism/optimism/op-service/log" - "github.com/ethereum-optimism/supersim/utils" "github.com/ethereum/go-ethereum/rpc" ) @@ -25,9 +24,13 @@ const ( func TestGenesisState(t *testing.T) { logger := oplog.NewLogger(os.Stderr, oplog.DefaultCLIConfig()) supersim := NewSupersim(logger, &DefaultConfig) - supersim.Start(context.Background()) + err := supersim.Start(context.Background()) defer supersim.Stop(context.Background()) + if err != nil { + t.Fatalf("Failed to start supersim: %v", err) + } + for _, l2ChainConfig := range DefaultConfig.l2Chains { rpcUrl := fmt.Sprintf("http://127.0.0.1:%d", l2ChainConfig.Port) client, clientCreateErr := rpc.Dial(rpcUrl) @@ -36,14 +39,10 @@ func TestGenesisState(t *testing.T) { t.Fatalf("Failed to create client: %v", clientCreateErr) } - err := utils.WaitForAnvilClientToBeReady(client, anvilClientTimeout) - if err != nil { - t.Fatalf("Failed to connect to RPC server: %v", err) - } defer client.Close() var code string - err = client.CallContext(context.Background(), &code, "eth_getCode", crossL2InboxAddress, "latest") + err := client.CallContext(context.Background(), &code, "eth_getCode", crossL2InboxAddress, "latest") if err != nil { log.Fatalf("Failed to get code: %v", err) } diff --git a/utils/rpc.go b/utils/rpc.go deleted file mode 100644 index 746b5b12..00000000 --- a/utils/rpc.go +++ /dev/null @@ -1,52 +0,0 @@ -package utils - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/ethereum/go-ethereum/rpc" -) - -func WaitForAnvilEndpointToBeReady(endpoint string, timeout time.Duration) error { - client, clientCreateErr := rpc.Dial(endpoint) - if clientCreateErr != nil { - return fmt.Errorf("failed to create client: %v", clientCreateErr) - } - - err := WaitForAnvilClientToBeReady(client, timeout) - if err != nil { - return fmt.Errorf("failed to connect to RPC server: %v", err) - } - - return nil -} - -func WaitForAnvilClientToBeReady(client *rpc.Client, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return fmt.Errorf("timed out waiting for response from client") - case <-ticker.C: - var result string - callErr := client.Call(&result, "web3_clientVersion") - - if callErr != nil { - continue - } - - if strings.HasPrefix(result, "anvil") { - return nil - } - - return fmt.Errorf("unexpected client version: %s", result) - } - } -}