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()); }
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 } }); } }
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()); }
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); }
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()); }
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)); }
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()); }
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))); } }
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()); }
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))); } }
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))); }
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))); } }
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))); } }
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." ); } }
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()); }
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 } }); } }
private static ICommandHandlingResult ValidationFail(string argumentName) { return(CommandHandlingResult.Fail($"Incorrect value for {argumentName}.")); }
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(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()); }