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 }