@@ -22,6 +22,7 @@ import (
2222 "log/slog"
2323 "math"
2424 "net/http"
25+ "net/url"
2526 "slices"
2627 "strings"
2728 "time"
@@ -43,6 +44,7 @@ const (
4344 cursorCacheSize = 20
4445
4546 maxAutoReconnectDelay = 60 * time .Second
47+ defaultKupoTimeout = 30 * time .Second
4648)
4749
4850type ChainSync struct {
@@ -507,41 +509,55 @@ func getKupoClient(c *ChainSync) (*kugo.Client, error) {
507509 return c .kupoClient , nil
508510 }
509511
512+ // Validate URL first
513+ _ , err := url .ParseRequestURI (c .kupoUrl )
514+ if err != nil {
515+ return nil , fmt .Errorf ("invalid kupo URL: %w" , err )
516+ }
517+
510518 KugoCustomLogger := logging .NewKugoCustomLogger (logging .LevelInfo )
511519
520+ // Create client with timeout
512521 k := kugo .New (
513522 kugo .WithEndpoint (c .kupoUrl ),
514523 kugo .WithLogger (KugoCustomLogger ),
524+ kugo .WithTimeout (defaultKupoTimeout ),
515525 )
516526
517- ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Second )
518- defer cancel ()
527+ httpClient := & http.Client {
528+ Timeout : 2 * time .Second ,
529+ }
519530
520531 healthUrl := strings .TrimRight (c .kupoUrl , "/" ) + "/health"
532+
533+ // Create context with timeout
534+ ctx , cancel := context .WithTimeout (context .Background (), 3 * time .Second )
535+ defer cancel ()
536+
521537 req , err := http .NewRequestWithContext (ctx , http .MethodGet , healthUrl , nil )
522538 if err != nil {
523539 return nil , fmt .Errorf ("failed to create health check request: %w" , err )
524540 }
525541
526- client := & http.Client {}
527- resp , err := client .Do (req )
542+ resp , err := httpClient .Do (req )
528543 if err != nil {
529- return nil , fmt .Errorf ("failed to perform health check: %w" , err )
530- }
531- if resp == nil {
532- return nil , errors .New ("health check response empty, aborting" )
544+ // Handle different error types
545+ switch {
546+ case errors .Is (err , context .DeadlineExceeded ):
547+ return nil , errors .New ("kupo health check timed out after 3 seconds" )
548+ case strings .Contains (err .Error (), "no such host" ):
549+ return nil , fmt .Errorf ("failed to resolve kupo host: %w" , err )
550+ default :
551+ return nil , fmt .Errorf ("failed to perform health check: %w" , err )
552+ }
533553 }
534554 defer resp .Body .Close ()
535555
536556 if resp .StatusCode != http .StatusOK {
537- return nil , fmt .Errorf (
538- "health check failed with status code: %d" ,
539- resp .StatusCode ,
540- )
557+ return nil , fmt .Errorf ("health check failed with status code: %d" , resp .StatusCode )
541558 }
542559
543560 c .kupoClient = k
544-
545561 return k , nil
546562}
547563
@@ -564,10 +580,18 @@ func resolveTransactionInputs(
564580 // Extract transaction ID and index from the input
565581 txId := input .Id ().String ()
566582 txIndex := int (input .Index ())
567- matches , err := k .Matches (context .Background (),
583+
584+ // Add timeout for matches query
585+ ctx , cancel := context .WithTimeout (context .Background (), defaultKupoTimeout )
586+ defer cancel ()
587+
588+ matches , err := k .Matches (ctx ,
568589 kugo .TxOut (chainsync .NewTxID (txId , txIndex )),
569590 )
570591 if err != nil {
592+ if errors .Is (err , context .DeadlineExceeded ) {
593+ return nil , fmt .Errorf ("kupo matches query timed out after %v" , defaultKupoTimeout )
594+ }
571595 return nil , fmt .Errorf (
572596 "error fetching matches for input TxId: %s, Index: %d. Error: %w" ,
573597 txId ,
0 commit comments