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  }