github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/collection/rpc/engine.go (about)

     1  // Package rpc implements accepting transactions into the system.
     2  // It implements a subset of the Observation API.
     3  package rpc
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"net"
     9  
    10  	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
    11  	"github.com/onflow/flow/protobuf/go/flow/access"
    12  	"github.com/rs/zerolog"
    13  	"google.golang.org/grpc"
    14  	"google.golang.org/grpc/codes"
    15  	_ "google.golang.org/grpc/encoding/gzip" //required for gRPC compression
    16  	"google.golang.org/grpc/status"
    17  
    18  	_ "github.com/onflow/flow-go/engine/common/grpc/compressor/deflate" // required for gRPC compression
    19  	_ "github.com/onflow/flow-go/engine/common/grpc/compressor/snappy"  // required for gRPC compression
    20  
    21  	"github.com/onflow/flow-go/engine"
    22  	"github.com/onflow/flow-go/engine/common/rpc"
    23  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    24  	"github.com/onflow/flow-go/model/flow"
    25  )
    26  
    27  // Backend defines the core functionality required by the RPC API.
    28  type Backend interface {
    29  	// ProcessTransaction handles validating and ingesting a new transaction,
    30  	// ultimately for inclusion in a future collection.
    31  	ProcessTransaction(*flow.TransactionBody) error
    32  }
    33  
    34  // Config defines the configurable options for the ingress server.
    35  type Config struct {
    36  	ListenAddr        string
    37  	MaxMsgSize        uint // in bytes
    38  	RpcMetricsEnabled bool // enable GRPC metrics
    39  }
    40  
    41  // Engine implements a gRPC server with a simplified version of the Observation
    42  // API to enable receiving transactions into the system.
    43  type Engine struct {
    44  	unit    *engine.Unit
    45  	log     zerolog.Logger
    46  	handler *handler     // the gRPC service implementation
    47  	server  *grpc.Server // the gRPC server
    48  	config  Config
    49  }
    50  
    51  // New returns a new ingress server.
    52  func New(
    53  	config Config,
    54  	backend Backend,
    55  	log zerolog.Logger,
    56  	chainID flow.ChainID,
    57  	apiRatelimits map[string]int, // the api rate limit (max calls per second) for each of the gRPC API e.g. Ping->100, ExecuteScriptAtBlockID->300
    58  	apiBurstLimits map[string]int, // the api burst limit (max calls at the same time) for each of the gRPC API e.g. Ping->50, ExecuteScriptAtBlockID->10
    59  ) *Engine {
    60  	// create a GRPC server to serve GRPC clients
    61  	grpcOpts := []grpc.ServerOption{
    62  		grpc.MaxRecvMsgSize(int(config.MaxMsgSize)),
    63  		grpc.MaxSendMsgSize(int(config.MaxMsgSize)),
    64  	}
    65  
    66  	var interceptors []grpc.UnaryServerInterceptor // ordered list of interceptors
    67  	// if rpc metrics is enabled, add the grpc metrics interceptor as a server option
    68  	if config.RpcMetricsEnabled {
    69  		interceptors = append(interceptors, grpc_prometheus.UnaryServerInterceptor)
    70  	}
    71  
    72  	if len(apiRatelimits) > 0 {
    73  		// create a rate limit interceptor
    74  		rateLimitInterceptor := rpc.NewRateLimiterInterceptor(log, apiRatelimits, apiBurstLimits).UnaryServerInterceptor
    75  		// append the rate limit interceptor to the list of interceptors
    76  		interceptors = append(interceptors, rateLimitInterceptor)
    77  	}
    78  
    79  	// create a chained unary interceptor
    80  	chainedInterceptors := grpc.ChainUnaryInterceptor(interceptors...)
    81  	grpcOpts = append(grpcOpts, chainedInterceptors)
    82  
    83  	server := grpc.NewServer(grpcOpts...)
    84  
    85  	e := &Engine{
    86  		unit: engine.NewUnit(),
    87  		log:  log.With().Str("engine", "collection_rpc").Logger(),
    88  		handler: &handler{
    89  			UnimplementedAccessAPIServer: access.UnimplementedAccessAPIServer{},
    90  			backend:                      backend,
    91  			chainID:                      chainID,
    92  		},
    93  		server: server,
    94  		config: config,
    95  	}
    96  
    97  	if config.RpcMetricsEnabled {
    98  		grpc_prometheus.EnableHandlingTimeHistogram()
    99  		grpc_prometheus.Register(server)
   100  	}
   101  
   102  	access.RegisterAccessAPIServer(e.server, e.handler)
   103  
   104  	return e
   105  }
   106  
   107  // Ready returns a ready channel that is closed once the module has fully
   108  // started. The ingress module is ready when the gRPC server has successfully
   109  // started.
   110  func (e *Engine) Ready() <-chan struct{} {
   111  	e.unit.Launch(e.serve)
   112  	return e.unit.Ready()
   113  }
   114  
   115  // Done returns a done channel that is closed once the module has fully stopped.
   116  // It sends a signal to stop the gRPC server, then closes the channel.
   117  func (e *Engine) Done() <-chan struct{} {
   118  	return e.unit.Done(e.server.GracefulStop)
   119  }
   120  
   121  // serve starts the gRPC server .
   122  //
   123  // When this function returns, the server is considered ready.
   124  func (e *Engine) serve() {
   125  
   126  	e.log.Info().Msgf("starting server on address %s", e.config.ListenAddr)
   127  
   128  	l, err := net.Listen("tcp", e.config.ListenAddr)
   129  	if err != nil {
   130  		e.log.Fatal().Err(err).Msg("failed to start server")
   131  		return
   132  	}
   133  
   134  	err = e.server.Serve(l)
   135  	if err != nil {
   136  		e.log.Error().Err(err).Msg("fatal error in server")
   137  	}
   138  }
   139  
   140  // handler implements a subset of the Observation API.
   141  type handler struct {
   142  	access.UnimplementedAccessAPIServer
   143  	backend Backend
   144  	chainID flow.ChainID
   145  }
   146  
   147  // Ping responds to requests when the server is up.
   148  func (h *handler) Ping(_ context.Context, _ *access.PingRequest) (*access.PingResponse, error) {
   149  	return &access.PingResponse{}, nil
   150  }
   151  
   152  // SendTransaction accepts new transactions and inputs them to the ingress
   153  // engine for validation and routing.
   154  func (h *handler) SendTransaction(_ context.Context, req *access.SendTransactionRequest) (*access.SendTransactionResponse, error) {
   155  	tx, err := convert.MessageToTransaction(req.Transaction, h.chainID.Chain())
   156  	if err != nil {
   157  		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to convert transaction: %v", err))
   158  	}
   159  
   160  	err = h.backend.ProcessTransaction(&tx)
   161  	if engine.IsInvalidInputError(err) {
   162  		return nil, status.Error(codes.InvalidArgument, err.Error())
   163  	}
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	txID := tx.ID()
   169  
   170  	return &access.SendTransactionResponse{Id: txID[:]}, nil
   171  }