code.vegaprotocol.io/vega@v0.79.0/core/api/core.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  	"encoding/hex"
    21  	"fmt"
    22  	"math"
    23  	"reflect"
    24  	"strings"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	"code.vegaprotocol.io/vega/core/events"
    30  	"code.vegaprotocol.io/vega/core/evtforward"
    31  	"code.vegaprotocol.io/vega/core/metrics"
    32  	"code.vegaprotocol.io/vega/core/stats"
    33  	"code.vegaprotocol.io/vega/core/vegatime"
    34  	"code.vegaprotocol.io/vega/libs/proto"
    35  	"code.vegaprotocol.io/vega/libs/subscribers"
    36  	"code.vegaprotocol.io/vega/logging"
    37  	ptypes "code.vegaprotocol.io/vega/protos/vega"
    38  	protoapi "code.vegaprotocol.io/vega/protos/vega/api/v1"
    39  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    40  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    41  	"code.vegaprotocol.io/vega/wallet/crypto"
    42  
    43  	"github.com/cometbft/cometbft/libs/bytes"
    44  	tmctypes "github.com/cometbft/cometbft/rpc/core/types"
    45  	"github.com/pkg/errors"
    46  	"google.golang.org/grpc/codes"
    47  )
    48  
    49  var (
    50  	ErrInvalidSignature           = errors.New("invalid signature")
    51  	ErrSubmitTxCommitDisabled     = errors.New("broadcast_tx_commit is disabled")
    52  	ErrUnknownSubmitTxRequestType = errors.New("invalid broadcast_tx type")
    53  )
    54  
    55  type coreService struct {
    56  	protoapi.UnimplementedCoreServiceServer
    57  	log  *logging.Logger
    58  	conf Config
    59  
    60  	blockchain Blockchain
    61  	stats      *stats.Stats
    62  
    63  	svcMu        sync.RWMutex
    64  	evtForwarder EvtForwarder
    65  	timesvc      TimeService
    66  	eventService EventService
    67  	subCancels   []func()
    68  	powParams    ProofOfWorkParams
    69  	spamEngine   SpamEngine
    70  	powEngine    PowEngine
    71  
    72  	chainID                  string
    73  	genesisTime              time.Time
    74  	hasGenesisTimeAndChainID atomic.Bool
    75  	mu                       sync.Mutex
    76  
    77  	netInfo   *tmctypes.ResultNetInfo
    78  	netInfoMu sync.RWMutex
    79  }
    80  
    81  func (s *coreService) UpdateProtocolServices(
    82  	evtforwarder EvtForwarder,
    83  	timesvc TimeService,
    84  	evtsvc EventService,
    85  	powParams ProofOfWorkParams,
    86  ) {
    87  	s.svcMu.Lock()
    88  	defer s.svcMu.Unlock()
    89  	// first cancel all subscriptions
    90  	for _, f := range s.subCancels {
    91  		f()
    92  	}
    93  	s.evtForwarder = evtforwarder
    94  	s.eventService = evtsvc
    95  	s.timesvc = timesvc
    96  	s.powParams = powParams
    97  }
    98  
    99  // no need for a mutex - we only access the config through a value receiver.
   100  func (s *coreService) updateConfig(conf Config) {
   101  	s.conf = conf
   102  }
   103  
   104  func (s *coreService) LastBlockHeight(
   105  	ctx context.Context,
   106  	_ *protoapi.LastBlockHeightRequest,
   107  ) (*protoapi.LastBlockHeightResponse, error) {
   108  	defer metrics.StartAPIRequestAndTimeGRPC("LastBlockHeight")()
   109  
   110  	if !s.hasGenesisTimeAndChainID.Load() {
   111  		if err := s.getGenesisTimeAndChainID(ctx); err != nil {
   112  			return nil, fmt.Errorf("failed to intialise chainID: %w", err)
   113  		}
   114  	}
   115  
   116  	blockHeight, blockHash := s.powParams.BlockData()
   117  	if s.log.IsDebug() {
   118  		s.log.Debug("block height requested, returning", logging.Uint64("block-height", blockHeight), logging.String("block hash", blockHash), logging.String("chaindID", s.chainID))
   119  	}
   120  
   121  	if !s.powParams.IsReady() {
   122  		return nil, errors.New("Failed to get last block height server is initialising")
   123  	}
   124  
   125  	return &protoapi.LastBlockHeightResponse{
   126  		Height:                      blockHeight,
   127  		Hash:                        blockHash,
   128  		SpamPowDifficulty:           s.powParams.SpamPoWDifficulty(),
   129  		SpamPowHashFunction:         s.powParams.SpamPoWHashFunction(),
   130  		SpamPowNumberOfPastBlocks:   s.powParams.SpamPoWNumberOfPastBlocks(),
   131  		SpamPowNumberOfTxPerBlock:   s.powParams.SpamPoWNumberOfTxPerBlock(),
   132  		SpamPowIncreasingDifficulty: s.powParams.SpamPoWIncreasingDifficulty(),
   133  		ChainId:                     s.chainID,
   134  	}, nil
   135  }
   136  
   137  // GetVegaTime returns the latest blockchain header timestamp, in UnixNano format.
   138  // Example: "1568025900111222333" corresponds to 2019-09-09T10:45:00.111222333Z.
   139  func (s *coreService) GetVegaTime(ctx context.Context, _ *protoapi.GetVegaTimeRequest) (*protoapi.GetVegaTimeResponse, error) {
   140  	defer metrics.StartAPIRequestAndTimeGRPC("GetVegaTime")()
   141  	s.svcMu.RLock()
   142  	defer s.svcMu.RUnlock()
   143  
   144  	return &protoapi.GetVegaTimeResponse{
   145  		Timestamp: s.timesvc.GetTimeNow().UnixNano(),
   146  	}, nil
   147  }
   148  
   149  func (s *coreService) SubmitTransaction(ctx context.Context, req *protoapi.SubmitTransactionRequest) (*protoapi.SubmitTransactionResponse, error) {
   150  	startTime := time.Now()
   151  	defer metrics.APIRequestAndTimeGRPC("SubmitTransaction", startTime)
   152  
   153  	if req == nil {
   154  		return nil, apiError(codes.InvalidArgument, ErrMalformedRequest)
   155  	}
   156  
   157  	txResult, err := s.blockchain.SubmitTransactionSync(ctx, req.Tx)
   158  	if err != nil {
   159  		return nil, apiError(codes.Internal, err)
   160  	}
   161  
   162  	return &protoapi.SubmitTransactionResponse{
   163  		Success: txResult.Code == 0,
   164  		Code:    txResult.Code,
   165  		Data:    string(txResult.Data.Bytes()),
   166  		Log:     txResult.Log,
   167  		Height:  0,
   168  		TxHash:  txResult.Hash.String(),
   169  	}, nil
   170  }
   171  
   172  func (s *coreService) CheckTransaction(ctx context.Context, req *protoapi.CheckTransactionRequest) (*protoapi.CheckTransactionResponse, error) {
   173  	startTime := time.Now()
   174  	defer metrics.APIRequestAndTimeGRPC("CheckTransaction", startTime)
   175  
   176  	if req == nil {
   177  		return nil, apiError(codes.InvalidArgument, ErrMalformedRequest)
   178  	}
   179  
   180  	checkResult, err := s.blockchain.CheckTransaction(ctx, req.Tx)
   181  	if err != nil {
   182  		return nil, apiError(codes.Internal, err)
   183  	}
   184  
   185  	return &protoapi.CheckTransactionResponse{
   186  		Code:      checkResult.Code,
   187  		Data:      string(checkResult.Data),
   188  		Info:      checkResult.Info,
   189  		Log:       checkResult.Log,
   190  		Success:   checkResult.IsOK(),
   191  		GasWanted: checkResult.GasWanted,
   192  		GasUsed:   checkResult.GasUsed,
   193  	}, nil
   194  }
   195  
   196  func (s *coreService) CheckRawTransaction(ctx context.Context, req *protoapi.CheckRawTransactionRequest) (*protoapi.CheckRawTransactionResponse, error) {
   197  	startTime := time.Now()
   198  	defer metrics.APIRequestAndTimeGRPC("CheckRawTransaction", startTime)
   199  
   200  	if req == nil {
   201  		return nil, apiError(codes.InvalidArgument, ErrMalformedRequest)
   202  	}
   203  
   204  	checkResult, err := s.blockchain.CheckRawTransaction(ctx, req.Tx)
   205  	if err != nil {
   206  		return nil, apiError(codes.Internal, err)
   207  	}
   208  
   209  	return &protoapi.CheckRawTransactionResponse{
   210  		Code:      checkResult.Code,
   211  		Data:      string(checkResult.Data),
   212  		Info:      checkResult.Info,
   213  		Log:       checkResult.Log,
   214  		Success:   checkResult.IsOK(),
   215  		GasWanted: checkResult.GasWanted,
   216  		GasUsed:   checkResult.GasUsed,
   217  	}, nil
   218  }
   219  
   220  func (s *coreService) PropagateChainEvent(ctx context.Context, req *protoapi.PropagateChainEventRequest) (*protoapi.PropagateChainEventResponse, error) {
   221  	if req.Event == nil {
   222  		return nil, apiError(codes.InvalidArgument, ErrMalformedRequest)
   223  	}
   224  
   225  	// verify the signature then
   226  	err := verifySignature(s.log, req.Event, req.Signature, req.PubKey)
   227  	if err != nil {
   228  		return nil, apiError(codes.InvalidArgument, fmt.Errorf("not a valid signature: %w", err))
   229  	}
   230  
   231  	evt := commandspb.ChainEvent{}
   232  	err = proto.Unmarshal(req.Event, &evt)
   233  	if err != nil {
   234  		return nil, apiError(codes.InvalidArgument, fmt.Errorf("not a valid chain event: %w", err))
   235  	}
   236  
   237  	ok := true
   238  	s.svcMu.RLock()
   239  	defer s.svcMu.RUnlock()
   240  	err = s.evtForwarder.Forward(ctx, &evt, req.PubKey)
   241  	if err != nil && err != evtforward.ErrEvtAlreadyExist {
   242  		s.log.Error("unable to forward chain event",
   243  			logging.String("pubkey", req.PubKey),
   244  			logging.Error(err))
   245  		if err == evtforward.ErrPubKeyNotAllowlisted {
   246  			return nil, apiError(codes.PermissionDenied, err)
   247  		}
   248  		return nil, apiError(codes.Internal, err)
   249  	}
   250  
   251  	return &protoapi.PropagateChainEventResponse{
   252  		Success: ok,
   253  	}, nil
   254  }
   255  
   256  func verifySignature(
   257  	log *logging.Logger,
   258  	message []byte,
   259  	sig []byte,
   260  	pubKey string,
   261  ) error {
   262  	validator, err := crypto.NewSignatureAlgorithm(crypto.Ed25519, 1)
   263  	if err != nil {
   264  		if log != nil {
   265  			log.Error("unable to instantiate new algorithm", logging.Error(err))
   266  		}
   267  		return err
   268  	}
   269  
   270  	pubKeyBytes, err := hex.DecodeString(pubKey)
   271  	if err != nil {
   272  		if log != nil {
   273  			log.Error("unable to decode hexencoded ubkey", logging.Error(err))
   274  		}
   275  		return err
   276  	}
   277  	ok, err := validator.Verify(pubKeyBytes, message, sig)
   278  	if err != nil {
   279  		if log != nil {
   280  			log.Error("unable to verify bundle", logging.Error(err))
   281  		}
   282  		return err
   283  	}
   284  	if !ok {
   285  		return ErrInvalidSignature
   286  	}
   287  	return nil
   288  }
   289  
   290  // Statistics provides various blockchain and Vega statistics, including:
   291  // Blockchain height, backlog length, current time, orders and trades per block, tendermint version
   292  // Vega counts for parties, markets, order actions (amend, cancel, submit), Vega version.
   293  func (s *coreService) Statistics(ctx context.Context, _ *protoapi.StatisticsRequest) (*protoapi.StatisticsResponse, error) {
   294  	defer metrics.StartAPIRequestAndTimeGRPC("Statistics")()
   295  	// Call tendermint and related services to get information for statistics
   296  	// We load read-only internal statistics through each package level statistics structs
   297  	s.svcMu.RLock()
   298  	epochTime := s.timesvc.GetTimeNow()
   299  	s.svcMu.RUnlock()
   300  
   301  	// Call tendermint via rpc client
   302  	var (
   303  		backlogLength, numPeers int
   304  		gt                      *time.Time
   305  		chainID                 string
   306  	)
   307  
   308  	backlogLength, numPeers, gt, chainID, err := s.getTendermintStats(ctx)
   309  	if err != nil {
   310  		// do not return an error, let just eventually log it
   311  		s.log.Debug("could not load tendermint stats", logging.Error(err))
   312  	}
   313  
   314  	// If the chain is replaying then genesis time can be nil
   315  	genesisTime := ""
   316  	if gt != nil {
   317  		genesisTime = vegatime.Format(*gt)
   318  	}
   319  
   320  	stats := &protoapi.Statistics{
   321  		BlockHeight:           s.stats.Blockchain.Height(),
   322  		BlockHash:             s.stats.Blockchain.Hash(),
   323  		BacklogLength:         uint64(backlogLength),
   324  		TotalPeers:            uint64(numPeers),
   325  		GenesisTime:           genesisTime,
   326  		CurrentTime:           vegatime.Format(time.Now()),
   327  		VegaTime:              vegatime.Format(epochTime),
   328  		Uptime:                vegatime.Format(s.stats.GetUptime()),
   329  		TxPerBlock:            s.stats.Blockchain.TotalTxLastBatch(),
   330  		AverageTxBytes:        s.stats.Blockchain.AverageTxSizeBytes(),
   331  		AverageOrdersPerBlock: s.stats.Blockchain.AverageOrdersPerBatch(),
   332  		TradesPerSecond:       s.stats.Blockchain.TradesPerSecond(),
   333  		OrdersPerSecond:       s.stats.Blockchain.OrdersPerSecond(),
   334  		Status:                ptypes.ChainStatus_CHAIN_STATUS_CONNECTED,
   335  		AppVersionHash:        s.stats.GetVersionHash(),
   336  		AppVersion:            s.stats.GetVersion(),
   337  		ChainVersion:          s.stats.GetChainVersion(),
   338  		TotalAmendOrder:       s.stats.Blockchain.TotalAmendOrder(),
   339  		TotalCancelOrder:      s.stats.Blockchain.TotalCancelOrder(),
   340  		TotalCreateOrder:      s.stats.Blockchain.TotalCreateOrder(),
   341  		TotalOrders:           s.stats.Blockchain.TotalOrders(),
   342  		TotalTrades:           s.stats.Blockchain.TotalTrades(),
   343  		BlockDuration:         s.stats.Blockchain.BlockDuration(),
   344  		EventCount:            s.stats.Blockchain.TotalEventsLastBatch(),
   345  		EventsPerSecond:       s.stats.Blockchain.EventsPerSecond(),
   346  		EpochSeq:              s.stats.GetEpochSeq(),
   347  		EpochStartTime:        vegatime.Format(s.stats.GetEpochStartTime()),
   348  		EpochExpiryTime:       vegatime.Format(s.stats.GetEpochExpireTime()),
   349  		ChainId:               chainID,
   350  	}
   351  	return &protoapi.StatisticsResponse{
   352  		Statistics: stats,
   353  	}, nil
   354  }
   355  
   356  func (s *coreService) getTendermintStats(
   357  	ctx context.Context,
   358  ) (
   359  	backlogLength, numPeers int,
   360  	genesis *time.Time,
   361  	chainID string,
   362  	err error,
   363  ) {
   364  	if s.stats == nil || s.stats.Blockchain == nil {
   365  		return 0, 0, nil, "", apiError(codes.Internal, ErrChainNotConnected)
   366  	}
   367  
   368  	const refused = "connection refused"
   369  
   370  	// Unconfirmed TX count == current transaction backlog length
   371  	backlogLength, err = s.blockchain.GetUnconfirmedTxCount(ctx)
   372  	if err != nil {
   373  		if strings.Contains(err.Error(), refused) {
   374  			return 0, 0, nil, "", nil
   375  		}
   376  		return 0, 0, nil, "", apiError(codes.Internal, ErrBlockchainBacklogLength, err)
   377  	}
   378  
   379  	if !s.hasGenesisTimeAndChainID.Load() {
   380  		if err = s.getGenesisTimeAndChainID(ctx); err != nil {
   381  			return 0, 0, nil, "", err
   382  		}
   383  	}
   384  
   385  	// Net info provides peer stats etc (block chain network info) == number of peers
   386  	netInfo, err := s.getTMNetInfo(ctx)
   387  	if err != nil {
   388  		return backlogLength, 0, &s.genesisTime, s.chainID, nil // nolint
   389  	}
   390  
   391  	return backlogLength, netInfo.NPeers, &s.genesisTime, s.chainID, nil
   392  }
   393  
   394  func (s *coreService) getTMNetInfo(_ context.Context) (tmctypes.ResultNetInfo, error) {
   395  	s.netInfoMu.RLock()
   396  	defer s.netInfoMu.RUnlock()
   397  
   398  	if s.netInfo == nil {
   399  		return tmctypes.ResultNetInfo{}, apiError(codes.Internal, ErrBlockchainNetworkInfo)
   400  	}
   401  
   402  	return *s.netInfo, nil
   403  }
   404  
   405  func (s *coreService) updateNetInfo(ctx context.Context) {
   406  	// update the net info every 1 minutes
   407  	ticker := time.NewTicker(1 * time.Minute)
   408  	defer ticker.Stop()
   409  
   410  	for {
   411  		select {
   412  		case <-ctx.Done():
   413  			return
   414  		case <-ticker.C:
   415  			netInfo, err := s.blockchain.GetNetworkInfo(ctx)
   416  			if err != nil {
   417  				continue
   418  			}
   419  			s.netInfoMu.Lock()
   420  			s.netInfo = netInfo
   421  			s.netInfoMu.Unlock()
   422  		}
   423  	}
   424  }
   425  
   426  func (s *coreService) getGenesisTimeAndChainID(ctx context.Context) error {
   427  	const refused = "connection refused"
   428  	// just lock in here, ideally we'll come here only once, so not a big issue to lock
   429  	s.mu.Lock()
   430  	defer s.mu.Unlock()
   431  
   432  	var err error
   433  	// Genesis retrieves the current genesis date/time for the blockchain
   434  	s.genesisTime, err = s.blockchain.GetGenesisTime(ctx)
   435  	if err != nil {
   436  		if strings.Contains(err.Error(), refused) {
   437  			return nil
   438  		}
   439  		return apiError(codes.Internal, ErrBlockchainGenesisTime, err)
   440  	}
   441  
   442  	s.chainID, err = s.blockchain.GetChainID(ctx)
   443  	if err != nil {
   444  		return apiError(codes.Internal, ErrBlockchainChainID, err)
   445  	}
   446  
   447  	s.hasGenesisTimeAndChainID.Store(true)
   448  	return nil
   449  }
   450  
   451  func (s *coreService) ObserveEventBus(
   452  	stream protoapi.CoreService_ObserveEventBusServer,
   453  ) error {
   454  	defer metrics.StartAPIRequestAndTimeGRPC("ObserveEventBus")()
   455  
   456  	ctx, cfunc := context.WithCancel(stream.Context())
   457  	defer cfunc()
   458  
   459  	// now we start listening for a few seconds in order to get at least the very first message
   460  	// this will be blocking until the connection by the client is closed
   461  	// and we will not start processing any events until we receive the original request
   462  	// indicating filters and batch size.
   463  	req, err := s.recvEventRequest(stream)
   464  	if err != nil {
   465  		// client exited, nothing to do
   466  		return nil // nolint
   467  	}
   468  
   469  	// now we will aggregate filter out of the initial request
   470  	types, err := events.ProtoToInternal(req.Type...)
   471  	if err != nil {
   472  		return apiError(codes.InvalidArgument, ErrMalformedRequest, err)
   473  	}
   474  	filters := []subscribers.EventFilter{}
   475  	if len(req.MarketId) > 0 && len(req.PartyId) > 0 {
   476  		filters = append(filters, events.GetPartyAndMarketFilter(req.MarketId, req.PartyId))
   477  	} else {
   478  		if len(req.MarketId) > 0 {
   479  			filters = append(filters, events.GetMarketIDFilter(req.MarketId))
   480  		}
   481  		if len(req.PartyId) > 0 {
   482  			filters = append(filters, events.GetPartyIDFilter(req.PartyId))
   483  		}
   484  	}
   485  
   486  	// here we add the cancel to the list of observer
   487  	// so if a protocol upgrade happen we can stop processing those
   488  	// and nicely do the upgrade
   489  	s.svcMu.Lock()
   490  	s.subCancels = append(s.subCancels, cfunc)
   491  	s.svcMu.Unlock()
   492  
   493  	// number of retries to -1 to have pretty much unlimited retries
   494  	ch, bCh := s.eventService.ObserveEvents(ctx, s.conf.StreamRetries, types, int(req.BatchSize), filters...)
   495  	defer close(bCh)
   496  
   497  	if req.BatchSize > 0 {
   498  		err := s.observeEventsWithAck(ctx, stream, req.BatchSize, ch, bCh)
   499  		return err
   500  	}
   501  	err = s.observeEvents(ctx, stream, ch)
   502  	return err
   503  }
   504  
   505  func (s *coreService) observeEvents(
   506  	ctx context.Context,
   507  	stream protoapi.CoreService_ObserveEventBusServer,
   508  	ch <-chan []*eventspb.BusEvent,
   509  ) error {
   510  	for {
   511  		select {
   512  		case data, ok := <-ch:
   513  			if !ok {
   514  				return nil
   515  			}
   516  			resp := &protoapi.ObserveEventBusResponse{
   517  				Events: data,
   518  			}
   519  			if err := stream.Send(resp); err != nil {
   520  				s.log.Error("Error sending event on stream", logging.Error(err))
   521  				return apiError(codes.Internal, ErrStreamInternal, err)
   522  			}
   523  		case <-ctx.Done():
   524  			return apiError(codes.Internal, ErrStreamInternal, ctx.Err())
   525  		}
   526  	}
   527  }
   528  
   529  func (s *coreService) recvEventRequest(
   530  	stream protoapi.CoreService_ObserveEventBusServer,
   531  ) (*protoapi.ObserveEventBusRequest, error) {
   532  	readCtx, cfunc := context.WithTimeout(stream.Context(), 5*time.Second)
   533  	oebCh := make(chan protoapi.ObserveEventBusRequest)
   534  	var err error
   535  	go func() {
   536  		defer close(oebCh)
   537  		nb := protoapi.ObserveEventBusRequest{}
   538  		if err = stream.RecvMsg(&nb); err != nil {
   539  			cfunc()
   540  			return
   541  		}
   542  		oebCh <- nb
   543  	}()
   544  	select {
   545  	case <-readCtx.Done():
   546  		if err != nil {
   547  			// this means the client disconnected
   548  			return nil, err
   549  		}
   550  		// this mean we timedout
   551  		return nil, readCtx.Err()
   552  	case nb := <-oebCh:
   553  		return &nb, nil
   554  	}
   555  }
   556  
   557  func (s *coreService) observeEventsWithAck(
   558  	ctx context.Context,
   559  	stream protoapi.CoreService_ObserveEventBusServer,
   560  	batchSize int64,
   561  	ch <-chan []*eventspb.BusEvent,
   562  	bCh chan<- int,
   563  ) error {
   564  	for {
   565  		select {
   566  		case data, ok := <-ch:
   567  			if !ok {
   568  				return nil
   569  			}
   570  			resp := &protoapi.ObserveEventBusResponse{
   571  				Events: data,
   572  			}
   573  			if err := stream.Send(resp); err != nil {
   574  				s.log.Error("Error sending event on stream", logging.Error(err))
   575  				return apiError(codes.Internal, ErrStreamInternal, err)
   576  			}
   577  		case <-ctx.Done():
   578  			return apiError(codes.Internal, ErrStreamInternal, ctx.Err())
   579  		}
   580  
   581  		// now we try to read again the new size / ack
   582  		req, err := s.recvEventRequest(stream)
   583  		if err != nil {
   584  			return err
   585  		}
   586  
   587  		if req.BatchSize != batchSize {
   588  			batchSize = req.BatchSize
   589  			bCh <- int(batchSize)
   590  		}
   591  	}
   592  }
   593  
   594  func (s *coreService) handleSubmitRawTxTMError(err error) error {
   595  	// This is Tendermint's specific error signature
   596  	if _, ok := err.(interface {
   597  		Code() uint32
   598  		Details() string
   599  		Error() string
   600  	}); ok {
   601  		s.log.Debug("unable to submit raw transaction", logging.Error(err))
   602  		return apiError(codes.InvalidArgument, err)
   603  	}
   604  	s.log.Debug("unable to submit raw transaction", logging.Error(err))
   605  
   606  	return apiError(codes.Internal, err)
   607  }
   608  
   609  func setResponseBasisContent(response *protoapi.SubmitRawTransactionResponse, code uint32, log string, data, hash bytes.HexBytes) {
   610  	response.TxHash = hash.String()
   611  	response.Code = code
   612  	response.Data = data.String()
   613  	response.Log = log
   614  }
   615  
   616  func (s *coreService) SubmitRawTransaction(ctx context.Context, req *protoapi.SubmitRawTransactionRequest) (*protoapi.SubmitRawTransactionResponse, error) {
   617  	startTime := time.Now()
   618  	defer metrics.APIRequestAndTimeGRPC("SubmitTransaction", startTime)
   619  
   620  	if req == nil {
   621  		return nil, apiError(codes.InvalidArgument, ErrMalformedRequest)
   622  	}
   623  
   624  	successResponse := &protoapi.SubmitRawTransactionResponse{Success: true}
   625  	switch req.Type {
   626  	case protoapi.SubmitRawTransactionRequest_TYPE_ASYNC:
   627  		txResult, err := s.blockchain.SubmitRawTransactionAsync(ctx, req.Tx)
   628  		if err != nil {
   629  			if txResult != nil {
   630  				return &protoapi.SubmitRawTransactionResponse{
   631  					Success: false,
   632  					Code:    txResult.Code,
   633  					Data:    txResult.Data.String(),
   634  					Log:     txResult.Log,
   635  				}, s.handleSubmitRawTxTMError(err)
   636  			}
   637  			return nil, s.handleSubmitRawTxTMError(err)
   638  		}
   639  		setResponseBasisContent(successResponse, txResult.Code, txResult.Log, txResult.Data, txResult.Hash)
   640  
   641  	case protoapi.SubmitRawTransactionRequest_TYPE_SYNC:
   642  		txResult, err := s.blockchain.SubmitRawTransactionSync(ctx, req.Tx)
   643  		if err != nil {
   644  			if txResult != nil {
   645  				return &protoapi.SubmitRawTransactionResponse{
   646  					Success: false,
   647  					Code:    txResult.Code,
   648  					Data:    txResult.Data.String(),
   649  					Log:     txResult.Log,
   650  				}, s.handleSubmitRawTxTMError(err)
   651  			}
   652  			return nil, s.handleSubmitRawTxTMError(err)
   653  		}
   654  		setResponseBasisContent(successResponse, txResult.Code, txResult.Log, txResult.Data, txResult.Hash)
   655  
   656  	case protoapi.SubmitRawTransactionRequest_TYPE_COMMIT:
   657  		txResult, err := s.blockchain.SubmitRawTransactionCommit(ctx, req.Tx)
   658  		if err != nil {
   659  			if txResult != nil {
   660  				return &protoapi.SubmitRawTransactionResponse{
   661  					Success: false,
   662  					Code:    txResult.TxResult.Code,
   663  					Data:    string(txResult.TxResult.Data),
   664  					Log:     txResult.TxResult.Log,
   665  				}, s.handleSubmitRawTxTMError(err)
   666  			}
   667  			return nil, s.handleSubmitRawTxTMError(err)
   668  		}
   669  		setResponseBasisContent(successResponse, txResult.TxResult.Code, txResult.TxResult.Log, txResult.TxResult.Data, txResult.Hash)
   670  		successResponse.Height = txResult.Height
   671  
   672  	default:
   673  		return nil, apiError(codes.InvalidArgument, errors.New("Invalid TX Type"))
   674  	}
   675  
   676  	return successResponse, nil
   677  }
   678  
   679  func (s *coreService) GetSpamStatistics(ctx context.Context, req *protoapi.GetSpamStatisticsRequest) (*protoapi.GetSpamStatisticsResponse, error) {
   680  	if req.PartyId == "" {
   681  		return nil, apiError(codes.InvalidArgument, ErrEmptyMissingPartyID)
   682  	}
   683  
   684  	if !s.hasGenesisTimeAndChainID.Load() {
   685  		if err := s.getGenesisTimeAndChainID(ctx); err != nil {
   686  			return nil, fmt.Errorf("failed to intialise chainID: %w", err)
   687  		}
   688  	}
   689  
   690  	spamStats := &protoapi.SpamStatistics{}
   691  	// Spam engine is not set when NullBlockChain is used
   692  	if s.spamEngine != nil && !reflect.ValueOf(s.spamEngine).IsNil() {
   693  		spamStats = s.spamEngine.GetSpamStatistics(req.PartyId)
   694  	} else {
   695  		defaultStats := &protoapi.SpamStatistic{
   696  			MaxForEpoch:       math.MaxUint64,
   697  			MinTokensRequired: "0",
   698  		}
   699  		spamStats.Delegations = defaultStats
   700  		spamStats.NodeAnnouncements = defaultStats
   701  		spamStats.Proposals = defaultStats
   702  		spamStats.IssueSignatures = defaultStats
   703  		spamStats.Transfers = defaultStats
   704  		spamStats.CreateReferralSet = defaultStats
   705  		spamStats.UpdateReferralSet = defaultStats
   706  		spamStats.ApplyReferralCode = defaultStats
   707  		spamStats.Votes = &protoapi.VoteSpamStatistics{
   708  			Statistics:  []*protoapi.VoteSpamStatistic{},
   709  			MaxForEpoch: math.MaxUint64,
   710  		}
   711  	}
   712  
   713  	// Noop PoW Engine is used for NullBlockChain so this should be safe
   714  	spamStats.Pow = s.powEngine.GetSpamStatistics(req.PartyId)
   715  	spamStats.EpochSeq = s.stats.GetEpochSeq()
   716  
   717  	resp := &protoapi.GetSpamStatisticsResponse{
   718  		ChainId:    s.chainID,
   719  		Statistics: spamStats,
   720  	}
   721  
   722  	return resp, nil
   723  }