github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rpc/engine.go (about) 1 package rpc 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 "net/http" 9 "sync" 10 11 "github.com/rs/zerolog" 12 "google.golang.org/grpc/credentials" 13 14 "github.com/onflow/flow-go/access" 15 "github.com/onflow/flow-go/consensus/hotstuff/model" 16 "github.com/onflow/flow-go/engine/access/rest" 17 "github.com/onflow/flow-go/engine/access/rpc/backend" 18 "github.com/onflow/flow-go/engine/access/state_stream" 19 statestreambackend "github.com/onflow/flow-go/engine/access/state_stream/backend" 20 "github.com/onflow/flow-go/model/flow" 21 "github.com/onflow/flow-go/module" 22 "github.com/onflow/flow-go/module/component" 23 "github.com/onflow/flow-go/module/events" 24 "github.com/onflow/flow-go/module/grpcserver" 25 "github.com/onflow/flow-go/module/irrecoverable" 26 "github.com/onflow/flow-go/state/protocol" 27 ) 28 29 // Config defines the configurable options for the access node server 30 // A secure GRPC server here implies a server that presents a self-signed TLS certificate and a client that authenticates 31 // the server via a pre-shared public key 32 type Config struct { 33 UnsecureGRPCListenAddr string // the non-secure GRPC server address as ip:port 34 SecureGRPCListenAddr string // the secure GRPC server address as ip:port 35 TransportCredentials credentials.TransportCredentials // the secure GRPC credentials 36 HTTPListenAddr string // the HTTP web proxy address as ip:port 37 CollectionAddr string // the address of the upstream collection node 38 HistoricalAccessAddrs string // the list of all access nodes from previous spork 39 40 BackendConfig backend.Config // configurable options for creating Backend 41 RestConfig rest.Config // the REST server configuration 42 MaxMsgSize uint // GRPC max message size 43 CompressorName string // GRPC compressor name 44 } 45 46 // Engine exposes the server with a simplified version of the Access API. 47 // An unsecured GRPC server (default port 9000), a secure GRPC server (default port 9001) and an HTTP Web proxy (default 48 // port 8000) are brought up. 49 type Engine struct { 50 component.Component 51 52 finalizedHeaderCacheActor *events.FinalizationActor // consumes events to populate the finalized header cache 53 backendNotifierActor *events.FinalizationActor // consumes events to notify the backend of finalized heights 54 finalizedHeaderCache *events.FinalizedHeaderCache 55 56 log zerolog.Logger 57 restCollector module.RestMetrics 58 backend *backend.Backend // the gRPC service implementation 59 unsecureGrpcServer *grpcserver.GrpcServer // the unsecure gRPC server 60 secureGrpcServer *grpcserver.GrpcServer // the secure gRPC server 61 httpServer *http.Server 62 restServer *http.Server 63 config Config 64 chain flow.Chain 65 66 restHandler access.API 67 68 addrLock sync.RWMutex 69 restAPIAddress net.Addr 70 71 stateStreamBackend state_stream.API 72 stateStreamConfig statestreambackend.Config 73 } 74 type Option func(*RPCEngineBuilder) 75 76 // NewBuilder returns a new RPC engine builder. 77 func NewBuilder(log zerolog.Logger, 78 state protocol.State, 79 config Config, 80 chainID flow.ChainID, 81 accessMetrics module.AccessMetrics, 82 rpcMetricsEnabled bool, 83 me module.Local, 84 backend *backend.Backend, 85 restHandler access.API, 86 secureGrpcServer *grpcserver.GrpcServer, 87 unsecureGrpcServer *grpcserver.GrpcServer, 88 stateStreamBackend state_stream.API, 89 stateStreamConfig statestreambackend.Config, 90 ) (*RPCEngineBuilder, error) { 91 log = log.With().Str("engine", "rpc").Logger() 92 93 // wrap the unsecured server with an HTTP proxy server to serve HTTP clients 94 httpServer := newHTTPProxyServer(unsecureGrpcServer.Server) 95 96 finalizedCache, finalizedCacheWorker, err := events.NewFinalizedHeaderCache(state) 97 if err != nil { 98 return nil, fmt.Errorf("could not create header cache: %w", err) 99 } 100 101 eng := &Engine{ 102 finalizedHeaderCache: finalizedCache, 103 finalizedHeaderCacheActor: finalizedCache.FinalizationActor, 104 log: log, 105 backend: backend, 106 unsecureGrpcServer: unsecureGrpcServer, 107 secureGrpcServer: secureGrpcServer, 108 httpServer: httpServer, 109 config: config, 110 chain: chainID.Chain(), 111 restCollector: accessMetrics, 112 restHandler: restHandler, 113 stateStreamBackend: stateStreamBackend, 114 stateStreamConfig: stateStreamConfig, 115 } 116 backendNotifierActor, backendNotifierWorker := events.NewFinalizationActor(eng.processOnFinalizedBlock) 117 eng.backendNotifierActor = backendNotifierActor 118 119 eng.Component = component.NewComponentManagerBuilder(). 120 AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 121 ready() 122 <-secureGrpcServer.Done() 123 }). 124 AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 125 ready() 126 <-unsecureGrpcServer.Done() 127 }). 128 AddWorker(eng.serveGRPCWebProxyWorker). 129 AddWorker(eng.serveREST). 130 AddWorker(finalizedCacheWorker). 131 AddWorker(backendNotifierWorker). 132 AddWorker(eng.shutdownWorker). 133 Build() 134 135 builder := NewRPCEngineBuilder(eng, me, finalizedCache) 136 if rpcMetricsEnabled { 137 builder.WithMetrics() 138 } 139 140 return builder, nil 141 } 142 143 // shutdownWorker is a worker routine which shuts down all servers when the context is cancelled. 144 func (e *Engine) shutdownWorker(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 145 ready() 146 <-ctx.Done() 147 e.shutdown() 148 } 149 150 // shutdown sequentially shuts down all servers managed by this engine. 151 // Errors which occur while shutting down a server are logged and otherwise ignored. 152 func (e *Engine) shutdown() { 153 // use unbounded context, rely on shutdown logic to have timeout 154 ctx := context.Background() 155 156 err := e.httpServer.Shutdown(ctx) 157 if err != nil { 158 e.log.Error().Err(err).Msg("error stopping http server") 159 } 160 if e.restServer != nil { 161 err := e.restServer.Shutdown(ctx) 162 if err != nil { 163 e.log.Error().Err(err).Msg("error stopping http REST server") 164 } 165 } 166 } 167 168 // OnFinalizedBlock responds to block finalization events. 169 func (e *Engine) OnFinalizedBlock(block *model.Block) { 170 e.finalizedHeaderCacheActor.OnFinalizedBlock(block) 171 e.backendNotifierActor.OnFinalizedBlock(block) 172 } 173 174 // processOnFinalizedBlock is invoked by the FinalizationActor when a new block is finalized. 175 // It informs the backend of the newly finalized block. 176 // The input to this callback is treated as trusted. 177 // No errors expected during normal operations. 178 func (e *Engine) processOnFinalizedBlock(_ *model.Block) error { 179 finalizedHeader := e.finalizedHeaderCache.Get() 180 181 var err error 182 // NOTE: The BlockTracker is currently only used by the access node and not by the observer node. 183 if e.backend.BlockTracker != nil { 184 err = e.backend.BlockTracker.ProcessOnFinalizedBlock() 185 if err != nil { 186 return err 187 } 188 } 189 190 err = e.backend.ProcessFinalizedBlockHeight(finalizedHeader.Height) 191 if err != nil { 192 return fmt.Errorf("could not process finalized block height %d: %w", finalizedHeader.Height, err) 193 } 194 195 return nil 196 } 197 198 // RestApiAddress returns the listen address of the REST API server. 199 // Guaranteed to be non-nil after Engine.Ready is closed. 200 func (e *Engine) RestApiAddress() net.Addr { 201 e.addrLock.RLock() 202 defer e.addrLock.RUnlock() 203 return e.restAPIAddress 204 } 205 206 // serveGRPCWebProxyWorker is a worker routine which starts the gRPC web proxy server. 207 func (e *Engine) serveGRPCWebProxyWorker(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 208 log := e.log.With().Str("http_proxy_address", e.config.HTTPListenAddr).Logger() 209 log.Info().Msg("starting http proxy server on address") 210 211 l, err := net.Listen("tcp", e.config.HTTPListenAddr) 212 if err != nil { 213 e.log.Err(err).Msg("failed to start the grpc web proxy server") 214 ctx.Throw(err) 215 return 216 } 217 ready() 218 219 err = e.httpServer.Serve(l) // blocking call 220 if err != nil { 221 if errors.Is(err, http.ErrServerClosed) { 222 return 223 } 224 log.Err(err).Msg("fatal error in grpc web proxy server") 225 ctx.Throw(err) 226 } 227 } 228 229 // serveREST is a worker routine which starts the HTTP REST server. 230 // The ready callback is called after the server address is bound and set. 231 // Note: The original REST BaseContext is discarded, and the irrecoverable.SignalerContext is used for error handling. 232 func (e *Engine) serveREST(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 233 if e.config.RestConfig.ListenAddress == "" { 234 e.log.Debug().Msg("no REST API address specified - not starting the server") 235 ready() 236 return 237 } 238 239 e.log.Info().Str("rest_api_address", e.config.RestConfig.ListenAddress).Msg("starting REST server on address") 240 241 r, err := rest.NewServer(e.restHandler, e.config.RestConfig, e.log, e.chain, e.restCollector, e.stateStreamBackend, 242 e.stateStreamConfig) 243 if err != nil { 244 e.log.Err(err).Msg("failed to initialize the REST server") 245 ctx.Throw(err) 246 return 247 } 248 e.restServer = r 249 250 // Set up the irrecoverable.SignalerContext for error handling in the REST server. 251 e.restServer.BaseContext = func(_ net.Listener) context.Context { 252 return irrecoverable.WithSignalerContext(ctx, ctx) 253 } 254 255 l, err := net.Listen("tcp", e.config.RestConfig.ListenAddress) 256 if err != nil { 257 e.log.Err(err).Msg("failed to start the REST server") 258 ctx.Throw(err) 259 return 260 } 261 262 e.addrLock.Lock() 263 e.restAPIAddress = l.Addr() 264 e.addrLock.Unlock() 265 266 e.log.Debug().Str("rest_api_address", e.restAPIAddress.String()).Msg("listening on port") 267 ready() 268 269 err = e.restServer.Serve(l) // blocking call 270 if err != nil { 271 if errors.Is(err, http.ErrServerClosed) { 272 return 273 } 274 e.log.Err(err).Msg("fatal error in REST server") 275 ctx.Throw(err) 276 } 277 }