Beispiel #1
0
        public async Task <OperationStatusUpdateResultModel> AcceptBatchAsync(Dictionary <Guid, string> operationsHashesDict)
        {
            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var operationRequestsDict = await _operationRequestsRepository.GetByIdsAsync(operationsHashesDict.Keys);
                var notInRequests = operationsHashesDict.Keys.Where(k => !operationRequestsDict.ContainsKey(k)).ToHashSet();

                await _operationRequestsRepository.AcceptBatchAsync(operationRequestsDict.Values, operationsHashesDict);

                if (notInRequests.Any())
                {
                    var operationsIds = await _operationsRepository.GetExistingIdsAsync(notInRequests);
                    foreach (var operationId in operationsIds)
                    {
                        notInRequests.Remove(operationId);
                    }
                    if (notInRequests.Any())
                    {
                        _log.Warning("Operation request not found by id", context: new { missingIds = notInRequests });
                        return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound);
                    }
                }

                _log.Info($"Accepted {operationRequestsDict.Count} operationss", new { operationIds = operationRequestsDict.Keys });

                return OperationStatusUpdateResultModel.Succeeded();
            }));
        }
Beispiel #2
0
        public async Task <OperationStatusUpdateResultModel> AcceptAsync(Guid id, string hash)
        {
            if (Guid.Empty == id)
            {
                _log.Warning("Operation id is empty");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound));
            }

            if (string.IsNullOrEmpty(hash))
            {
                _log.Warning("Transaction hash is empty");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidTransactionHash));
            }

            if (!hash.IsValidTransactionHash())
            {
                _log.Warning("Transaction hash has invalid format");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidTransactionHash));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var operationRequest = await _operationRequestsRepository.GetByIdAsync(id);
                if (operationRequest == null)
                {
                    var operation = await _operationsRepository.GetByIdAsync(id);
                    if (operation == null)
                    {
                        _log.Warning("Operation request not found by id", context: id);
                        return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound);
                    }

                    if (operation.Status == OperationStatus.Accepted)
                    {
                        _log.Warning("Operation has already been accepted", context: id);
                    }

                    return OperationStatusUpdateResultModel.Succeeded();
                }

                if (operationRequest.Status != OperationStatus.Created)
                {
                    _log.Error(message: "Current operation request status is not expected",
                               context: new { id = operationRequest.Id, currentStatus = operationRequest.Status.ToString() });
                    return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidStatus);
                }

                await _operationRequestsRepository.AcceptAsync(
                    operationRequest.Id,
                    operationRequest.CustomerId,
                    operationRequest.Nonce,
                    operationRequest.MasterWalletAddress,
                    operationRequest.Type,
                    operationRequest.ContextJson,
                    operationRequest.CreatedAt,
                    hash);

                return OperationStatusUpdateResultModel.Succeeded();
            }));
        }
        public async Task HandleAsync_ValidInput_Parameters_OperationUpdateGetsCalled(string hash)
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            var sut = CreateSutInstance();

            await sut.HandleAsync(hash);

            _operationStatusUpdaterMock.Verify(
                m => m.FailAsync(It.IsAny <string>()),
                Times.Once);
        }
Beispiel #4
0
        // todo: merge with SyncWithBlockchain into single method like EnsureSucceededAsync, no need to have both
        public async Task <OperationStatusUpdateResultModel> SucceedAsync(string hash)
        {
            if (string.IsNullOrEmpty(hash))
            {
                _log.Warning("Operation hash is empty");
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound));
            }

            return(await _transactionScopeHandler.WithTransactionAsync(async() =>
            {
                var operation = await _operationsRepository.GetByHashAsync(hash);
                if (operation == null)
                {
                    _log.Warning("Operation not found by hash", context: hash);
                    return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound);
                }

                if (operation.Status == OperationStatus.Succeeded)
                {
                    _log.Warning("Operation is already succeeded", context: hash);
                    return OperationStatusUpdateResultModel.Succeeded();
                }

                switch (operation.Status)
                {
                case OperationStatus.Accepted:
                    await _operationsRepository.SetStatusAsync(operation.Id, OperationStatus.Succeeded);
                    _log.Info("Operation succeeded", new { operationId = operation.Id });
                    break;

                case OperationStatus.Created:
                case OperationStatus.Failed:
                case OperationStatus.Succeeded:
                    _log.Warning("Operation status can't be updated", context:
                                 new
                    {
                        hash,
                        currentStatus = operation.Status.ToString(),
                        desiredStatus = OperationStatus.Succeeded.ToString()
                    });
                    return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidStatus);

                default:
                    _log.Error(message: "Current operation status is not expected",
                               context: new { hash, currentStatus = operation.Status.ToString() });
                    return OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.InvalidStatus);
                }

                return OperationStatusUpdateResultModel.Succeeded();
            }));
        }
        public async Task HandleAsync_AlreadyMarkedAsFailedOperationNotFoundInDb_WalletOwnersRepoNotCalled()
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(It.IsAny <string>()))
            .ReturnsAsync((IOperation)null);

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _walletOwnersRepoMock.Verify(x => x.GetByWalletAddressAsync(It.IsAny <string>()), Times.Never);
        }
        public async Task HandleAsync_OperationNotFoundAfterItWasUpdatedToSucceeded_PublisherNotCalled()
        {
            _operationStatusUpdaterMock.Setup(x => x.SucceedAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(ValidTransactionHash))
            .ReturnsAsync((IOperation)null);

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _transcationSucceededPublisherMock.Verify(
                m => m.PublishAsync(It.IsAny <TransactionSucceededEvent>()),
                Times.Never);
        }
Beispiel #7
0
        public async Task <OperationStatusUpdateResultModel> SyncWithBlockchainAsync(string hash)
        {
            var result = await _executorClient.TransactionsApi.GetTransactionStateAsync(hash);

            if (result.Error == GetTransactionStateError.TransactionNotFound)
            {
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationNotFound));
            }

            if (!result.OperationId.HasValue)
            {
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.OperationIdIsNull));
            }

            var enumValueExists = Enum.TryParse(typeof(OperationStatus),
                                                result.TransactionState.ToString(), true, out var operationStatus);

            if (!enumValueExists)
            {
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError.UnsupportedOperationStatus));
            }

            try
            {
                // Accepting operation if we haven't accepted it yet
                var acceptResult = await AcceptAsync(result.OperationId.Value, result.TransactionHash);

                if (acceptResult.Error != OperationStatusUpdateError.None)
                {
                    return(acceptResult);
                }

                await _operationsRepository.SetStatusAsync
                    (result.OperationId.Value, (OperationStatus)operationStatus, result.TransactionHash);

                _log.Info("Operation status and hash have been synced with blockchain",
                          new { operationId = result.OperationId.Value.ToString() });
            }
            catch (DuplicateException e)
            {
                _log.Warning("There is already an operation with the same hash", e, e.Key);
                return(OperationStatusUpdateResultModel.Failed(OperationStatusUpdateError
                                                               .DuplicateTransactionHash));
            }

            return(OperationStatusUpdateResultModel.Succeeded());
        }
        public async Task HandleAsync_PublisherGetsCalled()
        {
            _operationStatusUpdaterMock.Setup(x => x.SucceedAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(ValidTransactionHash))
            .ReturnsAsync(new OperationEntity {
                Id = Guid.Parse(FakeOperationId)
            });

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _transcationSucceededPublisherMock.Verify(
                m => m.PublishAsync(It.Is <TransactionSucceededEvent>(e => e.OperationId == FakeOperationId)),
                Times.Once);
        }
        public async Task HandleAsync_OperationIsNotTokensTransfer_WalletOwnersRepoNotCalledAndTransactionFailedPublisherCalled(OperationType operationType)
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(It.IsAny <string>()))
            .ReturnsAsync(new OperationEntity
            {
                Type = operationType
            });

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _walletOwnersRepoMock.Verify(x => x.GetByWalletAddressAsync(It.IsAny <string>()), Times.Never);
            _transactionFailedPublisherMock.Verify(x => x.PublishAsync(It.IsAny <TransactionFailedEvent>()), Times.Once);
        }
        public async Task HandleAsync_SenderIsNotACustomer_P2PFailedPublisherNotCalled()
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _walletOwnersRepoMock.Setup(x => x.GetByWalletAddressAsync(FakeSenderAddress))
            .ReturnsAsync((IWalletOwner)null);

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(It.IsAny <string>()))
            .ReturnsAsync(new OperationEntity
            {
                Type        = OperationType.TokensTransfer,
                ContextJson = FakeTransactionContext
            });

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _p2PFailedPublisherMock.Verify(x => x.PublishAsync(It.IsAny <P2PTransferFailedEvent>()), Times.Never);
        }
        public async Task HandleAsync_PublishedP2PFailedEvent()
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _walletOwnersRepoMock.Setup(x => x.GetByWalletAddressAsync(FakeSenderAddress))
            .ReturnsAsync(new WalletOwnerEntity
            {
                OwnerId  = FakeSenderId,
                WalletId = FakeSenderAddress
            });

            _walletOwnersRepoMock.Setup(x => x.GetByWalletAddressAsync(FakeReceiverAddress))
            .ReturnsAsync(new WalletOwnerEntity
            {
                OwnerId  = FakeReceiverId,
                WalletId = FakeReceiverAddress
            });

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(It.IsAny <string>()))
            .ReturnsAsync(new OperationEntity
            {
                Type            = OperationType.TokensTransfer,
                ContextJson     = FakeTransactionContext,
                TransactionHash = ValidTransactionHash
            });

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _p2PFailedPublisherMock.Verify(x => x.PublishAsync(It.Is <P2PTransferFailedEvent>(
                                                                   obj => obj.RequestId == FakeRequestId &&
                                                                   obj.Amount == ValidAmount &&
                                                                   obj.ReceiverCustomerId == FakeReceiverId &&
                                                                   obj.SenderCustomerId == FakeSenderId &&
                                                                   obj.TransactionHash == ValidTransactionHash)),
                                           Times.Once);
        }
        public async Task HandleAsync_ReceiverIsNotACustomer_P2PFailedPublisherNotCalled()
        {
            _operationStatusUpdaterMock.Setup(x => x.FailAsync(It.IsAny <string>()))
            .ReturnsAsync(OperationStatusUpdateResultModel.Succeeded());

            _walletOwnersRepoMock.Setup(x => x.GetByWalletAddressAsync(FakeSenderAddress))
            .ReturnsAsync(new WalletOwnerEntity
            {
                OwnerId  = FakeSenderId,
                WalletId = FakeSenderAddress
            });

            var transactionContext = new TokensTransferContext()
            {
                SenderWalletAddress    = FakeSenderAddress,
                RecipientWalletAddress = FakeReceiverAddress,
                Amount    = ValidAmount,
                RequestId = FakeRequestId
            };

            var operationContextJson = JsonConvert.SerializeObject(transactionContext);

            _walletOwnersRepoMock.Setup(x => x.GetByWalletAddressAsync(FakeReceiverAddress))
            .ReturnsAsync((IWalletOwner)null);

            _operationsFetcherMock.Setup(x => x.GetByHashAsync(It.IsAny <string>()))
            .ReturnsAsync(new OperationEntity
            {
                Type        = OperationType.TokensTransfer,
                ContextJson = operationContextJson
            });

            var sut = CreateSutInstance();

            await sut.HandleAsync(ValidTransactionHash);

            _p2PFailedPublisherMock.Verify(x => x.PublishAsync(It.IsAny <P2PTransferFailedEvent>()), Times.Never);
        }