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 }