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  }