code.vegaprotocol.io/vega@v0.79.0/wallet/api/node/retrying_node.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 "fmt" 21 "time" 22 23 apipb "code.vegaprotocol.io/vega/protos/vega/api/v1" 24 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 25 "code.vegaprotocol.io/vega/wallet/api/node/adapters" 26 nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types" 27 28 "github.com/cenkalti/backoff/v4" 29 "go.uber.org/zap" 30 "google.golang.org/grpc/codes" 31 ) 32 33 //go:generate go run github.com/golang/mock/mockgen -destination mocks/node_mock.go -package mocks code.vegaprotocol.io/vega/wallet/api/node GRPCAdapter 34 35 type GRPCAdapter interface { 36 Host() string 37 Statistics(ctx context.Context) (nodetypes.Statistics, error) 38 SpamStatistics(ctx context.Context, pubKey string) (nodetypes.SpamStatistics, error) 39 SubmitTransaction(ctx context.Context, in *apipb.SubmitTransactionRequest) (*apipb.SubmitTransactionResponse, error) 40 CheckTransaction(ctx context.Context, in *apipb.CheckTransactionRequest) (*apipb.CheckTransactionResponse, error) 41 LastBlock(ctx context.Context) (nodetypes.LastBlock, error) 42 Stop() error 43 } 44 45 type RetryingNode struct { 46 log *zap.Logger 47 48 grpcAdapter GRPCAdapter 49 50 retries uint64 51 52 requestTTL time.Duration 53 } 54 55 func (n *RetryingNode) Host() string { 56 return n.grpcAdapter.Host() 57 } 58 59 func (n *RetryingNode) Statistics(ctx context.Context) (nodetypes.Statistics, error) { 60 n.log.Debug("querying the node statistics through the graphQL API", zap.String("host", n.grpcAdapter.Host())) 61 requestTime := time.Now() 62 ctx, cancel := context.WithTimeout(ctx, n.requestTTL) 63 defer cancel() 64 resp, err := n.grpcAdapter.Statistics(ctx) 65 if err != nil { 66 n.log.Error("could not get the statistics", 67 zap.String("host", n.grpcAdapter.Host()), 68 zap.Error(err), 69 ) 70 return nodetypes.Statistics{}, err 71 } 72 n.log.Debug("response from Statistics", 73 zap.String("host", n.grpcAdapter.Host()), 74 zap.Uint64("block-height", resp.BlockHeight), 75 zap.String("block-hash", resp.BlockHash), 76 zap.String("chain-id", resp.ChainID), 77 zap.String("vega-time", resp.VegaTime), 78 zap.Time("request-time", requestTime), 79 ) 80 return resp, nil 81 } 82 83 func (n *RetryingNode) SpamStatistics(ctx context.Context, pubKey string) (nodetypes.SpamStatistics, error) { 84 n.log.Debug("querying the node statistics through the graphQL API", zap.String("host", n.grpcAdapter.Host())) 85 requestTime := time.Now() 86 ctx, cancel := context.WithTimeout(ctx, n.requestTTL) 87 defer cancel() 88 resp, err := n.grpcAdapter.SpamStatistics(ctx, pubKey) 89 if err != nil { 90 n.log.Error("could not get the statistics", 91 zap.String("host", n.grpcAdapter.Host()), 92 zap.Error(err), 93 ) 94 return nodetypes.SpamStatistics{}, err 95 } 96 97 n.log.Debug("response from SpamStatistics", 98 zap.String("host", n.grpcAdapter.Host()), 99 zap.String("chain-id", resp.ChainID), 100 zap.Uint64("epoch", resp.EpochSeq), 101 zap.Uint64("block-height", resp.LastBlockHeight), 102 zap.Uint64("prosposals-count-for-epoch", resp.Proposals.CountForEpoch), 103 zap.Uint64("transfers-count-for-epoch", resp.Transfers.CountForEpoch), 104 zap.Uint64("delegations-count-for-epoch", resp.Delegations.CountForEpoch), 105 zap.Uint64("issue-signatures-count-for-epoch", resp.IssuesSignatures.CountForEpoch), 106 zap.Uint64("node-announcements-count-for-epoch", resp.NodeAnnouncements.CountForEpoch), 107 zap.Time("request-time", requestTime), 108 ) 109 return resp, nil 110 } 111 112 // LastBlock returns information about the last block acknowledged by the node. 113 func (n *RetryingNode) LastBlock(ctx context.Context) (nodetypes.LastBlock, error) { 114 n.log.Debug("getting the last block from the gRPC API", zap.String("host", n.grpcAdapter.Host())) 115 var resp nodetypes.LastBlock 116 if err := n.retry(func() error { 117 requestTime := time.Now() 118 ctx, cancel := context.WithTimeout(ctx, n.requestTTL) 119 defer cancel() 120 r, err := n.grpcAdapter.LastBlock(ctx) 121 if err != nil { 122 return err 123 } 124 resp = r 125 n.log.Debug("response from LastBlockHeight", 126 zap.String("host", n.grpcAdapter.Host()), 127 zap.Uint64("block-height", r.BlockHeight), 128 zap.String("block-hash", r.BlockHash), 129 zap.Uint32("pow-difficulty", r.ProofOfWorkDifficulty), 130 zap.String("pow-hash-function", r.ProofOfWorkHashFunction), 131 zap.Time("request-time", requestTime), 132 ) 133 return nil 134 }); err != nil { 135 n.log.Error("could not the get last block", 136 zap.String("host", n.grpcAdapter.Host()), 137 zap.Error(err), 138 ) 139 return nodetypes.LastBlock{}, err 140 } 141 142 return resp, nil 143 } 144 145 func (n *RetryingNode) CheckTransaction(ctx context.Context, tx *commandspb.Transaction) error { 146 n.log.Debug("checking the transaction through the gRPC API", zap.String("host", n.grpcAdapter.Host())) 147 var resp *apipb.CheckTransactionResponse 148 if err := n.retry(func() error { 149 req := apipb.CheckTransactionRequest{ 150 Tx: tx, 151 } 152 requestTime := time.Now() 153 ctx, cancel := context.WithTimeout(ctx, n.requestTTL) 154 defer cancel() 155 r, err := n.grpcAdapter.CheckTransaction(ctx, &req) 156 if err != nil { 157 return n.handleSubmissionError(err) 158 } 159 n.log.Debug("response from CheckTransaction", 160 zap.String("host", n.grpcAdapter.Host()), 161 zap.Bool("success", r.Success), 162 zap.Time("request-time", requestTime), 163 ) 164 resp = r 165 return nil 166 }); err != nil { 167 return err 168 } 169 170 if !resp.Success { 171 return nodetypes.TransactionError{ 172 ABCICode: resp.Code, 173 Message: resp.Data, 174 } 175 } 176 177 return nil 178 } 179 180 func (n *RetryingNode) SendTransaction(ctx context.Context, tx *commandspb.Transaction, ty apipb.SubmitTransactionRequest_Type) (string, error) { 181 n.log.Debug("sending the transaction through the gRPC API", zap.String("host", n.grpcAdapter.Host())) 182 var resp *apipb.SubmitTransactionResponse 183 if err := n.retry(func() error { 184 req := apipb.SubmitTransactionRequest{ 185 Tx: tx, 186 Type: ty, 187 } 188 requestTime := time.Now() 189 ctx, cancel := context.WithTimeout(ctx, n.requestTTL) 190 defer cancel() 191 r, err := n.grpcAdapter.SubmitTransaction(ctx, &req) 192 if err != nil { 193 return n.handleSubmissionError(err) 194 } 195 n.log.Debug("response from SubmitTransaction", 196 zap.String("host", n.grpcAdapter.Host()), 197 zap.Bool("success", r.Success), 198 zap.String("hash", r.TxHash), 199 zap.Time("request-time", requestTime), 200 ) 201 resp = r 202 return nil 203 }); err != nil { 204 return "", err 205 } 206 207 if !resp.Success { 208 return "", nodetypes.TransactionError{ 209 ABCICode: resp.Code, 210 Message: resp.Data, 211 } 212 } 213 214 return resp.TxHash, nil 215 } 216 217 func (n *RetryingNode) Stop() error { 218 n.log.Debug("closing the gRPC API client", zap.String("host", n.grpcAdapter.Host())) 219 if err := n.grpcAdapter.Stop(); err != nil { 220 n.log.Warn("could not stop the gRPC API client-", 221 zap.String("host", n.grpcAdapter.Host()), 222 zap.Error(err), 223 ) 224 return fmt.Errorf("could not close properly stop the gRPC API client: %w", err) 225 } 226 n.log.Info("the gRPC API client successfully closed", zap.String("host", n.grpcAdapter.Host())) 227 return nil 228 } 229 230 func (n *RetryingNode) handleSubmissionError(err error) error { 231 statusErr := intoStatusError(err) 232 233 if statusErr == nil { 234 n.log.Error("could not submit the transaction", 235 zap.String("host", n.grpcAdapter.Host()), 236 zap.Error(err), 237 ) 238 return err 239 } 240 241 if statusErr.Code == codes.InvalidArgument { 242 n.log.Error( 243 "the transaction has been rejected because of an invalid argument or state, skipping retry...", 244 zap.String("host", n.grpcAdapter.Host()), 245 zap.Error(statusErr), 246 ) 247 // Returning a permanent error kills the retry loop. 248 return backoff.Permanent(statusErr) 249 } 250 251 n.log.Error("could not submit the transaction", 252 zap.String("host", n.grpcAdapter.Host()), 253 zap.Error(statusErr), 254 ) 255 return statusErr 256 } 257 258 func (n *RetryingNode) retry(o backoff.Operation) error { 259 return backoff.Retry(o, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), n.retries)) 260 } 261 262 func NewRetryingNode(log *zap.Logger, host string, retries uint64, ttl time.Duration) (*RetryingNode, error) { 263 grpcAdapter, err := adapters.NewGRPCAdapter(host) 264 if err != nil { 265 log.Error("could not initialise an insecure gRPC adapter", 266 zap.String("host", host), 267 zap.Error(err), 268 ) 269 return nil, err 270 } 271 272 return BuildRetryingNode(log, grpcAdapter, retries, ttl), nil 273 } 274 275 func BuildRetryingNode(log *zap.Logger, grpcAdapter GRPCAdapter, retries uint64, requestTTL time.Duration) *RetryingNode { 276 return &RetryingNode{ 277 log: log, 278 grpcAdapter: grpcAdapter, 279 retries: retries, 280 requestTTL: requestTTL, 281 } 282 }