public async Task <CommandHandlingResult> Handle(LockSourceAddressCommand command, IEventPublisher publisher)
        {
            var transactionExecution = await _transactionExecutionsRepository.GetAsync(command.TransactionId);

            if (transactionExecution.WasLocked)
            {
                _log.Info("Source address lock command has been skipped, since lock already was performed earlier", command);

                return(CommandHandlingResult.Ok());
            }

            var isSourceAdressCaptured = await _sourceAddresLocksRepoistory.TryGetLockAsync(
                command.BlockchainType,
                command.FromAddress,
                command.TransactionId);

            if (!isSourceAdressCaptured)
            {
                return(CommandHandlingResult.Fail(_retryDelayProvider.SourceAddressLockingRetryDelay));
            }

            _chaosKitty.Meow(command.TransactionId);

            publisher.PublishEvent(new SourceAddressLockedEvent
            {
                OperationId   = command.OperationId,
                TransactionId = command.TransactionId
            });

            return(CommandHandlingResult.Ok());
        }
        /// <summary>
        ///     BIL batched cashout event
        /// </summary>
        /// <param name="event"></param>
        /// <returns></returns>
        public async Task <CommandHandlingResult> Handle(
            Lykke.Job.BlockchainCashoutProcessor.Contract.Events.CashoutsBatchCompletedEvent @event)
        {
            if (@event.Cashouts == null || @event.Cashouts.Length == 0)
            {
                _logger.Warning($"BIL batched cashout event is empty, BatchId {@event.BatchId}", context: @event);

                return(CommandHandlingResult.Ok());
            }

            foreach (var cashout in @event.Cashouts)
            {
                if (!await _historyRecordsRepository.UpdateBlockchainHashAsync(cashout.OperationId,
                                                                               @event.TransactionHash))
                {
                    _logger.Warning($"Transaction hash was not set, BIL cashout", context: new
                    {
                        id   = cashout.OperationId,
                        hash = @event.TransactionHash
                    });

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

            return(CommandHandlingResult.Ok());
        }
示例#3
0
        public async Task <CommandHandlingResult> Handle(Commands.SegwitTransferCommand command)
        {
            var sw = new Stopwatch();

            sw.Start();

            try
            {
                var response = await _bitcoinApiClient.SegwitTransfer(Guid.Parse(command.Id), command.Address);

                if (response.HasError && response.Error.ErrorCode != ErrorCode.DuplicateTransactionId)
                {
                    _log.Error($"{nameof(BitcoinCommandHandler)}:{nameof(Commands.SegwitTransferCommand)}", new Exception(response.ToJson()), context: command.ToJson());
                    return(CommandHandlingResult.Fail(_retryTimeout));
                }

                ChaosKitty.Meow();

                return(CommandHandlingResult.Ok());
            }
            finally
            {
                sw.Stop();
                _log.Info("Command execution time",
                          context: new { TxHandler = new { Handler = nameof(BitcoinCommandHandler), Command = nameof(Commands.SegwitTransferCommand),
                                                           Time    = sw.ElapsedMilliseconds } });
            }
        }
示例#4
0
        public async Task <CommandHandlingResult> Handle(WaitForBatchExpirationCommand command, IEventPublisher publisher)
        {
            var batch = await _cashoutsBatchRepository.GetAsync(command.BatchId);

            if (!batch.HaveToBeExpired && batch.IsStillFillingUp)
            {
                return(CommandHandlingResult.Fail(_batchExpirationMonitoringPeriod));
            }

            var transitionResult = batch.Expire();

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

                _chaosKitty.Meow(command.BatchId);
            }

            if (transitionResult.ShouldPublishEvents())
            {
                publisher.PublishEvent
                (
                    new BatchExpiredEvent
                {
                    BatchId = batch.BatchId
                }
                );

                _chaosKitty.Meow(command.BatchId);
            }

            return(CommandHandlingResult.Ok());
        }
示例#5
0
        public async Task Test_that_Handle_call_returns_Fail_with_30_seconds_delay_if_exception_thrown()
        {
            // Arrange
            var distributionPlanServiceMock = new Mock <IDistributionPlanService>();
            var eventPublisherMock          = new Mock <IEventPublisher>();

            // Setup
            distributionPlanServiceMock
            .Setup(x => x.ExecutePlanAsync(It.IsAny <Guid>()))
            .ThrowsAsync(new Exception());

            distributionPlanServiceMock
            .Setup(x => x.PlanExistsAsync(It.IsAny <Guid>()))
            .ReturnsAsync(true);

            // Act
            var handler = CreateHandler(distributionPlanServiceMock);

            var actualResult = await handler.Handle
                               (
                new ExecuteDistributionPlanCommand(),
                eventPublisherMock.Object
                               );

            // Assert
            var expectedResult = CommandHandlingResult.Fail(TimeSpan.FromSeconds(30));

            actualResult
            .Should()
            .BeEquivalentTo(expectedResult);
        }
示例#6
0
        public async Task CommandsInterceptor__CommandIsNotRegistered__Throws()
        {
            PrepareServicesForInterception(out var _,
                                           out var messageCancellationService,
                                           out var messageCancellationRegistry,
                                           out var logFactory);

            var notRegisteredMessage = new OneMoreMessageWithSomeId()
            {
                MessageId = Guid.NewGuid()
            };
            var messageCancellationCommandInterceptor =
                new MessageCancellationCommandInterceptor(
                    messageCancellationService,
                    messageCancellationRegistry,
                    logFactory.Object);
            var interceptionContext = new Mock <ICommandInterceptionContext>();

            interceptionContext.Setup(x => x.Command).Returns(notRegisteredMessage);
            interceptionContext.Setup(x => x.HandlerObject).Returns(this);
            interceptionContext.Setup(x => x.InvokeNextAsync())
            .Returns(Task.FromResult(CommandHandlingResult.Fail(TimeSpan.Zero)));

            await Assert.ThrowsAsync <MessageCancellationInterceptionException>(async() =>
            {
                var result = await messageCancellationCommandInterceptor.InterceptAsync(interceptionContext.Object);
            });
        }
        /// <summary>
        ///     BIL cross client cashout event completed
        /// </summary>
        /// <param name="event"></param>
        /// <returns></returns>
        public async Task <CommandHandlingResult> Handle(
            Lykke.Job.BlockchainCashoutProcessor.Contract.Events.CrossClientCashoutCompletedEvent @event)
        {
            if (!await _historyRecordsRepository.UpdateBlockchainHashAsync(@event.OperationId,
                                                                           _crossClientTransactionHashSubstituition))
            {
                _logger.Warning($"Transaction hash was not set. " +
                                $"OperationId: {@event.OperationId}, " +
                                $"TxHash: {_crossClientTransactionHashSubstituition}", context: new
                {
                    id   = @event.OperationId,
                    hash = _crossClientTransactionHashSubstituition
                });

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

            if (!await _historyRecordsRepository.UpdateBlockchainHashAsync(@event.CashinOperationId,
                                                                           _crossClientTransactionHashSubstituition))
            {
                _logger.Warning($"Transaction cashin hash was not set. " +
                                $"OperationId: {@event.OperationId}, " +
                                $"TxHash: {_crossClientTransactionHashSubstituition}", context: new
                {
                    id   = @event.OperationId,
                    hash = _crossClientTransactionHashSubstituition
                });

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

            return(CommandHandlingResult.Ok());
        }
示例#8
0
        private async Task <CommandHandlingResult> Handle(UnsuspendAssetPairCommand command,
                                                          IEventPublisher publisher)
        {
            //idempotency handling not required
            var updateResult = await _productsDiscontinueService.ChangeSuspendStatusAsync(command.AssetPairId,
                                                                                          false,
                                                                                          username,
                                                                                          command.OperationId);

            if (!updateResult.IsSuccess)
            {
                return(CommandHandlingResult.Fail(_delay));
            }

            _chaosKitty.Meow(command.OperationId);

            var assetPair =
                AssetPair.CreateFromProduct(updateResult.NewValue, _defaultLegalEntitySettings.DefaultLegalEntity);

            publisher.PublishEvent(new AssetPairChangedEvent
            {
                OperationId = command.OperationId,
                AssetPair   = _convertService.Convert <IAssetPair, AssetPairContract>(assetPair),
            });

            publisher.PublishEvent(CreateProductChangedEvent(updateResult.OldValue,
                                                             updateResult.NewValue,
                                                             username,
                                                             command.OperationId));

            return(CommandHandlingResult.Ok());
        }
        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 ||
                    executionInfo.Data.RequestNumber > command.RequestNumber)
                {
                    return(CommandHandlingResult.Ok());
                }

                if (_dateService.Now() >= command.CreationTime.AddSeconds(command.TimeoutSeconds))
                {
                    publisher.PublishEvent(new SpecialLiquidationFailedEvent
                    {
                        OperationId          = command.OperationId,
                        CreationTime         = _dateService.Now(),
                        Reason               = $"Timeout of {command.TimeoutSeconds} seconds from {command.CreationTime:s}",
                        CanRetryPriceRequest = true
                    });

                    return(CommandHandlingResult.Ok());
                }
            }

            return(CommandHandlingResult.Fail(_marginTradingSettings.SpecialLiquidation.PriceRequestTimeoutCheckPeriod));
        }
示例#10
0
        public async Task <CommandHandlingResult> Handle(SendSmsCommand command, IEventPublisher eventPublisher)
        {
            var message = await _smsRepository.GetAsync(command.Id);

            var msg = new
            {
                Phone = command.Phone.SanitizePhone(),
                command.Id,
                command.Provider,
                command.CountryCode
            };

            if (message == null)
            {
                _log.WriteInfo(nameof(SendSmsCommand), msg, $"Sms message with messageId = {command.Id} not found");
                return(CommandHandlingResult.Ok());
            }

            if (message.IsExpired(_smsSettings.SmsRetryTimeout))
            {
                await _smsRepository.DeleteAsync(message.Id, message.MessageId);

                _log.WriteInfo(nameof(SendSmsCommand), msg, "Sms message expired and has been deleted");
                return(CommandHandlingResult.Ok());
            }

            var sender = _smsSenderFactory.GetSender(command.Provider);

            _log.WriteInfo(nameof(SendSmsCommand), msg, "Sending sms");

            try
            {
                string messageId = await sender.SendSmsAsync(command.Phone, command.Message, command.CountryCode);

                if (!string.IsNullOrEmpty(messageId))
                {
                    await _smsRepository.SetMessageIdAsync(messageId, command.Id);

                    _log.WriteInfo(nameof(SendSmsCommand), new { command.Id, MessageId = messageId }, "Message has been sent");
                }
                else
                {
                    await _smsRepository.DeleteAsync(command.Id, messageId);

                    _log.WriteInfo(nameof(SendSmsCommand), new { command.Id }, "Sms message has been deleted");
                }
            }
            catch (Exception)
            {
                await _smsProviderInfoRepository.AddAsync(command.Provider, command.CountryCode, SmsDeliveryStatus.Failed);

                return(CommandHandlingResult.Fail(_smsSettings.SmsSendDelay));
            }

            return(CommandHandlingResult.Ok());
        }
示例#11
0
 public ICommandHandlingResult Handle(CreateOrUpdateMessageCommand command)
 {
     try
     {
         return(HandlingPolicy.Execute(() => HandleInternal(command)));
     }
     catch (Exception ex)
     {
         return(CommandHandlingResult.Fail($"Error creating or updating message: {ex.Message}"));
     }
 }
 public async Task <CommandHandlingResult> InterceptAsync(ICommandInterceptionContext context)
 {
     try
     {
         return(await context.InvokeNextAsync());
     }
     catch (InvalidAggregateStateException ex)
     {
         _log.Warning($"{nameof(InvalidAggregateStateException)} handled", ex);
         return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(10)));
     }
 }
示例#13
0
        public async Task <CommandHandlingResult> InterceptAsync(IEventInterceptionContext context)
        {
            try
            {
                var result = await context.InvokeNextAsync();

                return(result);
            }
            catch (UnexpectedEventException ex)
            {
                _log.Warning($"{nameof(UnexpectedEventException)} handled", ex);
                return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(10)));
            }
        }
        public async Task <CommandHandlingResult> Handle(Commands.SegwitTransferCommand command)
        {
            var response = await _bitcoinApiClient.SegwitTransfer(Guid.Parse(command.Id), command.Address);

            if (response.HasError && response.Error.ErrorCode != ErrorCode.DuplicateTransactionId)
            {
                _log.Error($"{nameof(BitcoinCommandHandler)}:{nameof(Commands.SegwitTransferCommand)}", new Exception(response.ToJson()), context: command.ToJson());
                return(CommandHandlingResult.Fail(_retryTimeout));
            }

            ChaosKitty.Meow();

            return(CommandHandlingResult.Ok());
        }
        /// <summary>
        ///     Bitcoin cashout event
        /// </summary>
        /// <param name="event"></param>
        /// <returns></returns>
        public async Task <CommandHandlingResult> Handle(CashoutCompletedEvent @event)
        {
            if (!await _historyRecordsRepository.UpdateBlockchainHashAsync(@event.OperationId, @event.TxHash))
            {
                _logger.Warning($"Transaction hash was not set", context: new
                {
                    id   = @event.OperationId,
                    hash = @event.TxHash
                });

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

            return(CommandHandlingResult.Ok());
        }
示例#16
0
        public async Task <CommandHandlingResult> Handle(StartCashoutCommand 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");
            }

            var blockchainConfiguration = _blockchainConfigurationProvider.GetConfiguration(asset.BlockchainIntegrationLayerId);

            if (blockchainConfiguration.AreCashoutsDisabled)
            {
                _log.Warning(
                    $"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled",
                    context: command);

                await SendToOpsGenieAsync($"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled",
                                          $"Cashouts for {asset.BlockchainIntegrationLayerId} are disabled, operation Id = {command.OperationId}",
                                          command.ToJson());

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

            publisher.PublishEvent(new ValidationStartedEvent
            {
                OperationId       = command.OperationId,
                ClientId          = command.ClientId,
                AssetId           = asset.Id,
                BlockchainType    = asset.BlockchainIntegrationLayerId,
                BlockchainAssetId = asset.BlockchainIntegrationLayerAssetId,
                HotWalletAddress  = blockchainConfiguration.HotWalletAddress,
                ToAddress         = command.ToAddress,
                Amount            = command.Amount
            });

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(ProcessPaymentCommand command, IEventPublisher eventPublisher)
        {
            try
            {
                var asset = await _assetsServiceWithCache.TryGetAssetAsync(command.AssetId);

                var result = await _exchangeOperationsService.ExchangeOperations.TransferAsync(
                    new TransferRequestModel
                {
                    DestClientId     = command.ClientId,
                    SourceClientId   = _hotWalletId,
                    Amount           = command.Amount.TruncateDecimalPlaces(asset.Accuracy),
                    AssetId          = command.AssetId,
                    TransferTypeCode = "Common",
                    OperationId      = command.NewCashinId.ToString(),
                });

                if (result.IsOk())
                {
                    _log.Info($"Done processing: {command.ToJson()}");

                    eventPublisher.PublishEvent(new CashInProcesedEvent
                    {
                        ClientId    = command.ClientId,
                        OperationId = command.CashinId
                    });

                    return(CommandHandlingResult.Ok());
                }

                if (result.Code == (int)MeStatusCodes.Duplicate)
                {
                    _log.Warning($"Duplicate transfer attempt: {command.ToJson()}");

                    return(CommandHandlingResult.Ok());
                }

                throw new InvalidOperationException(
                          $"During transfer of {command.Id}, ME responded with: {result.Code}");
            }
            catch (Exception e)
            {
                _log.Error(e, context: command.ClientId);

                return(CommandHandlingResult.Fail(TimeSpan.FromMinutes(1)));
            }
        }
示例#18
0
        public async Task <CommandHandlingResult> Handle(WaitForTransactionEndingCommand command,
                                                         IEventPublisher publisher)
        {
            var blockchainAsset = await _client.GetAssetAsync(command.GasBlockchainAssetId);

            var tx = await _client.TryGetBroadcastedSingleTransactionAsync(command.TransactionId, blockchainAsset);

            _chaosKitty.Meow(command.TransactionId);

            if (tx == 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());
            }

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

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

            case BroadcastedTransactionState.Completed:

                publisher.PublishEvent(new GasClaimTransactionExecutedEvent
                {
                    TransactionId      = command.TransactionId,
                    Amount             = command.Amount,
                    BroadcastingBlock  = tx.Block,
                    BroadcastingMoment = tx.Timestamp,
                    TransactionHash    = tx.Hash
                });

                return(CommandHandlingResult.Ok());

            default:
                throw new ArgumentOutOfRangeException
                      (
                          nameof(tx.State),
                          $"Transaction state [{tx.State}] is not supported."
                      );
            }
        }
        public async Task <CommandHandlingResult> Handle(CashOutProcessedEvent cashOutProcessedEvent, ICommandSender commandSender)
        {
            var bitcoinTransactionExists = await _bitCoinTransactionsRepository.ForwardWithdrawalExistsAsync(cashOutProcessedEvent.OperationId.ToString());

            if (!bitcoinTransactionExists)
            {
                _log.Info("Skip cashout - not forward withdrawal.", context: cashOutProcessedEvent.ToJson());
                return(CommandHandlingResult.Ok());
            }

            var record = await _repository.TryGetByCashoutIdAsync(cashOutProcessedEvent.WalletId.ToString(), cashOutProcessedEvent.OperationId.ToString());

            if (record != null)
            {
                if (!Guid.TryParse(record.CashInId, out var cashinId))
                {
                    _log.Warning($"Cannot parse data : {record.ToJson()}");
                    return(CommandHandlingResult.Ok());
                }

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

                var forwardAsset = await _assetsServiceWithCache.TryGetAssetAsync(asset.ForwardBaseAsset);

                var settlementDate = record.DateTime.AddDays(asset.ForwardFrozenDays);

                var command = new CreateForwardCashinCommand
                {
                    AssetId     = forwardAsset.Id,
                    OperationId = cashinId,
                    Timestamp   = settlementDate,
                    Volume      = Math.Abs(cashOutProcessedEvent.Volume).TruncateDecimalPlaces(forwardAsset.Accuracy),
                    WalletId    = cashOutProcessedEvent.WalletId
                };

                commandSender.SendCommand(command, HistoryBoundedContext.Name);

                _log.Info("CreateForwardCashinCommand has been sent.", command);
                return(CommandHandlingResult.Ok());
            }

            _log.Warning("No forward withdrawal record found.", context: new { Id = cashOutProcessedEvent.OperationId.ToString() });
            return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(10)));
        }
示例#20
0
        public async Task <CommandHandlingResult> Handle(WaitForOperationResolutionCommand command, IEventPublisher publisher)
        {
            var resolution = await _operationValidationService.GetResolutionAsync(command.OperationId);

            switch (resolution)
            {
            case OperationValidationResolution.Unconfirmed:
                _log.Warning("Operation requires for manual confirmation", context: command);
                return(CommandHandlingResult.Fail(_checkingPeriod));

            case OperationValidationResolution.Accepted:
            case OperationValidationResolution.Rejected:
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
            }

            return(CommandHandlingResult.Ok());
        }
        public async Task <CommandHandlingResult> Handle(
            ExecuteDistributionPlanCommand command,
            IEventPublisher publisher)
        {
            if (!await _distributionPlanService.PlanExistsAsync(command.PlanId))
            {
                throw new InvalidOperationException($"Distribution plan [{command.PlanId}] has not been executed: plan does not exist.");
            }

            try
            {
                await _distributionPlanService.ExecutePlanAsync(command.PlanId);

                return(CommandHandlingResult.Ok());
            }
            catch (Exception e)
            {
                _log.Warning($"Distribution plan [{command.PlanId}] execution failed.", e);

                return(CommandHandlingResult.Fail(TimeSpan.FromSeconds(30)));
            }
        }
示例#22
0
        public async Task EventsInterceptor__CancellationRequested__FlowIsCancelled()
        {
            var @event = PrepareServicesForInterception(out var operationId,
                                                        out var messageCancellationService,
                                                        out var messageCancellationRegistry,
                                                        out var logFactory);

            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.False(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)));
            }
        }
示例#24
0
        public async Task CommandsInterceptor__CancellationIsNotRequested__FlowIsCompleted()
        {
            var command = PrepareServicesForInterception(out var operationId,
                                                         out var messageCancellationService,
                                                         out var messageCancellationRegistry,
                                                         out var logFactory);
            await messageCancellationService.RemoveMessageFromCancellationAsync(operationId);

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

            interceptionContext.Setup(x => x.Command).Returns(command);
            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(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."
                      );
            }
        }
示例#26
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());
        }
示例#27
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 } });
            }
        }
示例#28
0
 private static ICommandHandlingResult ValidationFail(string argumentName)
 {
     return(CommandHandlingResult.Fail($"Incorrect value for {argumentName}."));
 }
示例#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;
            }
        }
示例#30
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());
        }