@@ -22,6 +22,7 @@ import (
22
22
"log/slog"
23
23
"math"
24
24
"net/http"
25
+ "net/url"
25
26
"slices"
26
27
"strings"
27
28
"time"
@@ -43,6 +44,7 @@ const (
43
44
cursorCacheSize = 20
44
45
45
46
maxAutoReconnectDelay = 60 * time .Second
47
+ defaultKupoTimeout = 30 * time .Second
46
48
)
47
49
48
50
type ChainSync struct {
@@ -507,41 +509,55 @@ func getKupoClient(c *ChainSync) (*kugo.Client, error) {
507
509
return c .kupoClient , nil
508
510
}
509
511
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
+
510
518
KugoCustomLogger := logging .NewKugoCustomLogger (logging .LevelInfo )
511
519
520
+ // Create client with timeout
512
521
k := kugo .New (
513
522
kugo .WithEndpoint (c .kupoUrl ),
514
523
kugo .WithLogger (KugoCustomLogger ),
524
+ kugo .WithTimeout (defaultKupoTimeout ),
515
525
)
516
526
517
- ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Second )
518
- defer cancel ()
527
+ httpClient := & http.Client {
528
+ Timeout : 2 * time .Second ,
529
+ }
519
530
520
531
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
+
521
537
req , err := http .NewRequestWithContext (ctx , http .MethodGet , healthUrl , nil )
522
538
if err != nil {
523
539
return nil , fmt .Errorf ("failed to create health check request: %w" , err )
524
540
}
525
541
526
- client := & http.Client {}
527
- resp , err := client .Do (req )
542
+ resp , err := httpClient .Do (req )
528
543
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
+ }
533
553
}
534
554
defer resp .Body .Close ()
535
555
536
556
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 )
541
558
}
542
559
543
560
c .kupoClient = k
544
-
545
561
return k , nil
546
562
}
547
563
@@ -564,10 +580,18 @@ func resolveTransactionInputs(
564
580
// Extract transaction ID and index from the input
565
581
txId := input .Id ().String ()
566
582
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 ,
568
589
kugo .TxOut (chainsync .NewTxID (txId , txIndex )),
569
590
)
570
591
if err != nil {
592
+ if errors .Is (err , context .DeadlineExceeded ) {
593
+ return nil , fmt .Errorf ("kupo matches query timed out after %v" , defaultKupoTimeout )
594
+ }
571
595
return nil , fmt .Errorf (
572
596
"error fetching matches for input TxId: %s, Index: %d. Error: %w" ,
573
597
txId ,
0 commit comments