code.vegaprotocol.io/vega@v0.79.0/core/api/grpc.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 api
    17  
    18  import (
    19  	"context"
    20  	"net"
    21  	"strconv"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/stats"
    26  	"code.vegaprotocol.io/vega/core/vegatime"
    27  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    28  	"code.vegaprotocol.io/vega/libs/subscribers"
    29  	"code.vegaprotocol.io/vega/logging"
    30  	protoapi "code.vegaprotocol.io/vega/protos/vega/api/v1"
    31  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    32  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    33  
    34  	tmctypes "github.com/cometbft/cometbft/rpc/core/types"
    35  	"google.golang.org/grpc"
    36  	"google.golang.org/grpc/metadata"
    37  	"google.golang.org/grpc/peer"
    38  )
    39  
    40  //go:generate go run github.com/golang/mock/mockgen -destination mocks/event_service_mock.go -package mocks code.vegaprotocol.io/vega/core/api EventService
    41  type EventService interface {
    42  	ObserveEvents(ctx context.Context, retries int, eTypes []events.Type, batchSize int, filters ...subscribers.EventFilter) (<-chan []*eventspb.BusEvent, chan<- int)
    43  }
    44  
    45  // TimeService ...
    46  //
    47  //go:generate go run github.com/golang/mock/mockgen -destination mocks/time_service_mock.go -package mocks code.vegaprotocol.io/vega/core/api TimeService
    48  type TimeService interface {
    49  	GetTimeNow() time.Time
    50  }
    51  
    52  // EvtForwarder
    53  //
    54  //go:generate go run github.com/golang/mock/mockgen -destination mocks/evt_forwarder_mock.go -package mocks code.vegaprotocol.io/vega/core/api  EvtForwarder
    55  type EvtForwarder interface {
    56  	Forward(ctx context.Context, e *commandspb.ChainEvent, pk string) error
    57  }
    58  
    59  // Blockchain ...
    60  //
    61  //nolint:interfacebloat
    62  //go:generate go run github.com/golang/mock/mockgen -destination mocks/blockchain_mock.go -package mocks code.vegaprotocol.io/vega/core/api  Blockchain
    63  type Blockchain interface {
    64  	SubmitTransactionSync(ctx context.Context, tx *commandspb.Transaction) (*tmctypes.ResultBroadcastTx, error)
    65  	SubmitTransactionAsync(ctx context.Context, tx *commandspb.Transaction) (*tmctypes.ResultBroadcastTx, error)
    66  	SubmitTransactionCommit(ctx context.Context, tx *commandspb.Transaction) (*tmctypes.ResultBroadcastTxCommit, error)
    67  	SubmitRawTransactionSync(ctx context.Context, tx []byte) (*tmctypes.ResultBroadcastTx, error)
    68  	SubmitRawTransactionAsync(ctx context.Context, tx []byte) (*tmctypes.ResultBroadcastTx, error)
    69  	SubmitRawTransactionCommit(ctx context.Context, tx []byte) (*tmctypes.ResultBroadcastTxCommit, error)
    70  	CheckTransaction(ctx context.Context, tx *commandspb.Transaction) (*tmctypes.ResultCheckTx, error)
    71  	CheckRawTransaction(ctx context.Context, tx []byte) (*tmctypes.ResultCheckTx, error)
    72  	GetGenesisTime(ctx context.Context) (genesisTime time.Time, err error)
    73  	GetChainID(ctx context.Context) (chainID string, err error)
    74  	GetNetworkInfo(ctx context.Context) (netInfo *tmctypes.ResultNetInfo, err error)
    75  	GetStatus(ctx context.Context) (status *tmctypes.ResultStatus, err error)
    76  	GetUnconfirmedTxCount(ctx context.Context) (count int, err error)
    77  	Health() (*tmctypes.ResultHealth, error)
    78  }
    79  
    80  type ProofOfWorkParams interface {
    81  	SpamPoWNumberOfPastBlocks() uint32
    82  	SpamPoWDifficulty() uint32
    83  	SpamPoWHashFunction() string
    84  	SpamPoWNumberOfTxPerBlock() uint32
    85  	SpamPoWIncreasingDifficulty() bool
    86  	BlockData() (uint64, string)
    87  	IsReady() bool
    88  }
    89  
    90  type SpamEngine interface {
    91  	GetSpamStatistics(partyID string) *protoapi.SpamStatistics
    92  }
    93  
    94  type PowEngine interface {
    95  	GetSpamStatistics(partyID string) *protoapi.PoWStatistic
    96  }
    97  
    98  // GRPCServer represent the grpc api provided by the vega node.
    99  type GRPC struct {
   100  	Config
   101  
   102  	client     Blockchain
   103  	log        *logging.Logger
   104  	srv        *grpc.Server
   105  	stats      *stats.Stats
   106  	timesvc    *vegatime.Svc
   107  	evtfwd     EvtForwarder
   108  	evtService EventService
   109  	powParams  ProofOfWorkParams
   110  	spamEngine SpamEngine
   111  	powEngine  PowEngine
   112  
   113  	// used in order to gracefully close streams
   114  	ctx   context.Context
   115  	cfunc context.CancelFunc
   116  
   117  	core *coreService
   118  
   119  	services []func(*grpc.Server)
   120  }
   121  
   122  // NewGRPC create a new instance of the GPRC api for the vega node.
   123  func NewGRPC(
   124  	log *logging.Logger,
   125  	config Config,
   126  	stats *stats.Stats,
   127  	client Blockchain,
   128  	evtfwd EvtForwarder,
   129  	timeService *vegatime.Svc,
   130  	eventService *subscribers.Service,
   131  	powParams ProofOfWorkParams,
   132  	spamEngine SpamEngine,
   133  	powEngine PowEngine,
   134  ) *GRPC {
   135  	// setup logger
   136  	log = log.Named(namedLogger)
   137  	log.SetLevel(config.Level.Get())
   138  	ctx, cfunc := context.WithCancel(context.Background())
   139  
   140  	return &GRPC{
   141  		log:        log,
   142  		Config:     config,
   143  		stats:      stats,
   144  		client:     client,
   145  		timesvc:    timeService,
   146  		ctx:        ctx,
   147  		cfunc:      cfunc,
   148  		evtfwd:     evtfwd,
   149  		evtService: eventService,
   150  		powParams:  powParams,
   151  		spamEngine: spamEngine,
   152  		powEngine:  powEngine,
   153  	}
   154  }
   155  
   156  func (g *GRPC) UpdateProtocolServices(
   157  	evtforwarder EvtForwarder,
   158  	timesvc *vegatime.Svc,
   159  	evtsvc EventService,
   160  	powParams ProofOfWorkParams,
   161  ) {
   162  	// first save them, in case the core service is not started,
   163  	// it'll be used later
   164  	g.evtService = evtsvc
   165  	g.timesvc = timesvc
   166  	g.evtfwd = evtforwarder
   167  	g.powParams = powParams
   168  
   169  	if g.core != nil {
   170  		g.core.UpdateProtocolServices(evtforwarder, timesvc, evtsvc, powParams)
   171  	}
   172  }
   173  
   174  func (g *GRPC) RegisterService(f func(*grpc.Server)) {
   175  	g.services = append(g.services, f)
   176  }
   177  
   178  // ReloadConf update the internal configuration of the GRPC server.
   179  func (g *GRPC) ReloadConf(cfg Config) {
   180  	g.log.Info("reloading configuration")
   181  	if g.log.GetLevel() != cfg.Level.Get() {
   182  		g.log.Info("updating log level",
   183  			logging.String("old", g.log.GetLevel().String()),
   184  			logging.String("new", cfg.Level.String()),
   185  		)
   186  		g.log.SetLevel(cfg.Level.Get())
   187  	}
   188  
   189  	// TODO(): not updating the the actual server for now, may need to look at this later
   190  	// e.g restart the http server on another port or whatever
   191  	g.Config = cfg
   192  	g.core.updateConfig(cfg)
   193  }
   194  
   195  func remoteAddrInterceptor(log *logging.Logger) grpc.UnaryServerInterceptor {
   196  	return func(
   197  		ctx context.Context,
   198  		req interface{},
   199  		info *grpc.UnaryServerInfo,
   200  		handler grpc.UnaryHandler,
   201  	) (resp interface{}, err error) {
   202  		// first check if the request is forwarded from our restproxy
   203  		// get the metadata
   204  		var ip string
   205  		md, ok := metadata.FromIncomingContext(ctx)
   206  		if ok {
   207  			forwardedFor, ok := md["x-forwarded-for"]
   208  			if ok && len(forwardedFor) > 0 {
   209  				log.Debug("grpc request x-forwarded-for",
   210  					logging.String("method", info.FullMethod),
   211  					logging.String("remote-ip-addr", forwardedFor[0]),
   212  				)
   213  				ip = forwardedFor[0]
   214  			}
   215  		}
   216  
   217  		// if the request is not forwarded let's get it from the peer infos
   218  		if len(ip) <= 0 {
   219  			p, ok := peer.FromContext(ctx)
   220  			if ok && p != nil {
   221  				log.Debug("grpc peer client request",
   222  					logging.String("method", info.FullMethod),
   223  					logging.String("remote-ip-addr", p.Addr.String()))
   224  				ip = p.Addr.String()
   225  			}
   226  		}
   227  
   228  		ctx = vgcontext.WithRemoteIPAddr(ctx, ip)
   229  
   230  		// Calls the handler
   231  		h, err := handler(ctx, req)
   232  
   233  		log.Debug("Invoked RPC call",
   234  			logging.String("method", info.FullMethod),
   235  			logging.Error(err),
   236  		)
   237  
   238  		return h, err
   239  	}
   240  }
   241  
   242  // Start start the grpc server.
   243  func (g *GRPC) Start() {
   244  	ip := g.IP
   245  	port := strconv.Itoa(g.Port)
   246  
   247  	g.log.Info("Starting gRPC based API", logging.String("addr", ip), logging.String("port", port))
   248  
   249  	lis, err := net.Listen("tcp", net.JoinHostPort(ip, port))
   250  	if err != nil {
   251  		g.log.Panic("Failure listening on gRPC port", logging.String("port", port), logging.Error(err))
   252  	}
   253  
   254  	intercept := grpc.UnaryInterceptor(remoteAddrInterceptor(g.log))
   255  	g.srv = grpc.NewServer(intercept)
   256  
   257  	coreSvc := &coreService{
   258  		log:          g.log,
   259  		conf:         g.Config,
   260  		blockchain:   g.client,
   261  		timesvc:      g.timesvc,
   262  		stats:        g.stats,
   263  		evtForwarder: g.evtfwd,
   264  		eventService: g.evtService,
   265  		powParams:    g.powParams,
   266  		spamEngine:   g.spamEngine,
   267  		powEngine:    g.powEngine,
   268  	}
   269  	g.core = coreSvc
   270  	protoapi.RegisterCoreServiceServer(g.srv, coreSvc)
   271  
   272  	for _, f := range g.services {
   273  		f(g.srv)
   274  	}
   275  
   276  	go g.core.updateNetInfo(g.ctx)
   277  
   278  	err = g.srv.Serve(lis)
   279  	if err != nil {
   280  		g.log.Panic("Failure serving gRPC API", logging.Error(err))
   281  	}
   282  }
   283  
   284  // Stop stops the GRPC server.
   285  func (g *GRPC) Stop() {
   286  	if g.srv == nil {
   287  		return
   288  	}
   289  
   290  	done := make(chan struct{})
   291  	go func() {
   292  		g.log.Info("Gracefully stopping gRPC based API")
   293  		g.srv.GracefulStop()
   294  		done <- struct{}{}
   295  	}()
   296  
   297  	select {
   298  	case <-done:
   299  	case <-time.After(10 * time.Second):
   300  		g.log.Info("Force stopping gRPC based API")
   301  		g.srv.Stop()
   302  	}
   303  }