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 }