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()); }
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()); }
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))); } }
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." ); } }
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()); }
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 } }); } }
// 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." ); } }
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(RegisterOperationStatisticsCommand command, IEventPublisher publisher) { await _statisticsService.RegisterStatisticsAsync(command.OperationId); return(CommandHandlingResult.Ok()); }
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()); }
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(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()); } }
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()); }
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 }); } }
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()); }
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 } }); } }
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()); } }
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 } }); } }