88 "fmt"
99 "log/slog"
1010 "math"
11+ "net/http"
1112 "net/url"
13+ "strconv"
1214 "strings"
1315 "time"
1416
@@ -17,6 +19,34 @@ import (
1719 "github.com/hashicorp/go-retryablehttp"
1820)
1921
22+ // parseRetryAfterHeader parses the Retry-After header and returns the
23+ // delay duration according to the spec: https://httpwg.org/specs/rfc7231.html#header.retry-after
24+ // The bool returned will be true if the header was successfully parsed.
25+ // Otherwise, the header was either not present, or was not parseable according to the spec.
26+ func parseRetryAfterHeader (headers []string ) (time.Duration , bool ) {
27+ if len (headers ) == 0 || headers [0 ] == "" {
28+ return 0 , false
29+ }
30+ header := headers [0 ]
31+ // 'Retry-After' is provided in seconds.
32+ if sleep , err := strconv .ParseInt (header , 10 , 64 ); err == nil {
33+ if sleep < 0 { // a negative sleep doesn't make sense
34+ return 0 , false
35+ }
36+ return time .Second * time .Duration (sleep ), true
37+ }
38+
39+ // 'Retry-After' is provided as a date.
40+ retryTime , err := time .Parse (time .RFC1123 , header )
41+ if err != nil {
42+ return 0 , false
43+ }
44+ if duration := retryTime .Sub (time .Now ()); duration > 0 {
45+ return duration , true
46+ }
47+ return 0 , true // past date
48+ }
49+
2050// RetryConfig holds configuration for the retryable HTTP client.
2151type RetryConfig struct {
2252 MaxRetries uint64
@@ -39,6 +69,25 @@ func NewEthClient(
3969 rclient .RetryMax = int (min (retryConfig .MaxRetries , uint64 (math .MaxInt )))
4070 rclient .RetryWaitMin = retryConfig .RetryMinWait
4171 rclient .RetryWaitMax = retryConfig .RetryMaxWait
72+ // Supply a custom Backoff that takes 'min(serverRetryAfter, RetryMaxWait)'.
73+ // The provider keeps its ability to ask for politeness; it loses the ability
74+ // to dictate the node's clock.
75+ rclient .Backoff = func (minDuration , maxDuration time.Duration , attemptNum int , resp * http.Response ) time.Duration {
76+ if resp != nil {
77+ if resp .StatusCode == http .StatusTooManyRequests || resp .StatusCode == http .StatusServiceUnavailable {
78+ if sleep , ok := parseRetryAfterHeader (resp .Header ["Retry-After" ]); ok {
79+ return min (maxDuration , max (sleep , 0 ))
80+ }
81+ }
82+ }
83+
84+ mult := math .Pow (2 , float64 (attemptNum )) * float64 (minDuration )
85+ sleep := time .Duration (mult )
86+ if float64 (sleep ) != mult || sleep > maxDuration {
87+ sleep = maxDuration
88+ }
89+ return sleep
90+ }
4291
4392 opts := []rpc.ClientOption {
4493 rpc .WithHTTPClient (rclient .StandardClient ()),
0 commit comments