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