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 }