github.com/ledgerwatch/erigon-lib@v1.0.0/gointerfaces/grpcutil/utils.go (about) 1 package grpcutil 2 3 import ( 4 "context" 5 "crypto/tls" 6 "crypto/x509" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "strings" 12 "time" 13 14 "github.com/c2h5oh/datasize" 15 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 16 grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 17 "google.golang.org/grpc" 18 "google.golang.org/grpc/backoff" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/credentials" 21 "google.golang.org/grpc/keepalive" 22 "google.golang.org/grpc/reflection" 23 "google.golang.org/grpc/status" 24 ) 25 26 func TLS(tlsCACert, tlsCertFile, tlsKeyFile string) (credentials.TransportCredentials, error) { 27 // load peer cert/key, ca cert 28 if tlsCACert == "" { 29 if tlsCertFile == "" && tlsKeyFile == "" { 30 return nil, nil 31 } 32 return credentials.NewServerTLSFromFile(tlsCertFile, tlsKeyFile) 33 } 34 var caCert []byte 35 peerCert, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) 36 if err != nil { 37 return nil, fmt.Errorf("load peer cert/key error:%w", err) 38 } 39 caCert, err = os.ReadFile(tlsCACert) 40 if err != nil { 41 return nil, fmt.Errorf("read ca cert file error:%w", err) 42 } 43 caCertPool := x509.NewCertPool() 44 caCertPool.AppendCertsFromPEM(caCert) 45 return credentials.NewTLS(&tls.Config{ 46 Certificates: []tls.Certificate{peerCert}, 47 ClientCAs: caCertPool, 48 ClientAuth: tls.RequireAndVerifyClientCert, 49 MinVersion: tls.VersionTLS12, 50 //nolint:gosec 51 InsecureSkipVerify: true, // This is to make it work when Common Name does not match - remove when procedure is updated for common name 52 }), nil 53 } 54 55 func NewServer(rateLimit uint32, creds credentials.TransportCredentials) *grpc.Server { 56 var ( 57 streamInterceptors []grpc.StreamServerInterceptor 58 unaryInterceptors []grpc.UnaryServerInterceptor 59 ) 60 streamInterceptors = append(streamInterceptors, grpc_recovery.StreamServerInterceptor()) 61 unaryInterceptors = append(unaryInterceptors, grpc_recovery.UnaryServerInterceptor()) 62 63 //if metrics.Enabled { 64 // streamInterceptors = append(streamInterceptors, grpc_prometheus.StreamServerInterceptor) 65 // unaryInterceptors = append(unaryInterceptors, grpc_prometheus.UnaryServerInterceptor) 66 //} 67 68 //cpus := uint32(runtime.GOMAXPROCS(-1)) 69 opts := []grpc.ServerOption{ 70 //grpc.NumStreamWorkers(cpus), // reduce amount of goroutines 71 grpc.MaxConcurrentStreams(rateLimit), // to force clients reduce concurrency level 72 // Don't drop the connection, settings accordign to this comment on GitHub 73 // https://github.com/grpc/grpc-go/issues/3171#issuecomment-552796779 74 grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ 75 MinTime: 10 * time.Second, 76 PermitWithoutStream: true, 77 }), 78 grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)), 79 grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)), 80 grpc.Creds(creds), 81 } 82 grpcServer := grpc.NewServer(opts...) 83 reflection.Register(grpcServer) 84 85 //if metrics.Enabled { 86 // grpc_prometheus.Register(grpcServer) 87 //} 88 89 return grpcServer 90 } 91 92 func Connect(creds credentials.TransportCredentials, dialAddress string) (*grpc.ClientConn, error) { 93 var dialOpts []grpc.DialOption 94 95 backoffCfg := backoff.DefaultConfig 96 backoffCfg.BaseDelay = 500 * time.Millisecond 97 backoffCfg.MaxDelay = 10 * time.Second 98 dialOpts = []grpc.DialOption{ 99 grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoffCfg, MinConnectTimeout: 10 * time.Minute}), 100 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(200 * datasize.MB))), 101 grpc.WithKeepaliveParams(keepalive.ClientParameters{}), 102 } 103 if creds == nil { 104 dialOpts = append(dialOpts, grpc.WithInsecure()) 105 } else { 106 dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds)) 107 } 108 109 //if opts.inMemConn != nil { 110 // dialOpts = append(dialOpts, grpc.WithContextDialer(func(ctx context.Context, url string) (net.Conn, error) { 111 // return opts.inMemConn.Dial() 112 // })) 113 //} 114 115 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 116 defer cancel() 117 118 return grpc.DialContext(ctx, dialAddress, dialOpts...) 119 } 120 121 func IsRetryLater(err error) bool { 122 if s, ok := status.FromError(err); ok { 123 code := s.Code() 124 return code == codes.Unavailable || code == codes.Canceled || code == codes.ResourceExhausted 125 } 126 return false 127 } 128 129 func IsEndOfStream(err error) bool { 130 if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) { 131 return true 132 } 133 if s, ok := status.FromError(err); ok { 134 return s.Code() == codes.Canceled || strings.Contains(s.Message(), context.Canceled.Error()) 135 } 136 return false 137 } 138 139 // ErrIs - like `errors.Is` but for grpc errors 140 func ErrIs(err, target error) bool { 141 if errors.Is(err, target) { // direct clients do return Go-style errors 142 return true 143 } 144 if s, ok := status.FromError(err); ok { // remote clients do return GRPC-style errors 145 return strings.Contains(s.Message(), target.Error()) 146 } 147 return false 148 }