@@ -207,7 +207,9 @@ import (
207207 "errors"
208208 "fmt"
209209 "math/big"
210+ "net/http"
210211 "strconv"
212+ "strings"
211213 "sync"
212214 "testing"
213215 "time"
@@ -398,7 +400,7 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
398400 },
399401 }, p .config .ClientOpts ... )
400402 if err != nil {
401- return nil , err
403+ return nil , fmt . Errorf ( "failed to create multiclient for Anvil at %s: %w" , httpURL , err )
402404 }
403405
404406 // Get the Chain ID as big.Int for transactor generation
@@ -591,7 +593,18 @@ func (p *CTFAnvilChainProvider) startContainer(ctx context.Context, chainID stri
591593 testcontainers .CleanupContainer (p .config .T , output .Container )
592594 }
593595
594- return output .Nodes [0 ].ExternalHTTPUrl , nil
596+ // Validate that the ExternalHTTPUrl is not empty
597+ externalURL := output .Nodes [0 ].ExternalHTTPUrl
598+ if externalURL == "" {
599+ return "" , errors .New ("container started but ExternalHTTPUrl is empty" )
600+ }
601+
602+ // Perform health check to ensure Anvil is ready
603+ if healthErr := p .waitForAnvilReady (ctx , externalURL ); healthErr != nil {
604+ return "" , fmt .Errorf ("anvil container started but health check failed: %w" , healthErr )
605+ }
606+
607+ return externalURL , nil
595608 },
596609 retry .Context (ctx ),
597610 retry .Attempts (attempts ),
@@ -659,3 +672,44 @@ func (p *CTFAnvilChainProvider) getUserTransactors(chainID string) ([]*bind.Tran
659672
660673 return transactors , nil
661674}
675+
676+ // waitForAnvilReady performs a health check on the Anvil node to ensure it's ready to accept requests.
677+ // It sends a simple JSON-RPC request to check if the node is responding correctly.
678+ func (p * CTFAnvilChainProvider ) waitForAnvilReady (ctx context.Context , httpURL string ) error {
679+ const (
680+ maxAttempts = 30
681+ retryDelay = 1 * time .Second
682+ )
683+
684+ // Simple JSON-RPC request to check if Anvil is ready
685+ jsonRPCRequest := `{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}`
686+
687+ return retry .Do (func () error {
688+ reqCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
689+ defer cancel ()
690+
691+ req , err := http .NewRequestWithContext (reqCtx , http .MethodPost , httpURL , strings .NewReader (jsonRPCRequest ))
692+ if err != nil {
693+ return fmt .Errorf ("failed to create request: %w" , err )
694+ }
695+ req .Header .Set ("Content-Type" , "application/json" )
696+
697+ client := & http.Client {Timeout : 5 * time .Second }
698+ resp , err := client .Do (req )
699+ if err != nil {
700+ return fmt .Errorf ("failed to make request: %w" , err )
701+ }
702+ defer resp .Body .Close ()
703+
704+ if resp .StatusCode != http .StatusOK {
705+ return fmt .Errorf ("received non-200 status code: %d" , resp .StatusCode )
706+ }
707+
708+ return nil
709+ },
710+ retry .Context (ctx ),
711+ retry .Attempts (maxAttempts ),
712+ retry .Delay (retryDelay ),
713+ retry .DelayType (retry .FixedDelay ),
714+ )
715+ }
0 commit comments