From dad31ee8b1b4fa4a6ebefb994813753f8fee2160 Mon Sep 17 00:00:00 2001 From: James Kim Date: Thu, 27 Jun 2024 15:16:09 -0400 Subject: [PATCH 1/2] feat: startup logging --- anvil/anvil.go | 21 ++++++++++++--------- supersim.go | 38 ++++++++++++++++++++++++++++++++++++-- supersim_test.go | 11 ++++++++++- utils/rpc.go | 35 +++++++++++++++++++++++------------ 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/anvil/anvil.go b/anvil/anvil.go index dc357612..fb104174 100644 --- a/anvil/anvil.go +++ b/anvil/anvil.go @@ -8,9 +8,7 @@ import ( "os" "os/exec" "sync/atomic" - "time" - "github.com/ethereum-optimism/supersim/utils" "github.com/ethereum/go-ethereum/log" ) @@ -58,13 +56,12 @@ func (a *Anvil) Start(ctx context.Context) error { tempFile, err := os.CreateTemp("", "genesis-*.json") if err != nil { - return fmt.Errorf("Error creating temporary genesis file: %w", err) + return fmt.Errorf("error creating temporary genesis file: %w", err) } - defer os.Remove(tempFile.Name()) _, err = tempFile.Write(a.cfg.Genesis) if err != nil { - return fmt.Errorf("Error writing to genesis file: %w", err) + return fmt.Errorf("error writing to genesis file: %w", err) } // Prep args @@ -109,11 +106,9 @@ func (a *Anvil) Start(ctx context.Context) error { return fmt.Errorf("failed to start anvil: %w", err) } - if _, err := utils.WaitForAnvilClientToBeReady(fmt.Sprintf("http://%s:%d", host, a.cfg.Port), 5*time.Second); err != nil { - return fmt.Errorf("failed to start anvil: %w", err) - } - go func() { + defer os.Remove(tempFile.Name()) + if err := a.cmd.Wait(); err != nil { anvilLog.Error("anvil terminated with an error", "error", err) } else { @@ -141,3 +136,11 @@ func (a *Anvil) Stop() error { func (a *Anvil) Stopped() bool { return a.stopped.Load() } + +func (a *Anvil) Endpoint() string { + return fmt.Sprintf("http://%s:%d", host, a.cfg.Port) +} + +func (a *Anvil) ChainId() uint64 { + return a.cfg.ChainId +} diff --git a/supersim.go b/supersim.go index b816fa3f..e789210b 100644 --- a/supersim.go +++ b/supersim.go @@ -3,10 +3,14 @@ package supersim import ( _ "embed" "fmt" + "strings" + "sync" + "time" "context" "github.com/ethereum-optimism/supersim/anvil" + "github.com/ethereum-optimism/supersim/utils" "github.com/ethereum/go-ethereum/log" ) @@ -41,8 +45,8 @@ func NewSupersim(log log.Logger, config *Config) *Supersim { l1Chain := anvil.New(log, &config.l1Chain) l2Chains := make(map[uint64]*anvil.Anvil) - for _, l2Chain := range config.l2Chains { - l2Chains[l2Chain.ChainId] = anvil.New(log, &l2Chain) + for _, l2ChainConfig := range config.l2Chains { + l2Chains[l2ChainConfig.ChainId] = anvil.New(log, &l2ChainConfig) } return &Supersim{log, l1Chain, l2Chains} @@ -55,12 +59,27 @@ 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() + + s.log.Info("Supersim is ready") + s.log.Info(s.ConfigAsString()) + return nil } @@ -83,3 +102,18 @@ func (s *Supersim) Stop(_ context.Context) error { func (s *Supersim) Stopped() bool { return s.l1Chain.Stopped() } + +func (s *Supersim) ConfigAsString() string { + var b strings.Builder + + fmt.Fprintf(&b, "\nSupersim Config:\n") + fmt.Fprintf(&b, "L1:\n") + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", s.l1Chain.ChainId(), s.l1Chain.Endpoint()) + + fmt.Fprintf(&b, "L2:\n") + for _, l2Chain := range s.l2Chains { + fmt.Fprintf(&b, " Chain ID: %d RPC: %s\n", l2Chain.ChainId(), l2Chain.Endpoint()) + } + + return b.String() +} diff --git a/supersim_test.go b/supersim_test.go index b6d8172e..8cd5233a 100644 --- a/supersim_test.go +++ b/supersim_test.go @@ -10,6 +10,8 @@ import ( oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/supersim/utils" + + "github.com/ethereum/go-ethereum/rpc" ) const ( @@ -33,7 +35,14 @@ func TestGenesisState(t *testing.T) { }() for _, l2ChainConfig := range DefaultConfig.l2Chains { - client, err := utils.WaitForAnvilClientToBeReady(fmt.Sprintf("http://127.0.0.1:%d", l2ChainConfig.Port), anvilClientTimeout) + rpcUrl := fmt.Sprintf("http://127.0.0.1:%d", l2ChainConfig.Port) + client, clientCreateErr := rpc.Dial(rpcUrl) + + if clientCreateErr != nil { + 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) } diff --git a/utils/rpc.go b/utils/rpc.go index 30440ac2..746b5b12 100644 --- a/utils/rpc.go +++ b/utils/rpc.go @@ -3,13 +3,27 @@ package utils import ( "context" "fmt" - "net/http" + "strings" "time" "github.com/ethereum/go-ethereum/rpc" ) -func WaitForAnvilClientToBeReady(rpcUrl string, timeout time.Duration) (*rpc.Client, error) { +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() @@ -19,23 +33,20 @@ func WaitForAnvilClientToBeReady(rpcUrl string, timeout time.Duration) (*rpc.Cli for { select { case <-ctx.Done(): - return nil, fmt.Errorf("timed out waiting for response from %s", rpcUrl) + return fmt.Errorf("timed out waiting for response from client") case <-ticker.C: - res, err := http.Get(rpcUrl) + var result string + callErr := client.Call(&result, "web3_clientVersion") - if err != nil { - fmt.Printf("Error making request: %v\n", err) + if callErr != nil { continue } - defer res.Body.Close() - client, err := rpc.Dial(rpcUrl) - if err != nil { - fmt.Printf("Error creating rpc client: %v\n", err) - continue + if strings.HasPrefix(result, "anvil") { + return nil } - return client, nil + return fmt.Errorf("unexpected client version: %s", result) } } } From a167a843111ac08cfffaac73b2b9eb79c1228566 Mon Sep 17 00:00:00 2001 From: James Kim Date: Tue, 9 Jul 2024 10:55:25 -0400 Subject: [PATCH 2/2] refactor waitforanvil --- anvil/anvil.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ supersim.go | 52 ++++++++++++++++++++++++++++++++++------------- supersim_test.go | 13 ++++++------ utils/rpc.go | 52 ----------------------------------------------- 4 files changed, 97 insertions(+), 73 deletions(-) delete mode 100644 utils/rpc.go diff --git a/anvil/anvil.go b/anvil/anvil.go index fb104174..46f2bab7 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: %w", err) + } + + defer client.Close() + + if err := waitForAnvilClientToBeReady(ctx, client, timeout); err != nil { + return fmt.Errorf("failed to connect to RPC server: %w", 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 8cd5233a..defaa78e 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,7 +24,7 @@ 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 func() { err := supersim.Stop(context.Background()) @@ -34,6 +33,10 @@ func TestGenesisState(t *testing.T) { } }() + 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) @@ -42,14 +45,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) - } - } -}