code.vegaprotocol.io/vega@v0.79.0/wallet/node/forwarder.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 node
    17  
    18  import (
    19  	"context"
    20  	"sync/atomic"
    21  	"time"
    22  
    23  	api "code.vegaprotocol.io/vega/protos/vega/api/v1"
    24  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    25  	"code.vegaprotocol.io/vega/wallet/network"
    26  
    27  	"github.com/cenkalti/backoff/v4"
    28  	"go.uber.org/zap"
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/codes"
    31  	"google.golang.org/grpc/credentials/insecure"
    32  )
    33  
    34  type Forwarder struct {
    35  	log      *zap.Logger
    36  	nodeCfgs network.HostConfig
    37  	clts     []api.CoreServiceClient
    38  	conns    []*grpc.ClientConn
    39  	next     uint64
    40  }
    41  
    42  func NewForwarder(log *zap.Logger, nodeConfigs network.HostConfig) (*Forwarder, error) {
    43  	if len(nodeConfigs.Hosts) == 0 {
    44  		return nil, ErrNoHostSpecified
    45  	}
    46  
    47  	clts := make([]api.CoreServiceClient, 0, len(nodeConfigs.Hosts))
    48  	conns := make([]*grpc.ClientConn, 0, len(nodeConfigs.Hosts))
    49  	for _, v := range nodeConfigs.Hosts {
    50  		conn, err := grpc.Dial(v, grpc.WithTransportCredentials(insecure.NewCredentials()))
    51  		if err != nil {
    52  			log.Debug("Couldn't dial gRPC host", zap.String("address", v))
    53  			return nil, err
    54  		}
    55  		conns = append(conns, conn)
    56  		clts = append(clts, api.NewCoreServiceClient(conn))
    57  	}
    58  
    59  	return &Forwarder{
    60  		log:      log,
    61  		nodeCfgs: nodeConfigs,
    62  		clts:     clts,
    63  		conns:    conns,
    64  	}, nil
    65  }
    66  
    67  func (n *Forwarder) Stop() {
    68  	hasErrors := false
    69  	for i, v := range n.nodeCfgs.Hosts {
    70  		n.log.Debug("Closing gRPC client", zap.String("address", v))
    71  		if err := n.conns[i].Close(); err != nil {
    72  			n.log.Warn("Couldn't close gRPC client", zap.Error(err))
    73  			hasErrors = true
    74  		}
    75  	}
    76  	if hasErrors {
    77  		n.log.Warn("gRPC clients successfully with errors")
    78  	} else {
    79  		n.log.Info("gRPC clients successfully closed")
    80  	}
    81  }
    82  
    83  func (n *Forwarder) HealthCheck(ctx context.Context) error {
    84  	req := api.GetVegaTimeRequest{}
    85  	return backoff.Retry(
    86  		func() error {
    87  			clt := n.clts[n.nextClt()]
    88  			resp, err := clt.GetVegaTime(ctx, &req)
    89  			if err != nil {
    90  				return err
    91  			}
    92  			n.log.Debug("Response from GetVegaTime", zap.Int64("timestamp", resp.Timestamp))
    93  			return nil
    94  		},
    95  		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
    96  	)
    97  }
    98  
    99  // LastBlockHeightAndHash returns information about the last block from vega and the node it used to fetch it.
   100  func (n *Forwarder) LastBlockHeightAndHash(ctx context.Context) (*api.LastBlockHeightResponse, int, error) {
   101  	req := api.LastBlockHeightRequest{}
   102  	var resp *api.LastBlockHeightResponse
   103  	clt := -1
   104  	err := backoff.Retry(
   105  		func() error {
   106  			clt = n.nextClt()
   107  			r, err := n.clts[clt].LastBlockHeight(ctx, &req)
   108  			if err != nil {
   109  				n.log.Debug("Couldn't get last block", zap.Error(err))
   110  				return err
   111  			}
   112  			resp = r
   113  			n.log.Info("", zap.Uint64("block-height", r.Height), zap.String("block-hash", r.Hash), zap.Uint32("difficulty", r.SpamPowDifficulty), zap.String("function", r.SpamPowHashFunction))
   114  			return nil
   115  		},
   116  		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
   117  	)
   118  
   119  	if err != nil {
   120  		n.log.Error("Couldn't get last block", zap.Error(err))
   121  		clt = -1
   122  	} else {
   123  		n.log.Debug("Last block when sending transaction",
   124  			zap.Time("request.time", time.Now()),
   125  			zap.Uint64("block.height", resp.Height),
   126  		)
   127  	}
   128  
   129  	return resp, clt, err
   130  }
   131  
   132  func (n *Forwarder) CheckTx(ctx context.Context, tx *commandspb.Transaction, cltIdx int) (*api.CheckTransactionResponse, error) {
   133  	req := api.CheckTransactionRequest{
   134  		Tx: tx,
   135  	}
   136  	var resp *api.CheckTransactionResponse
   137  	if cltIdx < 0 {
   138  		cltIdx = n.nextClt()
   139  	}
   140  	err := backoff.Retry(
   141  		func() error {
   142  			clt := n.clts[cltIdx]
   143  			r, err := clt.CheckTransaction(ctx, &req)
   144  			if err != nil {
   145  				n.log.Error("Couldn't check transaction", zap.Error(err))
   146  				return err
   147  			}
   148  			n.log.Debug("Response from CheckTransaction",
   149  				zap.Bool("success", r.Success),
   150  			)
   151  			resp = r
   152  			return nil
   153  		},
   154  		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
   155  	)
   156  
   157  	return resp, err
   158  }
   159  
   160  func (n *Forwarder) SpamStatistics(ctx context.Context, pubkey string) (*api.GetSpamStatisticsResponse, int, error) {
   161  	req := api.GetSpamStatisticsRequest{
   162  		PartyId: pubkey,
   163  	}
   164  	var resp *api.GetSpamStatisticsResponse
   165  	clt := -1
   166  	err := backoff.Retry(
   167  		func() error {
   168  			clt = n.nextClt()
   169  			r, err := n.clts[clt].GetSpamStatistics(ctx, &req)
   170  			if err != nil {
   171  				n.log.Debug("Couldn't get spam statistics", zap.Error(err))
   172  				return err
   173  			}
   174  			resp = r
   175  			return nil
   176  		},
   177  		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
   178  	)
   179  
   180  	if err != nil {
   181  		n.log.Error("Couldn't get spam statistics", zap.Error(err))
   182  		clt = -1
   183  	} else {
   184  		n.log.Debug("Spam statistics",
   185  			zap.Time("request.time", time.Now()),
   186  		)
   187  	}
   188  
   189  	return resp, clt, err
   190  }
   191  
   192  func (n *Forwarder) SendTx(ctx context.Context, tx *commandspb.Transaction, ty api.SubmitTransactionRequest_Type, cltIdx int) (*api.SubmitTransactionResponse, error) {
   193  	req := api.SubmitTransactionRequest{
   194  		Tx:   tx,
   195  		Type: ty,
   196  	}
   197  	var resp *api.SubmitTransactionResponse
   198  	if cltIdx < 0 {
   199  		cltIdx = n.nextClt()
   200  	}
   201  	if err := backoff.Retry(
   202  		func() error {
   203  			clt := n.clts[cltIdx]
   204  			r, err := clt.SubmitTransaction(ctx, &req)
   205  			if err != nil {
   206  				return n.handleSubmissionError(err)
   207  			}
   208  			n.log.Debug("Transaction successfully submitted",
   209  				zap.Bool("success", r.Success),
   210  				zap.String("hash", r.TxHash),
   211  			)
   212  			resp = r
   213  			return nil
   214  		},
   215  		backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5),
   216  	); err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	return resp, nil
   221  }
   222  
   223  func (n *Forwarder) handleSubmissionError(err error) error {
   224  	statusErr := intoStatusError(err)
   225  
   226  	if statusErr == nil {
   227  		n.log.Error("couldn't submit transaction",
   228  			zap.Error(err),
   229  		)
   230  		return err
   231  	}
   232  
   233  	if statusErr.Code == codes.InvalidArgument {
   234  		n.log.Error(
   235  			"transaction has been rejected because of an invalid argument or state, skipping retry...",
   236  			zap.Error(statusErr),
   237  		)
   238  		// Returning a permanent error kills the retry loop.
   239  		return backoff.Permanent(statusErr)
   240  	}
   241  
   242  	n.log.Error("couldn't submit transaction",
   243  		zap.Error(statusErr),
   244  	)
   245  	return statusErr
   246  }
   247  
   248  func (n *Forwarder) nextClt() int {
   249  	i := atomic.AddUint64(&n.next, 1)
   250  	n.log.Info("Sending transaction to Vega node",
   251  		zap.String("host", n.nodeCfgs.Hosts[(int(i)-1)%len(n.clts)]),
   252  	)
   253  	return (int(i) - 1) % len(n.clts)
   254  }