Beispiel #1
0
        public async Task <CommandHandlingResult> Handle(FailBatchCommand command, IEventPublisher publisher)
        {
            var batch = await _cashoutsBatchRepository.GetAsync(command.BatchId);

            var transitionResult = batch.Fail();

            if (transitionResult.ShouldSaveAggregate())
            {
                await _cashoutsBatchRepository.SaveAsync(batch);

                _chaosKitty.Meow(batch.BatchId);
            }

            if (transitionResult.ShouldPublishEvents())
            {
                if (!batch.FinishMoment.HasValue)
                {
                    throw new InvalidOperationException("Finish moment should be not null here");
                }

                publisher.PublishEvent
                (
                    new CashoutsBatchFailedEvent
                {
                    BatchId  = batch.BatchId,
                    AssetId  = batch.AssetId,
                    Cashouts = batch.Cashouts
                               .Select(c => c.ToContract())
                               .ToArray(),
                    ErrorCode    = command.ErrorCode.ToContract(),
                    Error        = command.Error,
                    StartMoment  = batch.StartMoment,
                    FinishMoment = batch.FinishMoment.Value
                }
                );
            }

            return(CommandHandlingResult.Ok());
        }
        public async Task<CommandHandlingResult> Handle(RegisterEmployeeCommand command, IEventPublisher publisher)
        {
            Employee employee;

            try
            {
                employee = await _employeeService.AddAsync(Mapper.Map<Employee>(command));
            }
            catch (Exception e)
            {
                if (e is MerchantNotFoundException merchantEx)
                    _log.WarningWithDetails(merchantEx.Message, new {merchantEx.MerchantId});

                if (e is EmployeeExistException employeeEx)
                    _log.WarningWithDetails(employeeEx.Message, new { command.Email });

                publisher.PublishEvent(new EmployeeRegistrationFailedEvent
                {
                    Email = command.Email,
                    Error = e.Message
                });

                _chaosKitty.Meow("Issue with RabbitMq publishing EmployeeRegistrationFailedEvent");

                return CommandHandlingResult.Ok();
            }

            publisher.PublishEvent(new EmployeeRegisteredEvent
            {
                Id = employee.Id,
                Email = employee.Email,
                MerchantId = employee.MerchantId,
                Password = command.Password
            });

            _chaosKitty.Meow("Issue with RabbitMq publishing EmployeeRegisteredEvent");

            return CommandHandlingResult.Ok();
        }
        public async Task <CommandHandlingResult> Handle(SignTransactionCommand command, IEventPublisher publisher)
        {
            var alreadyPublishedEvt = await _commandHandlerEventRepository.TryGetEventAsync(command.TransactionId, CommandHandlerId);

            if (alreadyPublishedEvt != null)
            {
                publisher.PublishEvent(alreadyPublishedEvt);

                return(CommandHandlingResult.Ok());
            }

            var transactionSigningResult = await _signFacadeClient.SignTransactionAsync
                                           (
                blockchainType : command.BlockchainType,
                request : new SignTransactionRequest
            {
                PublicAddresses    = new[] { command.SignerAddress },
                TransactionContext = command.TransactionContext
            }
                                           );

            _chaosKitty.Meow(command.TransactionId);

            if (string.IsNullOrWhiteSpace(transactionSigningResult?.SignedTransaction))
            {
                throw new InvalidOperationException("Sign service returned the empty transaction");
            }

            publisher.PublishEvent(await _commandHandlerEventRepository.InsertEventAsync(command.TransactionId,
                                                                                         CommandHandlerId,
                                                                                         new TransactionSignedEvent
            {
                OperationId       = command.OperationId,
                TransactionId     = command.TransactionId,
                SignedTransaction = transactionSigningResult.SignedTransaction
            }));

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(CreateTransferCommand createTransferCommand, IEventPublisher eventPublisher)
        {
            if (await _antiFraudChecker.IsPaymentSuspicious(createTransferCommand.ClientId, createTransferCommand.OrderId))
            {
                return(new CommandHandlingResult {
                    Retry = true, RetryDelay = (long)TimeSpan.FromMinutes(10).TotalMilliseconds
                });
            }

            var bankCardFees = await _feeCalculatorClient.GetBankCardFees();

            var result = await _exchangeOperationsService.TransferWithNotificationAsync(
                transferId : createTransferCommand.TransferId,
                destClientId : createTransferCommand.ClientId,
                sourceClientId : createTransferCommand.SourceClientId,
                amount : createTransferCommand.Amount,
                assetId : createTransferCommand.AssetId,
                feeClientId : _bankCardFeeClientId,
                feeSizePercentage : bankCardFees.Percentage,
                destWalletId : createTransferCommand.WalletId);

            if (!result.IsOk())
            {
                await _paymentTransactionEventsLog.WriteAsync(PaymentTransactionLogEvent.Create(createTransferCommand.OrderId, "N/A", $"{result.Code}:{result.Message}", nameof(CreateTransferCommand)));

                return(CommandHandlingResult.Ok());
            }

            eventPublisher.PublishEvent(new TransferCreatedEvent
            {
                OrderId    = createTransferCommand.OrderId,
                TransferId = createTransferCommand.TransferId,
                ClientId   = createTransferCommand.ClientId,
                Amount     = createTransferCommand.Amount,
                AssetId    = createTransferCommand.AssetId
            });

            return(CommandHandlingResult.Ok());
        }
Beispiel #5
0
        public async Task <CommandHandlingResult> Handle(EndTransactionHistoryMonitoringCommand command, IEventPublisher publisher)
        {
            var apiClient = _blockchainIntegrationService.TryGetApiClient(command.BlockchainType);

            if (apiClient == null)
            {
                throw new NotSupportedException($"Blockchain type [{command.BlockchainType}] is not supportedю");
            }


            const MonitoringSubscriptionType subscriptionType = MonitoringSubscriptionType.TransactionHistory;

            var address        = command.Address;
            var blockchainType = command.BlockchainType;


            await _monitoringSubscriptionRepository.UnregisterWalletSubscriptionAsync
            (
                blockchainType : blockchainType,
                address : address,
                subscriptionType : subscriptionType
            );

            // TODO: Fix potential issue with subscription/unsubscription race conditions
            // If we have no subscriptions for address-asset pairs for specified address...
            if (await _monitoringSubscriptionRepository.WalletSubscriptionsCount(blockchainType, address, subscriptionType) == 0)
            {
                try
                {
                    // Unsubscribe from address transactions observation (for all assets)
                    await apiClient.StopHistoryObservationOfIncomingTransactionsAsync(address);
                }
                catch (ErrorResponseException e) when(e.StatusCode == HttpStatusCode.NoContent)
                {
                }
            }

            return(CommandHandlingResult.Ok());
        }
Beispiel #6
0
        public async Task EventsInterceptor__CancellationIsNotRequested__FlowIsCompleted()
        {
            var @event = PrepareServicesForInterception(out var operationId,
                                                        out var messageCancellationService,
                                                        out var messageCancellationRegistry,
                                                        out var logFactory);
            await messageCancellationService.RemoveMessageFromCancellationAsync(operationId);

            var messageCancellationCommandInterceptor =
                new MessageCancellationEventInterceptor(
                    messageCancellationService,
                    messageCancellationRegistry,
                    logFactory.Object);
            var interceptionContext = new Mock <IEventInterceptionContext>();

            interceptionContext.Setup(x => x.Event).Returns(@event);
            interceptionContext.Setup(x => x.HandlerObject).Returns(this);
            interceptionContext.Setup(x => x.InvokeNextAsync())
            .Returns(Task.FromResult(CommandHandlingResult.Fail(TimeSpan.Zero)));
            var result = await messageCancellationCommandInterceptor.InterceptAsync(interceptionContext.Object);

            Assert.True(result.Retry);
        }
        public async Task <CommandHandlingResult> Handle(RemoveEntryFromHistoryJobCommand command,
                                                         IEventPublisher eventPublisher)
        {
            try
            {
                eventPublisher.PublishEvent(new CashInRemovedFromHistoryJobEvent
                {
                    Id       = command.Id,
                    ClientId = command.ClientId,
                    AssetId  = command.AssetId,
                    Amount   = command.Amount,
                    CashInId = command.CashInId
                });

                return(CommandHandlingResult.Ok());
            }
            catch (Exception e)
            {
                _log.Error(e, context: command.ClientId);

                return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(30)));
            }
        }
Beispiel #8
0
        private async Task <CommandHandlingResult> Handle(GetPriceForSpecialLiquidationTimeoutInternalCommand command,
                                                          IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <SpecialLiquidationOperationData>(
                operationName : SpecialLiquidationSaga.OperationName,
                id : command.OperationId);

            if (executionInfo?.Data != null)
            {
                if (executionInfo.Data.State > SpecialLiquidationOperationState.PriceRequested)
                {
                    return(CommandHandlingResult.Ok());
                }

                if (_dateService.Now() >= command.CreationTime.AddSeconds(command.TimeoutSeconds))
                {
                    if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceRequested,
                                                       SpecialLiquidationOperationState.Failed))
                    {
                        publisher.PublishEvent(new SpecialLiquidationFailedEvent
                        {
                            OperationId  = command.OperationId,
                            CreationTime = _dateService.Now(),
                            Reason       = $"Timeout of {command.TimeoutSeconds} seconds from {command.CreationTime:s}",
                        });

                        _chaosKitty.Meow(command.OperationId);

                        await _operationExecutionInfoRepository.Save(executionInfo);
                    }

                    return(CommandHandlingResult.Ok());
                }
            }

            return(CommandHandlingResult.Fail(_marginTradingSettings.SpecialLiquidation.RetryTimeout));
        }
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command,
                                                         IEventPublisher eventPublisher)
        {
            var address = command.Address.Trim('\n', ' ', '\t');

            try
            {
                var transactionId = await _builder.AddTransactionId(command.Id, $"Cashout: {command.ToJson()}");

                if (OpenAssetsHelper.IsBitcoin(command.AssetId))
                {
                    await _cashoutRequestRepository.CreateCashoutRequest(transactionId, command.Amount, address);
                }
                else
                {
                    var assetSetting = await _assetSettingCache.GetItemAsync(command.AssetId);

                    var hotWallet = !string.IsNullOrEmpty(assetSetting.ChangeWallet)
                        ? assetSetting.ChangeWallet
                        : assetSetting.HotWallet;

                    await _transactionQueueWriter.AddCommand(transactionId, TransactionCommandType.Transfer, new TransferCommand
                    {
                        Amount             = command.Amount,
                        SourceAddress      = hotWallet,
                        Asset              = command.AssetId,
                        DestinationAddress = command.Address
                    }.ToJson());
                }
            }
            catch (BackendException ex) when(ex.Code == ErrorCode.DuplicateTransactionId)
            {
                _logger.WriteWarning(nameof(CashinCommandHandler), nameof(Handle), $"Duplicated id: {command.Id}");
            }

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(BroadcastTransactionCommand command, IEventPublisher publisher)
        {
            var broadcastingResult = await _client.BroadcastTransactionAsync(command.TransactionId, command.SignedTransactionContext);

            _chaosKitty.Meow(command.TransactionId);

            switch (broadcastingResult)
            {
            case TransactionBroadcastingResult.Success:

                publisher.PublishEvent(new TransactionBroadcastedEvent
                {
                    TransactionId = command.TransactionId
                });

                return(CommandHandlingResult.Ok());

            case TransactionBroadcastingResult.AlreadyBroadcasted:

                _log.Info("API said that the transaction already was broadcasted", command);

                publisher.PublishEvent(new TransactionBroadcastedEvent
                {
                    TransactionId = command.TransactionId
                });

                return(CommandHandlingResult.Ok());

            default:
                throw new ArgumentOutOfRangeException
                      (
                          nameof(broadcastingResult),
                          $"Transaction broadcastring result [{broadcastingResult}] is not supported."
                      );
            }
        }
Beispiel #11
0
        public async Task <CommandHandlingResult> Handle(StartOneToManyOutputsExecutionCommand command, IEventPublisher publisher)
        {
            var asset = await _assetsService.TryGetAssetAsync(command.AssetId);

            if (asset == null)
            {
                throw new InvalidOperationException("Asset not found");
            }

            if (string.IsNullOrWhiteSpace(asset.BlockchainIntegrationLayerId))
            {
                throw new InvalidOperationException("BlockchainIntegrationLayerId of the asset is not configured");
            }

            if (string.IsNullOrWhiteSpace(asset.BlockchainIntegrationLayerAssetId))
            {
                throw new InvalidOperationException("BlockchainIntegrationLayerAssetId of the asset is not configured");
            }

            publisher.PublishEvent
            (
                new OperationExecutionStartedEvent
            {
                OperationId            = command.OperationId,
                FromAddress            = command.FromAddress,
                Outputs                = command.Outputs,
                BlockchainType         = asset.BlockchainIntegrationLayerId,
                BlockchainAssetId      = asset.BlockchainIntegrationLayerAssetId,
                AssetId                = command.AssetId,
                IncludeFee             = command.IncludeFee,
                EndpointsConfiguration = OperationExecutionEndpointsConfiguration.OneToMany
            }
            );

            return(CommandHandlingResult.Ok());
        }
Beispiel #12
0
        public async Task <CommandHandlingResult> Handle(RetrieveClientCommand command, IEventPublisher publisher)
        {
            // TODO: Add client cache for the walletsClient

            var clientId = await _walletsClient.TryGetClientIdAsync(
                command.BlockchainType,
                command.DepositWalletAddress
                );

            if (clientId == null)
            {
                throw new InvalidOperationException("Client ID for the blockchain deposit wallet address is not found");
            }

            publisher.PublishEvent(new ClientRetrievedEvent
            {
                OperationId = command.OperationId,
                ClientId    = clientId.Value
            });

            _chaosKitty.Meow(command.OperationId);

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(Commands.SaveIssueOperationStateCommand command, IEventPublisher eventPublisher)
        {
            var sw = new Stopwatch();

            sw.Start();

            try
            {
                await SaveState(command.Command, command.Context);

                eventPublisher.PublishEvent(new IssueTransactionStateSavedEvent {
                    Message = command.Message, Command = command.Command
                });

                return(CommandHandlingResult.Ok());
            }
            finally
            {
                sw.Stop();
                _log.Info("Command execution time",
                          context: new { TxHandler = new { Handler = nameof(OperationsCommandHandler), Command = nameof(Commands.SaveIssueOperationStateCommand),
                                                           Time    = sw.ElapsedMilliseconds } });
            }
        }
Beispiel #14
0
        // entry point
        public async Task <CommandHandlingResult> Handle(ProcessTransactionCommand command, IEventPublisher eventPublisher)
        {
            var confirmations = (await _qBitNinjaApiCaller.GetTransaction(command.TransactionHash))?.Block?.Confirmations;

            var isConfirmed = confirmations >= _settings.TxDetectorConfirmationsLimit;

            if (!isConfirmed)
            {
                //put back if not confirmed yet
                return(new CommandHandlingResult {
                    Retry = true, RetryDelay = (long)RetryTimeoutForTransactionConfirmations.TotalMilliseconds
                });
            }

            ChaosKitty.Meow();

            var hash = command.TransactionHash;
            var balanceChangeTransactions = await _balanceChangeTransactionsRepository.GetAsync(hash);

            foreach (var tx in balanceChangeTransactions)
            {
                var alreadyProcessed = !await _confirmedTransactionsRepository.SaveConfirmedIfNotExist(hash, tx.ClientId);

                if (alreadyProcessed)
                {
                    _log.WriteInfo(nameof(ProcessTransactionCommand), command,
                                   $"Transaction with hash {hash} for client {tx.ClientId} is already processed; ignoring it.");
                    continue;
                }

                eventPublisher.PublishEvent(new ConfirmationSavedEvent {
                    TransactionHash = hash, ClientId = tx.ClientId, Multisig = tx.Multisig
                });
            }
            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(WaitForTransactionEndingCommand command, IEventPublisher publisher)
        {
            var apiClient = _apiClientProvider.Get(command.BlockchainType);

            // TODO: Cache it

            var blockchainAsset = await apiClient.GetAssetAsync(command.BlockchainAssetId);

            BaseBroadcastedTransaction transaction;

            OperationOutput[] transactionOutputs = null;

            if (command.Outputs.Length > 1)
            {
                var manyOutputsTransaction = await apiClient.TryGetBroadcastedTransactionWithManyOutputsAsync
                                             (
                    command.TransactionId,
                    blockchainAsset
                                             );

                transaction = manyOutputsTransaction;

                if (manyOutputsTransaction != null)
                {
                    transactionOutputs = manyOutputsTransaction.Outputs
                                         .Select(o => new OperationOutput
                    {
                        Address = o.ToAddress,
                        Amount  = o.Amount
                    })
                                         .ToArray();
                }
            }
            else if (command.Outputs.Length == 1)
            {
                var singleTransaction = await apiClient.TryGetBroadcastedSingleTransactionAsync
                                        (
                    command.TransactionId,
                    blockchainAsset
                                        );

                transaction = singleTransaction;

                if (singleTransaction != null)
                {
                    transactionOutputs = new[]
                    {
                        new OperationOutput
                        {
                            Address = command.Outputs.Single().Address,
                            Amount  = singleTransaction.Amount
                        }
                    };
                }
            }
            else
            {
                throw new InvalidOperationException("There should be at least one output");
            }

            if (transaction == null)
            {
                _log.Info("Blockchain API returned no transaction. Assuming, that it's already was cleared", command);

                // Transaction already has been forgotten, this means,
                // that process has been went further and no events should be generated here.

                return(CommandHandlingResult.Ok());
            }

            if (transactionOutputs == null)
            {
                throw new InvalidOperationException("Transaction outputs should be not null here");
            }

            switch (transaction.State)
            {
            case BroadcastedTransactionState.InProgress:

                return(CommandHandlingResult.Fail(_delayProvider.WaitForTransactionRetryDelay));

            case BroadcastedTransactionState.Completed:

                publisher.PublishEvent(new TransactionExecutionCompletedEvent
                {
                    OperationId        = command.OperationId,
                    TransactionId      = command.TransactionId,
                    TransactionNumber  = command.TransactionNumber,
                    TransactionHash    = transaction.Hash,
                    TransactionOutputs = transactionOutputs,
                    TransactionFee     = transaction.Fee,
                    TransactionBlock   = transaction.Block
                });

                return(CommandHandlingResult.Ok());

            case BroadcastedTransactionState.Failed:

                if (transaction.ErrorCode == BlockchainErrorCode.NotEnoughBalance ||
                    transaction.ErrorCode == BlockchainErrorCode.BuildingShouldBeRepeated)
                {
                    publisher.PublishEvent(new TransactionExecutionRepeatRequestedEvent
                    {
                        OperationId       = command.OperationId,
                        TransactionId     = command.TransactionId,
                        TransactionNumber = command.TransactionNumber,
                        ErrorCode         = transaction.ErrorCode.Value.MapToTransactionExecutionResult(),
                        Error             = transaction.Error
                    });
                }
                else
                {
                    publisher.PublishEvent(new TransactionExecutionFailedEvent
                    {
                        OperationId       = command.OperationId,
                        TransactionId     = command.TransactionId,
                        TransactionNumber = command.TransactionNumber,
                        ErrorCode         = transaction.ErrorCode?.MapToTransactionExecutionResult() ?? TransactionExecutionResult.UnknownError,
                        Error             = transaction.Error
                    });
                }

                return(CommandHandlingResult.Ok());

            default:
                throw new ArgumentOutOfRangeException
                      (
                          nameof(transaction.State),
                          $"Transaction state [{transaction.State}] is not supported."
                      );
            }
        }
Beispiel #16
0
        public async Task <CommandHandlingResult> Handle(TransferEthereumCommand command, IEventPublisher eventPublisher)
        {
            var sw = new Stopwatch();

            sw.Start();

            try
            {
                var txRequest = await _ethereumTransactionRequestRepository.GetAsync(command.TransactionId);

                // todo: udpate txRequest in separated command
                var context = await _transactionService.GetTransactionContext <TransferContextData>(command.TransactionId.ToString());

                txRequest.OperationIds = new[] { context.Transfers[0].OperationId, context.Transfers[1].OperationId };
                await _ethereumTransactionRequestRepository.UpdateAsync(txRequest);

                ChaosKitty.Meow();

                var clientAddress = await _bcnClientCredentialsRepository.GetClientAddress(txRequest.ClientId);

                var hotWalletAddress = _settings.HotwalletAddress;

                string addressFrom;
                string addressTo;
                Guid   transferId;
                string sign;
                switch (txRequest.OperationType)
                {
                case OperationType.TransferToTrusted:
                    addressFrom = clientAddress;
                    addressTo   = hotWalletAddress;
                    transferId  = txRequest.SignedTransfer.Id;
                    sign        = txRequest.SignedTransfer.Sign;
                    break;

                case OperationType.TransferFromTrusted:
                    addressFrom = hotWalletAddress;
                    addressTo   = clientAddress;
                    transferId  = txRequest.Id;
                    sign        = string.Empty;
                    break;

                case OperationType.TransferBetweenTrusted:
                    return(CommandHandlingResult.Ok());

                default:
                    _log.Error(nameof(TransferEthereumCommand), message: "Unknown transfer type");
                    return(CommandHandlingResult.Fail(_retryTimeout));
                }

                var asset = await _assetsServiceWithCache.TryGetAssetAsync(txRequest.AssetId);

                var response = await _srvEthereumHelper.SendTransferAsync(transferId, sign, asset, addressFrom,
                                                                          addressTo, txRequest.Volume);

                ChaosKitty.Meow();

                if (response.HasError &&
                    response.Error.ErrorCode != ErrorCode.OperationWithIdAlreadyExists &&
                    response.Error.ErrorCode != ErrorCode.EntityAlreadyExists)
                {
                    var errorMessage = response.Error.ToJson();
                    _log.Error(nameof(TransferEthereumCommand), new Exception(errorMessage));
                    return(CommandHandlingResult.Fail(_retryTimeout));
                }

                eventPublisher.PublishEvent(new EthereumTransferSentEvent {
                    TransferId = transferId
                });

                ChaosKitty.Meow();

                return(CommandHandlingResult.Ok());
            }
            finally
            {
                sw.Stop();
                _log.Info("Command execution time",
                          context: new { TxHandler = new { Handler = nameof(EthereumCommandHandler), Command = nameof(TransferEthereumCommand),
                                                           Time    = sw.ElapsedMilliseconds } });
            }
        }
Beispiel #17
0
 private static ICommandHandlingResult ValidationFail(string argumentName)
 {
     return(CommandHandlingResult.Fail($"Incorrect value for {argumentName}."));
 }
Beispiel #18
0
        public async Task <CommandHandlingResult> Handle(RegisterOperationStatisticsCommand command, IEventPublisher publisher)
        {
            await _statisticsService.RegisterStatisticsAsync(command.OperationId);

            return(CommandHandlingResult.Ok());
        }
Beispiel #19
0
        public async Task <CommandHandlingResult> Handle(GenerateActiveTransactionIdCommand command, IEventPublisher publisher)
        {
            if (command.IsCashout &&
                command.ActiveTransactioNumber > 0)     // First transaction executes always without manual confirmation
            {
                var aggregate = await _repository.GetAsync(command.OperationId);

                var loggingContext = new
                {
                    aggregate.OperationId,
                    aggregateActiveTransactionNumber = aggregate.ActiveTransactionNumber,
                    commandActiveTransactionNumber   = command.ActiveTransactioNumber
                };

                if (aggregate.ActiveTransactionNumber > command.ActiveTransactioNumber)
                {
                    _log.Warning("Command already handled. " +
                                 "Do nothing",
                                 context: loggingContext);

                    return(CommandHandlingResult.Ok());
                }

                if (aggregate.IsFinished)
                {
                    _log.Warning("Operation already finished. " +
                                 "Do nothing",
                                 context: loggingContext);

                    return(CommandHandlingResult.Ok());
                }


                switch (aggregate.RebuildConfirmationResult)
                {
                case RebuildConfirmationResult.Unconfirmed:
                {
                    _log.Warning("Transaction rebuild manual confirmation required. " +
                                 "RebuildConfirmationResult will checked later again",
                                 context: loggingContext);

                    return(CommandHandlingResult.Fail(_retryDelayProvider.RebuildingConfirmationCheckRetryDelay));
                }

                case RebuildConfirmationResult.Accepted:
                {
                    _log.Warning("Transaction rebuild manual confirmation granted. " +
                                 "Transaction rebuilding started",
                                 context: loggingContext);

                    break;
                }

                case RebuildConfirmationResult.Rejected:
                {
                    _log.Warning("Transaction rebuild manual confirmation rejected. " +
                                 "Transaction rebuilding rejected",
                                 context: loggingContext);


                    publisher.PublishEvent(new TransactionReBuildingRejectedEvent
                        {
                            OperationId = command.OperationId
                        });

                    return(CommandHandlingResult.Ok());
                }

                default:
                {
                    throw new ArgumentException($"Unknown switch {aggregate.RebuildConfirmationResult}. " +
                                                "This should not happen",
                                                nameof(aggregate.RebuildConfirmationResult));
                }
                }
            }

            var activeTransactionId = await _activeTransactionsRepository.GetOrStartTransactionAsync(
                command.OperationId,
                Guid.NewGuid);

            publisher.PublishEvent(new ActiveTransactionIdGeneratedEvent
            {
                OperationId       = command.OperationId,
                TransactionId     = activeTransactionId,
                TransactionNumber = command.ActiveTransactioNumber + 1
            });

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(Commands.CreateApiKeyCommand command, IEventPublisher eventPublisher)
        {
            _log.Info("Create api key.", command);

            var existedApiKey = await _apiKeyRepository.Get(x => x.WalletId == command.WalletId && x.ValidTill == null);

            if (existedApiKey != null)
            {
                existedApiKey.ValidTill = DateTime.UtcNow;

                if (string.IsNullOrEmpty(existedApiKey.Token))
                {
                    existedApiKey.Token = command.Token;
                }

                await _apiKeyRepository.Update(existedApiKey);

                await _keysPublisher.PublishAsync(new KeyUpdatedEvent
                {
                    Id        = existedApiKey.Id.ToString(),
                    IsDeleted = true,
                    Apiv2Only = existedApiKey.Apiv2Only
                });

                eventPublisher.PublishEvent(new ApiKeyUpdatedEvent
                {
                    ApiKey    = existedApiKey.Token ?? existedApiKey.Id.ToString(),
                    Token     = command.Token,
                    WalletId  = existedApiKey.WalletId,
                    Enabled   = false,
                    Apiv2Only = existedApiKey.Apiv2Only
                });
            }

            var key = new ApiKey
            {
                Id        = Guid.Parse(command.ApiKey),
                Token     = command.Token,
                ClientId  = command.ClientId,
                WalletId  = command.WalletId,
                Created   = command.Created,
                Apiv2Only = command.Apiv2Only
            };

            await _apiKeyRepository.Add(key);

            _chaosKitty.Meow("repository unavailable");

            eventPublisher.PublishEvent(new ApiKeyUpdatedEvent
            {
                ApiKey    = key.Token ?? key.Id.ToString(),
                Token     = command.Token,
                WalletId  = key.WalletId,
                Enabled   = true,
                Apiv2Only = key.Apiv2Only
            });

            await _keysPublisher.PublishAsync(new KeyUpdatedEvent
            {
                Id        = key.Id.ToString(),
                IsDeleted = false,
                Apiv2Only = key.Apiv2Only
            });

            return(CommandHandlingResult.Ok());
        }
Beispiel #21
0
        private async Task <CommandHandlingResult> StartBatchedCashoutAsync(
            AcceptCashoutCommand command,
            IEventPublisher publisher,
            CashoutsAggregationConfiguration aggregationConfiguration)
        {
            if (await _closedBatchedCashoutRepository.IsCashoutClosedAsync(command.OperationId))
            {
                return(CommandHandlingResult.Ok());
            }

            var activeCashoutBatchId = await _activeCashoutsBatchIdRepository.GetActiveOrNextBatchId
                                       (
                command.BlockchainType,
                command.BlockchainAssetId,
                command.HotWalletAddress,
                CashoutsBatchAggregate.GetNextId
                                       );

            _chaosKitty.Meow(command.OperationId);

            var batch = await _cashoutsBatchRepository.GetOrAddAsync
                        (
                activeCashoutBatchId.BatchId,
                () => CashoutsBatchAggregate.Start
                (
                    activeCashoutBatchId.BatchId,
                    command.BlockchainType,
                    command.AssetId,
                    command.BlockchainAssetId,
                    command.HotWalletAddress,
                    aggregationConfiguration.CountThreshold,
                    aggregationConfiguration.AgeThreshold
                )
                        );

            _chaosKitty.Meow(command.OperationId);

            var cashout = batch.Cashouts.SingleOrDefault(p => p.CashoutId == command.OperationId) ??
                          new BatchedCashoutValueType(command.OperationId, command.ClientId, command.ToAddress, command.Amount, batch.Cashouts.Count, DateTime.UtcNow);

            var isCashoutShouldWaitForNextBatch = !(batch.IsStillFillingUp || batch.Cashouts.Contains(cashout));

            if (isCashoutShouldWaitForNextBatch)
            {
                return(CommandHandlingResult.Fail(_cqrsSettings.RetryDelay));
            }

            var transitionResult = batch.AddCashout(cashout);

            if (transitionResult.ShouldSaveAggregate())
            {
                await _cashoutsBatchRepository.SaveAsync(batch);

                _chaosKitty.Meow(command.OperationId);
            }

            if (transitionResult.ShouldPublishEvents())
            {
                if (batch.State == CashoutsBatchState.FillingUp && cashout.IndexInBatch == 0)
                {
                    publisher.PublishEvent
                    (
                        new BatchFillingStartedEvent
                    {
                        BatchId = batch.BatchId
                    }
                    );
                }
                else if (batch.State == CashoutsBatchState.Filled)
                {
                    publisher.PublishEvent
                    (
                        new BatchFilledEvent
                    {
                        BatchId = batch.BatchId
                    }
                    );
                }

                _chaosKitty.Meow(command.OperationId);

                publisher.PublishEvent
                (
                    new BatchedCashoutStartedEvent
                {
                    BatchId           = batch.BatchId,
                    OperationId       = command.OperationId,
                    BlockchainType    = command.BlockchainType,
                    BlockchainAssetId = command.BlockchainAssetId,
                    AssetId           = command.AssetId,
                    HotWalletAddress  = command.HotWalletAddress,
                    ToAddress         = command.ToAddress,
                    Amount            = command.Amount,
                    ClientId          = command.ClientId
                }
                );

                _chaosKitty.Meow(command.OperationId);
            }

            return(CommandHandlingResult.Ok());
        }
Beispiel #22
0
        public async Task <CommandHandlingResult> Handle(EnrollToMatchingEngineCommand command, IEventPublisher publisher)
        {
            // First level deduplication just to reduce traffic to the ME
            if (await _deduplicationRepository.IsExistsAsync(command.CashinOperationId))
            {
                _log.Info("Deduplicated at first level", command);

                // Workflow should be continued

                publisher.PublishEvent(new CashinEnrolledToMatchingEngineEvent
                {
                    CashoutOperationId = command.CashoutOperationId
                });

                return(CommandHandlingResult.Ok());
            }

            var cashInResult = await _meClient.CashInOutAsync(
                command.CashinOperationId.ToString(),
                command.RecipientClientId.ToString(),
                command.AssetId,
                (double)command.Amount);

            _chaosKitty.Meow(command.CashoutOperationId);

            if (cashInResult == null)
            {
                throw new InvalidOperationException("ME response is null, don't know what to do");
            }

            switch (cashInResult.Status)
            {
            case MeStatusCodes.Ok:
            case MeStatusCodes.Duplicate:
                if (cashInResult.Status == MeStatusCodes.Duplicate)
                {
                    _log.Info("Deduplicated by the ME", command);
                }

                publisher.PublishEvent(new CashinEnrolledToMatchingEngineEvent
                {
                    CashoutOperationId = command.CashoutOperationId
                });

                _chaosKitty.Meow(command.CashinOperationId);

                await _deduplicationRepository.InsertOrReplaceAsync(command.CashinOperationId);

                _chaosKitty.Meow(command.CashinOperationId);

                return(CommandHandlingResult.Ok());

            case MeStatusCodes.Runtime:
                // Retry forever with the default delay + log the error outside.
                throw new Exception($"Cashin into the ME is failed. ME status: {cashInResult.Status}, ME message: {cashInResult.Message}");

            default:
                // Just abort cashout for further manual processing. ME call could not be retried anyway if responce was received.
                _log.Warning(
                    $"Unexpected response from ME. Status: {cashInResult.Status}, ME message: {cashInResult.Message}",
                    context: command);
                return(CommandHandlingResult.Ok());
            }
        }
Beispiel #23
0
        public async Task <CommandHandlingResult> Handle(BeginTransactionHistoryMonitoringCommand command, IEventPublisher publisher)
        {
            var apiClient = _blockchainIntegrationService.TryGetApiClient(command.BlockchainType);

            if (apiClient == null)
            {
                throw new NotSupportedException($"Blockchain type [{command.BlockchainType}] is not supported");
            }


            const MonitoringSubscriptionType subscriptionType = MonitoringSubscriptionType.TransactionHistory;

            var address        = command.Address;
            var blockchainType = command.BlockchainType;

            if (await _monitoringSubscriptionRepository.WalletSubscriptionsCount(blockchainType, address, subscriptionType) == 0)
            {
                try
                {
                    await apiClient.StartHistoryObservationOfIncomingTransactionsAsync(address);
                }
                catch (ErrorResponseException e) when(e.StatusCode == HttpStatusCode.Conflict)
                {
                }
                catch (ErrorResponseException e)
                {
                    string warningMessage;

                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (e.StatusCode)
                    {
                    case HttpStatusCode.NotImplemented:
                        warningMessage =
                            $"Blockchain type [{blockchainType}] does not support transactions history.";
                        break;

                    case HttpStatusCode.NotFound:
                        warningMessage =
                            $"Blockchain type [{blockchainType}] either does not support transactions history, or not respond.";
                        break;

                    default:
                        throw;
                    }

                    _log.Warning(warningMessage, context: command);

                    return(CommandHandlingResult.Ok());
                }
            }

            // Register subscription of specified asset for specified wallet
            await _monitoringSubscriptionRepository.RegisterWalletSubscriptionAsync
            (
                blockchainType,
                address,
                subscriptionType
            );

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(Commands.CompleteOperationCommand command, IEventPublisher eventPublisher)
        {
            await _operationsClient.Complete(command.CommandId);

            return(CommandHandlingResult.Ok());
        }
Beispiel #25
0
        public async Task <CommandHandlingResult> Handle(CashInCommand command, IEventPublisher eventPublisher)
        {
            var tx = await _paymentTransactionsRepository.GetByTransactionIdAsync(command.TransactionId);

            if (tx != null && (tx.Status == PaymentStatus.NotifyDeclined || tx.Status == PaymentStatus.NotifyProcessed || tx.Status == PaymentStatus.Processing))
            {
                return(CommandHandlingResult.Ok());
            }

            var transactionStatus = await _link4PayApiService.GetTransactionInfoAsync(command.TransactionId);

            if (!string.IsNullOrEmpty(transactionStatus.Card?.CardHash) && transactionStatus.OriginalTxnStatus == TransactionStatus.Successful)
            {
                await _paymentTransactionsRepository.SaveCardHashAsync(command.TransactionId, transactionStatus.Card.CardHash);

                if (tx != null)
                {
                    var evt = new CreditCardUsedEvent
                    {
                        ClientId     = tx.ClientId,
                        OrderId      = command.TransactionId,
                        CardHash     = transactionStatus.Card.CardHash,
                        CardNumber   = transactionStatus.Card.CardNo,
                        CustomerName = transactionStatus.Card.CardHolderName
                    };

                    eventPublisher.PublishEvent(evt);
                }
                else
                {
                    _log.Warning("CreditCardUsedEvent is not sent!", context: new { tranasactionId = transactionStatus.TxnReference }.ToJson());
                }
            }

            switch (transactionStatus.OriginalTxnStatus)
            {
            case TransactionStatus.Successful:
                tx = await _paymentTransactionsRepository.StartProcessingTransactionAsync(command.TransactionId);

                if (tx != null)     // initial status
                {
                    eventPublisher.PublishEvent(new ProcessingStartedEvent
                    {
                        OrderId = command.TransactionId
                    });
                }

                return(CommandHandlingResult.Ok());

            case TransactionStatus.Failed:
                await _paymentTransactionsRepository.SetStatusAsync(command.TransactionId, PaymentStatus.NotifyDeclined);

                await _paymentTransactionEventsLog.WriteAsync(
                    PaymentTransactionLogEvent.Create(
                        command.TransactionId, command.Request, "Declined by Payment status from payment system", nameof(CashInCommand)));

                return(CommandHandlingResult.Ok());

            default:
                return(new CommandHandlingResult {
                    Retry = true, RetryDelay = (long)TimeSpan.FromMinutes(1).TotalMilliseconds
                });
            }
        }
Beispiel #26
0
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand command, IEventPublisher eventPublisher)
        {
            var operationId = command.OperationId.ToString();

            _log.Info("Got cashout command", context: new { operationId, command = command.ToJson() });

            var  clientId  = command.ClientId.ToString();
            long?accountId = null;

            string walletId;

            if (command.WalletId.HasValue)
            {
                walletId = command.WalletId.Value == Guid.Empty
                    ? clientId
                    : command.WalletId.Value.ToString();
            }
            else
            {
                walletId = clientId;
            }

            var accountSearchResponse = await _siriusApiClient.Accounts.SearchAsync(new AccountSearchRequest
            {
                BrokerAccountId = _brokerAccountId,
                UserNativeId    = clientId,
                ReferenceId     = walletId
            });

            if (accountSearchResponse.ResultCase == AccountSearchResponse.ResultOneofCase.Error)
            {
                var message = "Error fetching Sirius Account";
                _log.Warning(nameof(CashoutCommandHandler),
                             message,
                             context: new
                {
                    error = accountSearchResponse.Error,
                    walletId,
                    clientId,
                    operationId
                });
                throw new Exception(message);
            }

            if (!accountSearchResponse.Body.Items.Any())
            {
                var accountRequestId = $"{_brokerAccountId}_{walletId}_account";
                var userRequestId    = $"{clientId}_user";

                var userCreateResponse = await _siriusApiClient.Users.CreateAsync(new CreateUserRequest
                {
                    RequestId = userRequestId,
                    NativeId  = clientId
                });

                if (userCreateResponse.BodyCase == CreateUserResponse.BodyOneofCase.Error)
                {
                    var message = "Error creating User in Sirius";
                    _log.Warning(nameof(CashoutCommandHandler),
                                 message,
                                 context: new
                    {
                        error = userCreateResponse.Error,
                        clientId,
                        requestId = userRequestId,
                        operationId
                    });
                    throw new Exception(message);
                }

                var createResponse = await _siriusApiClient.Accounts.CreateAsync(new AccountCreateRequest
                {
                    RequestId       = accountRequestId,
                    BrokerAccountId = _brokerAccountId,
                    UserId          = userCreateResponse.User.Id,
                    ReferenceId     = walletId
                });

                if (createResponse.ResultCase == AccountCreateResponse.ResultOneofCase.Error)
                {
                    var message = "Error creating user in Sirius";
                    _log.Warning(nameof(CashoutCommandHandler),
                                 message,
                                 context: new
                    {
                        error = createResponse.Error,
                        clientId,
                        requestId = accountRequestId,
                        operationId
                    });
                    throw new Exception(message);
                }

                accountId = createResponse.Body.Account.Id;
            }
            else
            {
                accountId = accountSearchResponse.Body.Items.FirstOrDefault()?.Id;
            }

            var whitelistingRequestId = $"lykke:trading_wallet:{clientId}";

            var whitelistItemCreateResponse = await _siriusApiClient.WhitelistItems.CreateAsync(new WhitelistItemCreateRequest
            {
                Name  = "Trading Wallet Whitelist",
                Scope = new WhitelistItemScope
                {
                    BrokerAccountId = _brokerAccountId,
                    AccountId       = accountId,
                    UserNativeId    = clientId
                },
                Details = new WhitelistItemDetails
                {
                    TransactionType = WhitelistTransactionType.Any,
                    TagType         = new  NullableWhitelistItemTagType
                    {
                        Null = NullValue.NullValue
                    }
                },
                Lifespan = new WhitelistItemLifespan
                {
                    StartsAt = Timestamp.FromDateTime(DateTime.UtcNow)
                },
                RequestId = whitelistingRequestId
            });

            if (whitelistItemCreateResponse.BodyCase == WhitelistItemCreateResponse.BodyOneofCase.Error)
            {
                _log.Warning(nameof(CashoutCommandHandler), "Error creating Whitelist item",
                             context: new
                {
                    error = whitelistItemCreateResponse.Error,
                    clientId,
                    requestId = whitelistingRequestId,
                    operationId
                });

                throw new Exception("Error creating Whitelist item");
            }

            var tag = !string.IsNullOrWhiteSpace(command.Tag) ? command.Tag : string.Empty;

            WithdrawalDestinationTagType?tagType =
                !string.IsNullOrWhiteSpace(command.Tag)
                    ? (long.TryParse(command.Tag, out _)
                        ? WithdrawalDestinationTagType.Number
                        : WithdrawalDestinationTagType.Text)
                    : null;

            var document = new WithdrawalDocument
            {
                BrokerAccountId       = _brokerAccountId,
                WithdrawalReferenceId = command.OperationId.ToString(),
                AssetId            = command.SiriusAssetId,
                Amount             = command.Amount,
                DestinationDetails = new WithdrawalDestinationDetails
                {
                    Address = command.Address,
                    Tag     = tag,
                    TagType = tagType
                },
                AccountReferenceId = walletId
            }.ToJson();

            var signatureBytes = _encryptionService.GenerateSignature(Encoding.UTF8.GetBytes(document), _privateKeyService.GetPrivateKey());
            var signature      = Convert.ToBase64String(signatureBytes);

            var result = await _siriusApiClient.Withdrawals.ExecuteAsync(new WithdrawalExecuteRequest
            {
                RequestId = $"{_brokerAccountId}_{command.OperationId}",
                Document  = document,
                Signature = signature
            });


            if (result.ResultCase == WithdrawalExecuteResponse.ResultOneofCase.Error)
            {
                switch (result.Error.ErrorCode)
                {
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.IsNotAuthorized:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.Unknown:
                    LogError(operationId, result.Error);
                    return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(10)));

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.InvalidParameters:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.NotFound:
                    LogError(operationId, result.Error);
                    return(CommandHandlingResult.Ok());    // abort

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.NotEnoughBalance:
                    LogWarning(operationId, result.Error);
                    return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(_notEnoughBalanceRetryDelayInSeconds)));

                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.InvalidAddress:
                case WithdrawalExecuteErrorResponseBody.Types.ErrorCode.AmountIsTooLow:
                    LogWarning(operationId, result.Error);
                    return(CommandHandlingResult.Ok());    // abort
                }
            }

            _log.Info("Cashout sent to Sirius", context: new { operationId, withdrawal = result.Body.Withdrawal.ToJson() });

            return(CommandHandlingResult.Ok());
        }
Beispiel #27
0
        public async Task <CommandHandlingResult> Handle(ProcessLimitOrderCommand command, IEventPublisher eventPublisher)
        {
            var sw = new Stopwatch();

            sw.Start();
            var stepWatch = new Stopwatch();

            stepWatch.Start();

            try
            {
                var clientId = command.LimitOrder.Order.ClientId;

                if (!_trusted.ContainsKey(clientId))
                {
                    _trusted[clientId] = (await _clientAccountClient.IsTrustedAsync(clientId)).Value;
                }

                _log.Info("LimitOrderProcessing", new { TxHandler = new { Step = "01. Check client account is trusted", Time = stepWatch.ElapsedMilliseconds } });
                stepWatch.Restart();

                var isTrustedClient = _trusted[clientId];

                var limitOrderExecutedEvent = new LimitOrderExecutedEvent
                {
                    IsTrustedClient = isTrustedClient,
                    LimitOrder      = command.LimitOrder
                };

                var tradesWerePerformed = command.LimitOrder.Trades != null && command.LimitOrder.Trades.Any();
                if (!isTrustedClient)
                {
                    // need previous order state for not trusted clients
                    var prevOrderState = await _limitOrdersRepository.GetOrderAsync(command.LimitOrder.Order.ClientId, command.LimitOrder.Order.Id);

                    var isImmediateTrade = tradesWerePerformed && command.LimitOrder.Trades.First().Timestamp == command.LimitOrder.Order.Registered;
                    limitOrderExecutedEvent.HasPrevOrderState   = prevOrderState != null && !isImmediateTrade;
                    limitOrderExecutedEvent.PrevRemainingVolume = prevOrderState?.RemainingVolume;

                    limitOrderExecutedEvent.Aggregated = AggregateSwaps(limitOrderExecutedEvent.LimitOrder.Trades);

                    _log.Info("LimitOrderProcessing", new { TxHandler = new { Step = "02. Get Previous order state for not trusted client", Time = stepWatch.ElapsedMilliseconds } });
                    stepWatch.Restart();
                }

                if (!isTrustedClient)
                {
                    await _limitOrdersRepository.CreateOrUpdateAsync(command.LimitOrder.Order);

                    _log.Info("LimitOrderProcessing", new { TxHandler = new { Step = "03. Upsert limit order for not trusted client", Time = stepWatch.ElapsedMilliseconds } });
                    stepWatch.Restart();
                }

                var status = (OrderStatus)Enum.Parse(typeof(OrderStatus), command.LimitOrder.Order.Status);

                // workaround: ME sends wrong status
                if (status == OrderStatus.Processing && !tradesWerePerformed)
                {
                    status = OrderStatus.InOrderBook;
                }
                else if (status == OrderStatus.PartiallyMatched && !tradesWerePerformed)
                {
                    status = OrderStatus.Placed;
                }

                if (status == OrderStatus.Processing ||
                    status == OrderStatus.PartiallyMatched ||  // new version of Processing
                    status == OrderStatus.Matched ||
                    status == OrderStatus.Cancelled)
                {
                    limitOrderExecutedEvent.Trades = await CreateTrades(command.LimitOrder);
                }

                _log.Info("LimitOrderProcessing", new { TxHandler = new { Step = "04. Create trades", Time = stepWatch.ElapsedMilliseconds } });
                stepWatch.Restart();

                eventPublisher.PublishEvent(limitOrderExecutedEvent);

                return(CommandHandlingResult.Ok());
            }
            finally
            {
                sw.Stop();
                _log.Info("Command execution time",
                          context: new { TxHandler = new { Handler = nameof(LimitOrderCommandHandler), Command = nameof(ProcessLimitOrderCommand),
                                                           Time    = sw.ElapsedMilliseconds } });
            }
        }
Beispiel #28
0
        public async Task <CommandHandlingResult> Handle(EnrollToMatchingEngineCommand command, IEventPublisher publisher)
        {
            var clientId = command.ClientId;

            var amountDecimal = (decimal)command.MatchingEngineOperationAmount;

            var scale  = amountDecimal.GetScale();
            var amount = command.MatchingEngineOperationAmount.TruncateDecimalPlaces(scale);

            if (clientId == null)
            {
                clientId = await _walletsClient.TryGetClientIdAsync(
                    command.BlockchainType,
                    command.DepositWalletAddress);
            }

            if (clientId == null)
            {
                throw new InvalidOperationException("Client ID for the blockchain deposit wallet address is not found");
            }

            // First level deduplication just to reduce traffic to the ME
            if (await _deduplicationRepository.IsExistsAsync(command.OperationId))
            {
                _log.Info(nameof(EnrollToMatchingEngineCommand), "Deduplicated at first level", command.OperationId);

                // Workflow should be continued

                publisher.PublishEvent(new CashinEnrolledToMatchingEngineEvent
                {
                    ClientId    = clientId.Value,
                    OperationId = command.OperationId
                });

                return(CommandHandlingResult.Ok());
            }

            var cashInResult = await _meClient.CashInOutAsync
                               (
                id : command.OperationId.ToString(),
                clientId : clientId.Value.ToString(),
                assetId : command.AssetId,
                amount : amount
                               );

            _chaosKitty.Meow(command.OperationId);

            if (cashInResult == null)
            {
                throw new InvalidOperationException("ME response is null, don't know what to do");
            }

            switch (cashInResult.Status)
            {
            case MeStatusCodes.Ok:
            case MeStatusCodes.Duplicate:
                if (cashInResult.Status == MeStatusCodes.Duplicate)
                {
                    _log.Info(nameof(EnrollToMatchingEngineCommand), "Deduplicated by the ME", command.OperationId);
                }

                publisher.PublishEvent(new CashinEnrolledToMatchingEngineEvent
                {
                    ClientId    = clientId.Value,
                    OperationId = command.OperationId
                });

                _chaosKitty.Meow(command.OperationId);

                await _deduplicationRepository.InsertOrReplaceAsync(command.OperationId);

                _chaosKitty.Meow(command.OperationId);

                return(CommandHandlingResult.Ok());

            case MeStatusCodes.Runtime:
                // Retry forever with the default delay + log the error outside.
                throw new Exception($"Cashin into the ME is failed. ME status: {cashInResult.Status}, ME message: {cashInResult.Message}");

            default:
                // Just abort cashin for futher manual processing. ME call could not be retried anyway if responce was received.
                _log.Error(nameof(EnrollToMatchingEngineCommand), null, $"Unexpected response from ME. Status: {cashInResult.Status}, ME message: {cashInResult.Message}", context: command.OperationId);
                return(CommandHandlingResult.Ok());
            }
        }
Beispiel #29
0
        public async Task <CommandHandlingResult> Handle(EnrollEthCashinToMatchingEngineCommand command, IEventPublisher eventPublisher)
        {
            try
            {
                var cashinId = command.CashinOperationId.ToString("N");
                var clientId = command.ClientId;
                var hash     = command.TransactionHash;
                var amount   = command.Amount;
                var asset    = await _assetsServiceWithCache.TryGetAssetAsync(command.AssetId);

                var createPendingActions = command.CreatePendingActions;
                var paymentTransaction   = PaymentTransaction.Create(hash,
                                                                     CashInPaymentSystem.Ethereum, clientId.ToString(), (double)amount,
                                                                     asset.DisplayId ?? asset.Id, status: PaymentStatus.Processing);

                var exists = await _paymentTransactionsRepository.CheckExistsAsync(paymentTransaction);

                if (exists)
                {
                    _log.Warning(command.TransactionHash ?? "Empty", $"Transaction already handled {hash}",
                                 context: command);

                    return(CommandHandlingResult.Ok());
                }

                if (createPendingActions && asset.IsTrusted)
                {
                    await _ethererumPendingActionsRepository.CreateAsync(clientId.ToString(), Guid.NewGuid().ToString());
                }

                ChaosKitty.Meow();

                MeResponseModel result = null;

                await ExecuteWithTimeoutHelper.ExecuteWithTimeoutAsync(async() =>
                {
                    result = await _matchingEngineClient.CashInOutAsync(cashinId, clientId.ToString(), asset.Id, (double)amount);
                }, 5 * 60 * 1000);     // 5 min in ms

                if (result == null ||
                    (result.Status != MeStatusCodes.Ok &&
                     result.Status != MeStatusCodes.Duplicate))
                {
                    _log.Warning(command.TransactionHash ?? "Empty", "ME error", context: result);

                    return(CommandHandlingResult.Fail(TimeSpan.FromMinutes(1)));
                }

                eventPublisher.PublishEvent(new EthCashinEnrolledToMatchingEngineEvent()
                {
                    TransactionHash = hash,
                });

                ChaosKitty.Meow();

                await _paymentTransactionsRepository.TryCreateAsync(paymentTransaction);

                return(CommandHandlingResult.Ok());
            }
            catch (Exception e)
            {
                _log.Error(nameof(EnrollEthCashinToMatchingEngineCommand), e, context: command);
                throw;
            }
        }
        public async Task <CommandHandlingResult> Handle(Commands.SaveTransferOperationStateCommand command, IEventPublisher eventPublisher)
        {
            var sw = new Stopwatch();

            sw.Start();

            try
            {
                var message       = command.QueueMessage;
                var transactionId = message.Id;

                var transaction = await _transactionsRepository.FindByTransactionIdAsync(transactionId);

                if (transaction == null)
                {
                    _log.Error(nameof(Commands.SaveManualOperationStateCommand), new Exception($"unknown transaction {transactionId}"), context: command);
                    return(CommandHandlingResult.Ok());
                }

                var amountNoFee = await _feeCalculationService.GetAmountNoFeeAsync(message);

                var context = await _transactionService.GetTransactionContext <TransferContextData>(transactionId) ??
                              TransferContextData.Create(
                    message.FromClientId,
                    new TransferContextData.TransferModel
                {
                    ClientId = message.ToClientid
                },
                    new TransferContextData.TransferModel
                {
                    ClientId = message.FromClientId
                });

                context.Transfers[0].OperationId = Guid.NewGuid().ToString();
                context.Transfers[1].OperationId = Guid.NewGuid().ToString();

                var destWallet = await _walletCredentialsRepository.GetAsync(message.ToClientid);

                var sourceWallet = await _walletCredentialsRepository.GetAsync(message.FromClientId);

                var contextJson = context.ToJson();
                var cmd         = new TransferCommand
                {
                    Amount             = amountNoFee,
                    AssetId            = message.AssetId,
                    Context            = contextJson,
                    SourceAddress      = sourceWallet?.MultiSig,
                    DestinationAddress = destWallet?.MultiSig,
                    TransactionId      = Guid.Parse(transactionId)
                };

                await SaveState(cmd, context);

                eventPublisher.PublishEvent(new TransferOperationStateSavedEvent {
                    TransactionId = transactionId, QueueMessage = message, AmountNoFee = (double)amountNoFee
                });

                return(CommandHandlingResult.Ok());
            }
            finally
            {
                sw.Stop();
                _log.Info("Command execution time",
                          context: new { TxHandler = new { Handler = nameof(OperationsCommandHandler), Command = nameof(Commands.SaveTransferOperationStateCommand),
                                                           Time    = sw.ElapsedMilliseconds } });
            }
        }