88 "time"
99
1010 pebbleDB "github.com/cockroachdb/pebble"
11- "github.com/onflow/flow-evm-gateway/metrics"
1211 "github.com/onflow/flow-go-sdk/access"
1312 "github.com/onflow/flow-go-sdk/access/grpc"
1413 "github.com/onflow/flow-go/fvm/environment"
@@ -21,9 +20,12 @@ import (
2120 "github.com/rs/zerolog"
2221 "github.com/sethvargo/go-limiter/memorystore"
2322 grpcOpts "google.golang.org/grpc"
23+ "google.golang.org/grpc/codes"
24+ "google.golang.org/grpc/status"
2425
2526 "github.com/onflow/flow-evm-gateway/api"
2627 "github.com/onflow/flow-evm-gateway/config"
28+ "github.com/onflow/flow-evm-gateway/metrics"
2729 "github.com/onflow/flow-evm-gateway/models"
2830 errs "github.com/onflow/flow-evm-gateway/models/errors"
2931 "github.com/onflow/flow-evm-gateway/services/ingestion"
@@ -33,6 +35,19 @@ import (
3335 "github.com/onflow/flow-evm-gateway/storage/pebble"
3436)
3537
38+ const (
39+ // DefaultMaxMessageSize is the default maximum message size for gRPC responses
40+ DefaultMaxMessageSize = 1024 * 1024 * 1024
41+
42+ // DefaultResourceExhaustedRetryDelay is the default delay between retries when the server returns
43+ // a ResourceExhausted error.
44+ DefaultResourceExhaustedRetryDelay = 100 * time .Millisecond
45+
46+ // DefaultResourceExhaustedMaxRetryDelay is the default max request duration when retrying server
47+ // ResourceExhausted errors.
48+ DefaultResourceExhaustedMaxRetryDelay = 30 * time .Second
49+ )
50+
3651type Storages struct {
3752 Storage * pebble.Storage
3853 Registers * pebble.RegisterStorage
@@ -452,7 +467,13 @@ func setupCrossSporkClient(config config.Config, logger zerolog.Logger) (*reques
452467 // create access client with cross-spork capabilities
453468 currentSporkClient , err := grpc .NewClient (
454469 config .AccessNodeHost ,
455- grpc .WithGRPCDialOptions (grpcOpts .WithDefaultCallOptions (grpcOpts .MaxCallRecvMsgSize (1024 * 1024 * 1024 ))),
470+ grpc .WithGRPCDialOptions (
471+ grpcOpts .WithDefaultCallOptions (grpcOpts .MaxCallRecvMsgSize (DefaultMaxMessageSize )),
472+ grpcOpts .WithUnaryInterceptor (retryInterceptor (
473+ DefaultResourceExhaustedMaxRetryDelay ,
474+ DefaultResourceExhaustedRetryDelay ,
475+ )),
476+ ),
456477 )
457478 if err != nil {
458479 return nil , fmt .Errorf (
@@ -487,6 +508,44 @@ func setupCrossSporkClient(config config.Config, logger zerolog.Logger) (*reques
487508 return client , nil
488509}
489510
511+ // retryInterceptor is a gRPC client interceptor that retries the request when the server returns
512+ // a ResourceExhausted error
513+ func retryInterceptor (maxDuration , pauseDuration time.Duration ) grpcOpts.UnaryClientInterceptor {
514+ return func (
515+ ctx context.Context ,
516+ method string ,
517+ req , reply interface {},
518+ cc * grpcOpts.ClientConn ,
519+ invoker grpcOpts.UnaryInvoker ,
520+ opts ... grpcOpts.CallOption ,
521+ ) error {
522+ start := time .Now ()
523+ attempts := 0
524+ for {
525+ err := invoker (ctx , method , req , reply , cc , opts ... )
526+ if err == nil {
527+ return nil
528+ }
529+
530+ if status .Code (err ) != codes .ResourceExhausted {
531+ return err
532+ }
533+
534+ attempts ++
535+ duration := time .Since (start )
536+ if duration >= maxDuration {
537+ return fmt .Errorf ("request failed (attempts: %d, duration: %v): %w" , attempts , duration , err )
538+ }
539+
540+ select {
541+ case <- ctx .Done ():
542+ return ctx .Err ()
543+ case <- time .After (pauseDuration ):
544+ }
545+ }
546+ }
547+ }
548+
490549// setupStorage creates storage and initializes it with configured starting cadence height
491550// in case such a height doesn't already exist in the database.
492551func setupStorage (
@@ -570,9 +629,9 @@ func setupStorage(
570629 Stringer ("fvm_address_for_evm_storage_account" , storageAddress ).
571630 Msgf ("database initialized with cadence height: %d" , cadenceHeight )
572631 }
573- //else {
632+ // else {
574633 // // TODO(JanezP): verify storage account owner is correct
575- //}
634+ // }
576635
577636 return db , & Storages {
578637 Storage : store ,
0 commit comments