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  }