code.vegaprotocol.io/vega@v0.79.0/blockexplorer/blockexplorer.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package blockexplorer
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  
    22  	"code.vegaprotocol.io/vega/blockexplorer/api"
    23  	ourGrpc "code.vegaprotocol.io/vega/blockexplorer/api/grpc"
    24  	"code.vegaprotocol.io/vega/blockexplorer/config"
    25  	"code.vegaprotocol.io/vega/blockexplorer/store"
    26  	"code.vegaprotocol.io/vega/libs/net/pipe"
    27  	"code.vegaprotocol.io/vega/logging"
    28  	pb "code.vegaprotocol.io/vega/protos/blockexplorer/api/v1"
    29  
    30  	"golang.org/x/sync/errgroup"
    31  )
    32  
    33  type BlockExplorer struct {
    34  	config                  config.Config
    35  	log                     *logging.Logger
    36  	store                   *store.Store
    37  	blockExplorerGrpcServer pb.BlockExplorerServiceServer
    38  	internalGRPCServer      *ourGrpc.Server
    39  	externalGRPCServer      *ourGrpc.Server
    40  	grpcPipeConn            *pipe.Pipe
    41  	grpcUI                  *api.GRPCUIHandler
    42  	restAPI                 *api.RESTHandler
    43  	portal                  *api.Portal
    44  	gateway                 *api.Gateway
    45  }
    46  
    47  func NewFromConfig(config config.Config) *BlockExplorer {
    48  	a := &BlockExplorer{}
    49  	a.config = config
    50  	a.log = logging.NewLoggerFromConfig(config.Logging)
    51  	a.store = store.MustNewStore(config.Store, a.log)
    52  
    53  	// grpc-ui; a web front end that talks to the grpc api through a 'pipe' (fake connection)
    54  	a.grpcPipeConn = pipe.NewPipe("grpc-pipe")
    55  	a.grpcUI = api.NewGRPCUIHandler(a.log, a.grpcPipeConn, config.API.GRPCUI)
    56  
    57  	// a REST api that proxies to the GRPC api, generated by grpc-rest
    58  	// a.restApiConn = pipe.NewPipe("rest-api")
    59  	a.restAPI = api.NewRESTHandler(a.log, a.grpcPipeConn, config.API.REST)
    60  
    61  	// However GRPC is special, because it uses HTTP2 and really wants to be in control
    62  	// of its own connection. Fortunately there's a tool called cMux which creates dummy listeners
    63  	// and peeks at the stream to decide where to send it. If it's http/2 - send to grpc server
    64  	// otherwise dispatch to the gateway, which then sends it to which ever handler has registered
    65  	a.portal = api.NewPortal(config.API, a.log)
    66  
    67  	// The gateway collects all the HTTP handlers into a big 'serveMux'
    68  	a.gateway = api.NewGateway(a.log, config.API.Gateway, a.portal.GatewayListener())
    69  	a.gateway.Register(a.grpcUI, config.API.GRPCUI.Endpoint)
    70  	a.gateway.Register(a.restAPI, config.API.REST.Endpoint)
    71  
    72  	// main grpc api
    73  	a.blockExplorerGrpcServer = ourGrpc.NewBlockExplorerAPI(a.store, config.API.GRPC, a.log)
    74  	a.internalGRPCServer = ourGrpc.NewServer(config.API.GRPC, a.log, a.blockExplorerGrpcServer, a.grpcPipeConn)
    75  	a.externalGRPCServer = ourGrpc.NewServer(config.API.GRPC, a.log, a.blockExplorerGrpcServer, a.portal.GRPCListener())
    76  
    77  	return a
    78  }
    79  
    80  func (a *BlockExplorer) Run(ctx context.Context) error {
    81  	ctx, cancel := context.WithCancel(ctx)
    82  	defer cancel()
    83  	g, ctx := errgroup.WithContext(ctx)
    84  
    85  	// Two grpc services; one for internal connections using a fast pipe, and another for external
    86  	g.Go(func() error {
    87  		return a.internalGRPCServer.Serve()
    88  	})
    89  	g.Go(func() error {
    90  		return a.externalGRPCServer.Serve()
    91  	})
    92  
    93  	// Then start our gateway and portal servers
    94  	g.Go(func() error {
    95  		return a.gateway.Serve()
    96  	})
    97  	g.Go(func() error {
    98  		return a.portal.Serve()
    99  	})
   100  
   101  	g.Go(func() error { return a.store.Migrate(ctx) })
   102  
   103  	// Now we can do all the http 'handlers' that talk to the gateway
   104  	if err := a.grpcUI.Start(ctx); err != nil {
   105  		return fmt.Errorf("could not start grpc-ui: %w", err)
   106  	}
   107  
   108  	if err := a.restAPI.Start(ctx); err != nil {
   109  		return fmt.Errorf("could not start REST<>GRPC proxy: %w", err)
   110  	}
   111  
   112  	// If one of the errgroup.Go func return an error, or the parent context
   113  	// get cancelled, then we initiate the shutdown.
   114  	cleaningDone := make(chan any)
   115  	go func() {
   116  		<-ctx.Done()
   117  		a.stop()
   118  		close(cleaningDone)
   119  	}()
   120  
   121  	err := g.Wait()
   122  
   123  	// Ensure goroutine shutting down the block explorer is triggered to avoid
   124  	// a dead-lock.
   125  	cancel()
   126  	<-cleaningDone
   127  
   128  	return err
   129  }
   130  
   131  func (a *BlockExplorer) stop() {
   132  	a.log.Info("Shutting down block explorer")
   133  	a.externalGRPCServer.Stop()
   134  	a.internalGRPCServer.Stop()
   135  
   136  	a.gateway.Stop()
   137  	a.portal.Stop()
   138  
   139  	a.restAPI.Stop()
   140  	a.grpcUI.Stop()
   141  
   142  	a.store.Close()
   143  	a.log.Info("Resources released")
   144  }