code.vegaprotocol.io/vega@v0.79.0/wallet/api/interactor/parallel.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 interactor 17 18 import ( 19 "context" 20 "errors" 21 "time" 22 23 "code.vegaprotocol.io/vega/wallet/api" 24 ) 25 26 var ErrTooManyRequests = errors.New("there are too many requests") 27 28 // ParallelInteractor is built to handle multiple requests at a time. 29 type ParallelInteractor struct { 30 // userCtx is the context used to listen to the user-side cancellation 31 // requests. It interrupts the wait on responses. 32 userCtx context.Context 33 34 outboundCh chan<- Interaction 35 } 36 37 func (i *ParallelInteractor) NotifyInteractionSessionBegan(_ context.Context, traceID string, workflow api.WorkflowType, numberOfSteps uint8) error { 38 interaction := Interaction{ 39 TraceID: traceID, 40 Name: InteractionSessionBeganName, 41 Data: InteractionSessionBegan{ 42 Workflow: string(workflow), 43 MaximumNumberOfSteps: numberOfSteps, 44 }, 45 } 46 47 select { 48 case i.outboundCh <- interaction: 49 return nil 50 default: 51 return ErrTooManyRequests 52 } 53 } 54 55 func (i *ParallelInteractor) NotifyInteractionSessionEnded(_ context.Context, traceID string) { 56 i.outboundCh <- Interaction{ 57 TraceID: traceID, 58 Name: InteractionSessionEndedName, 59 Data: InteractionSessionEnded{}, 60 } 61 } 62 63 func (i *ParallelInteractor) NotifyError(ctx context.Context, traceID string, t api.ErrorType, err error) { 64 if err := ctx.Err(); err != nil { 65 return 66 } 67 68 i.outboundCh <- Interaction{ 69 TraceID: traceID, 70 Name: ErrorOccurredName, 71 Data: ErrorOccurred{ 72 Type: string(t), 73 Error: err.Error(), 74 }, 75 } 76 } 77 78 func (i *ParallelInteractor) NotifySuccessfulTransaction(ctx context.Context, traceID string, stepNumber uint8, txHash, deserializedInputData, tx string, sentAt time.Time, host string) { 79 if err := ctx.Err(); err != nil { 80 return 81 } 82 83 i.outboundCh <- Interaction{ 84 TraceID: traceID, 85 Name: TransactionSucceededName, 86 Data: TransactionSucceeded{ 87 DeserializedInputData: deserializedInputData, 88 TxHash: txHash, 89 Tx: tx, 90 SentAt: sentAt, 91 Node: SelectedNode{ 92 Host: host, 93 }, 94 StepNumber: stepNumber, 95 }, 96 } 97 } 98 99 func (i *ParallelInteractor) NotifyFailedTransaction(ctx context.Context, traceID string, stepNumber uint8, deserializedInputData, tx string, err error, sentAt time.Time, host string) { 100 if err := ctx.Err(); err != nil { 101 return 102 } 103 104 i.outboundCh <- Interaction{ 105 TraceID: traceID, 106 Name: TransactionFailedName, 107 Data: TransactionFailed{ 108 DeserializedInputData: deserializedInputData, 109 Tx: tx, 110 Error: err, 111 SentAt: sentAt, 112 Node: SelectedNode{ 113 Host: host, 114 }, 115 StepNumber: stepNumber, 116 }, 117 } 118 } 119 120 func (i *ParallelInteractor) NotifySuccessfulRequest(ctx context.Context, traceID string, stepNumber uint8, message string) { 121 if err := ctx.Err(); err != nil { 122 return 123 } 124 125 i.outboundCh <- Interaction{ 126 TraceID: traceID, 127 Name: RequestSucceededName, 128 Data: RequestSucceeded{ 129 Message: message, 130 StepNumber: stepNumber, 131 }, 132 } 133 } 134 135 func (i *ParallelInteractor) Log(ctx context.Context, traceID string, t api.LogType, msg string) { 136 if err := ctx.Err(); err != nil { 137 return 138 } 139 140 i.outboundCh <- Interaction{ 141 TraceID: traceID, 142 Name: LogName, 143 Data: Log{ 144 Type: string(t), 145 Message: msg, 146 }, 147 } 148 } 149 150 func (i *ParallelInteractor) RequestWalletConnectionReview(ctx context.Context, traceID string, stepNumber uint8, hostname string) (string, error) { 151 if err := ctx.Err(); err != nil { 152 return "", api.ErrRequestInterrupted 153 } 154 155 responseCh := make(chan Interaction, 1) 156 defer close(responseCh) 157 158 controlCh := make(chan error, 1) 159 defer close(controlCh) 160 161 i.outboundCh <- Interaction{ 162 TraceID: traceID, 163 Name: RequestWalletConnectionReviewName, 164 Data: RequestWalletConnectionReview{ 165 Hostname: hostname, 166 StepNumber: stepNumber, 167 ResponseCh: responseCh, 168 ControlCh: controlCh, 169 }, 170 } 171 172 interaction, err := i.waitForResponse(ctx, traceID, WalletConnectionDecisionName, responseCh, controlCh) 173 if err != nil { 174 return "", err 175 } 176 177 decision, ok := interaction.Data.(WalletConnectionDecision) 178 if !ok { 179 return "", InvalidResponsePayloadError(WalletConnectionDecisionName) 180 } 181 182 return decision.ConnectionApproval, nil 183 } 184 185 func (i *ParallelInteractor) RequestWalletSelection(ctx context.Context, traceID string, stepNumber uint8, hostname string, availableWallets []string) (string, error) { 186 if err := ctx.Err(); err != nil { 187 return "", api.ErrRequestInterrupted 188 } 189 190 responseCh := make(chan Interaction, 1) 191 defer close(responseCh) 192 193 controlCh := make(chan error, 1) 194 defer close(controlCh) 195 196 i.outboundCh <- Interaction{ 197 TraceID: traceID, 198 Name: RequestWalletSelectionName, 199 Data: RequestWalletSelection{ 200 Hostname: hostname, 201 AvailableWallets: availableWallets, 202 StepNumber: stepNumber, 203 ResponseCh: responseCh, 204 ControlCh: controlCh, 205 }, 206 } 207 208 interaction, err := i.waitForResponse(ctx, traceID, SelectedWalletName, responseCh, controlCh) 209 if err != nil { 210 return "", err 211 } 212 213 selectedWallet, ok := interaction.Data.(SelectedWallet) 214 if !ok { 215 return "", InvalidResponsePayloadError(SelectedWalletName) 216 } 217 218 return selectedWallet.Wallet, nil 219 } 220 221 func (i *ParallelInteractor) RequestPassphrase(ctx context.Context, traceID string, stepNumber uint8, wallet, reason string) (string, error) { 222 if err := ctx.Err(); err != nil { 223 return "", api.ErrRequestInterrupted 224 } 225 226 responseCh := make(chan Interaction, 1) 227 defer close(responseCh) 228 229 controlCh := make(chan error, 1) 230 defer close(controlCh) 231 232 i.outboundCh <- Interaction{ 233 TraceID: traceID, 234 Name: RequestPassphraseName, 235 Data: RequestPassphrase{ 236 Wallet: wallet, 237 Reason: reason, 238 StepNumber: stepNumber, 239 ResponseCh: responseCh, 240 ControlCh: controlCh, 241 }, 242 } 243 244 interaction, err := i.waitForResponse(ctx, traceID, EnteredPassphraseName, responseCh, controlCh) 245 if err != nil { 246 return "", err 247 } 248 249 enteredPassphrase, ok := interaction.Data.(EnteredPassphrase) 250 if !ok { 251 return "", InvalidResponsePayloadError(EnteredPassphraseName) 252 } 253 return enteredPassphrase.Passphrase, nil 254 } 255 256 func (i *ParallelInteractor) RequestPermissionsReview(ctx context.Context, traceID string, stepNumber uint8, hostname, wallet string, perms map[string]string) (bool, error) { 257 if err := ctx.Err(); err != nil { 258 return false, api.ErrRequestInterrupted 259 } 260 261 responseCh := make(chan Interaction, 1) 262 defer close(responseCh) 263 264 controlCh := make(chan error, 1) 265 defer close(controlCh) 266 267 i.outboundCh <- Interaction{ 268 TraceID: traceID, 269 Name: RequestPermissionsReviewName, 270 Data: RequestPermissionsReview{ 271 Hostname: hostname, 272 Wallet: wallet, 273 Permissions: perms, 274 StepNumber: stepNumber, 275 ResponseCh: responseCh, 276 ControlCh: controlCh, 277 }, 278 } 279 280 interaction, err := i.waitForResponse(ctx, traceID, DecisionName, responseCh, controlCh) 281 if err != nil { 282 return false, err 283 } 284 285 approval, ok := interaction.Data.(Decision) 286 if !ok { 287 return false, InvalidResponsePayloadError(DecisionName) 288 } 289 return approval.Approved, nil 290 } 291 292 func (i *ParallelInteractor) RequestTransactionReviewForSending(ctx context.Context, traceID string, stepNumber uint8, hostname, wallet, pubKey, transaction string, receivedAt time.Time) (bool, error) { 293 if err := ctx.Err(); err != nil { 294 return false, api.ErrRequestInterrupted 295 } 296 297 responseCh := make(chan Interaction, 1) 298 defer close(responseCh) 299 300 controlCh := make(chan error, 1) 301 defer close(controlCh) 302 303 i.outboundCh <- Interaction{ 304 TraceID: traceID, 305 Name: RequestTransactionReviewForSendingName, 306 Data: RequestTransactionReviewForSending{ 307 Hostname: hostname, 308 Wallet: wallet, 309 PublicKey: pubKey, 310 Transaction: transaction, 311 ReceivedAt: receivedAt, 312 StepNumber: stepNumber, 313 ResponseCh: responseCh, 314 ControlCh: controlCh, 315 }, 316 } 317 318 interaction, err := i.waitForResponse(ctx, traceID, DecisionName, responseCh, controlCh) 319 if err != nil { 320 return false, err 321 } 322 323 approval, ok := interaction.Data.(Decision) 324 if !ok { 325 return false, InvalidResponsePayloadError(DecisionName) 326 } 327 return approval.Approved, nil 328 } 329 330 func (i *ParallelInteractor) RequestTransactionReviewForSigning(ctx context.Context, traceID string, stepNumber uint8, hostname, wallet, pubKey, transaction string, receivedAt time.Time) (bool, error) { 331 if err := ctx.Err(); err != nil { 332 return false, api.ErrRequestInterrupted 333 } 334 335 responseCh := make(chan Interaction, 1) 336 defer close(responseCh) 337 338 controlCh := make(chan error, 1) 339 defer close(controlCh) 340 341 i.outboundCh <- Interaction{ 342 TraceID: traceID, 343 Name: RequestTransactionReviewForSigningName, 344 Data: RequestTransactionReviewForSigning{ 345 Hostname: hostname, 346 Wallet: wallet, 347 PublicKey: pubKey, 348 Transaction: transaction, 349 ReceivedAt: receivedAt, 350 StepNumber: stepNumber, 351 ResponseCh: responseCh, 352 ControlCh: controlCh, 353 }, 354 } 355 356 interaction, err := i.waitForResponse(ctx, traceID, DecisionName, responseCh, controlCh) 357 if err != nil { 358 return false, err 359 } 360 361 approval, ok := interaction.Data.(Decision) 362 if !ok { 363 return false, InvalidResponsePayloadError(DecisionName) 364 } 365 return approval.Approved, nil 366 } 367 368 func (i *ParallelInteractor) RequestTransactionReviewForChecking(ctx context.Context, traceID string, stepNumber uint8, hostname, wallet, pubKey, transaction string, receivedAt time.Time) (bool, error) { 369 if err := ctx.Err(); err != nil { 370 return false, api.ErrRequestInterrupted 371 } 372 373 responseCh := make(chan Interaction, 1) 374 defer close(responseCh) 375 376 controlCh := make(chan error, 1) 377 defer close(controlCh) 378 379 i.outboundCh <- Interaction{ 380 TraceID: traceID, 381 Name: RequestTransactionReviewForCheckingName, 382 Data: RequestTransactionReviewForChecking{ 383 Hostname: hostname, 384 Wallet: wallet, 385 PublicKey: pubKey, 386 Transaction: transaction, 387 ReceivedAt: receivedAt, 388 StepNumber: stepNumber, 389 ResponseCh: responseCh, 390 ControlCh: controlCh, 391 }, 392 } 393 394 interaction, err := i.waitForResponse(ctx, traceID, DecisionName, responseCh, controlCh) 395 if err != nil { 396 return false, err 397 } 398 399 approval, ok := interaction.Data.(Decision) 400 if !ok { 401 return false, InvalidResponsePayloadError(DecisionName) 402 } 403 return approval.Approved, nil 404 } 405 406 func (i *ParallelInteractor) waitForResponse(ctx context.Context, traceID string, expectedResponseName InteractionName, responseCh <-chan Interaction, controlCh chan<- error) (Interaction, error) { 407 var response Interaction 408 running := true 409 for running { 410 select { 411 case <-ctx.Done(): 412 controlCh <- api.ErrRequestInterrupted 413 return Interaction{}, api.ErrRequestInterrupted 414 case <-i.userCtx.Done(): 415 return Interaction{}, api.ErrUserCloseTheConnection 416 case r := <-responseCh: 417 response = r 418 running = false 419 } 420 } 421 422 if response.TraceID != traceID { 423 return Interaction{}, TraceIDMismatchError(traceID, response.TraceID) 424 } 425 426 if response.Name == CancelRequestName { 427 return Interaction{}, api.ErrUserCancelledTheRequest 428 } 429 430 if response.Name != expectedResponseName { 431 return Interaction{}, WrongResponseTypeError(expectedResponseName, response.Name) 432 } 433 434 return response, nil 435 } 436 437 func NewParallelInteractor(userCtx context.Context, outboundCh chan<- Interaction) *ParallelInteractor { 438 i := &ParallelInteractor{ 439 userCtx: userCtx, 440 outboundCh: outboundCh, 441 } 442 443 return i 444 }