public async Task MonitoringCoinTransactionJobUnitTest_TransactionIsInBlockchainSenhdEvent() { string operationId = "OpId-2"; QueueTriggeringContext context = new QueueTriggeringContext(DateTimeOffset.UtcNow); CoinTransactionMessage coinTransactionMessage = new CoinTransactionMessage() { PutDateTime = DateTime.UtcNow, TransactionHash = _commitedTransactionHash, OperationId = operationId }; #region ArrangeMocks ICoinTransaction coinTransaction = new CoinTransaction() { ConfirmationLevel = 1, TransactionHash = _commitedTransactionHash }; ICoinEvent coinEvent = new CoinEvent(operationId, _commitedTransactionHash, "from", "to", "1000000000000000000", CoinEventType.TransferStarted, "contractAddress", true); _ethereumTransactionService.Setup(x => x.IsTransactionInPool(_commitedTransactionHash)).Returns(Task.FromResult(false)); _coinTransactionService.Setup(x => x.ProcessTransaction(coinTransactionMessage)).Returns(Task.FromResult <ICoinTransaction>(coinTransaction)); _coinEventService.Setup(x => x.GetCoinEvent(_commitedTransactionHash)).Returns(Task.FromResult <ICoinEvent>(coinEvent)); _coinEventService.Setup(x => x.PublishEvent(coinEvent, It.IsAny <bool>())).Returns(Task.FromResult(0)).Verifiable(); #endregion MonitoringCoinTransactionJob job = GetJob(); await job.Execute(coinTransactionMessage, context); _coinEventService.Verify(x => x.PublishEvent(coinEvent, It.IsAny <bool>()), Times.Once); }
public async Task MonitoringCoinTransactionJobUnitTest_LostTransactionProcessing() { string operationId = "OpId-3"; QueueTriggeringContext context = new QueueTriggeringContext(DateTimeOffset.UtcNow); CoinTransactionMessage coinTransactionMessage = new CoinTransactionMessage() { PutDateTime = DateTime.UtcNow, TransactionHash = _lostTransactionHash, OperationId = operationId }; #region ArrangeMocks ICoinEvent coinEvent = new CoinEvent(operationId, _lostTransactionHash, "from", "to", "1000000000000000000", CoinEventType.TransferStarted, "contractAddress", true); _ethereumTransactionService.Setup(x => x.IsTransactionInPool(_lostTransactionHash)).Returns(Task.FromResult(false)); _coinTransactionService.Setup(x => x.ProcessTransaction(coinTransactionMessage)).Returns(Task.FromResult <ICoinTransaction>(null)); _coinEventService.Setup(x => x.GetCoinEvent(_lostTransactionHash)).Returns(Task.FromResult <ICoinEvent>(coinEvent)); _coinEventService.Setup(x => x.PublishEvent(coinEvent, It.IsAny <bool>())).Returns(Task.FromResult(0)).Verifiable(); _pendingOperationService.Setup(x => x.RefreshOperationByIdAsync(operationId)).Returns(Task.FromResult(0)).Verifiable(); #endregion MonitoringCoinTransactionJob job = GetJob(); await job.Execute(coinTransactionMessage, context); _coinEventService.Verify(x => x.PublishEvent(coinEvent, It.IsAny <bool>()), Times.Never); _pendingOperationService.Verify(x => x.RefreshOperationByIdAsync(operationId), Times.Once); }
private void SendMessageToTheQueueEnd(QueueTriggeringContext context, CoinTransactionMessage transaction, int delay, string error = "") { transaction.DequeueCount++; transaction.LastError = string.IsNullOrEmpty(error) ? transaction.LastError : error; context.MoveMessageToEnd(transaction.ToJson()); context.SetCountQueueBasedDelay(_settings.EthereumCore.MaxQueueDelay, delay); }
private async Task RepeatOperationTillWin(CoinTransactionMessage message) { var operation = await GetOperationAsync(message?.TransactionHash, message?.OperationId); if (operation == null) { return; } switch (operation.OperationType) { case HotWalletOperationType.Cashout: break; case HotWalletOperationType.Cashin: var retryMessage = new LykkePayErc20TransferMessage() { OperationId = operation.OperationId }; await _transferStartQueue.PutRawMessageAsync(retryMessage.ToJson()); break; default: return; } }
public async Task <ICoinTransaction> ProcessTransaction(CoinTransactionMessage transaction) { var receipt = await _transactionService.GetTransactionReceipt(transaction.TransactionHash); if (receipt == null) { return(null); } ICoinTransaction coinDbTransaction = await _coinTransactionRepository.GetTransaction(transaction.TransactionHash) ?? new CoinTransaction() { ConfirmationLevel = 0, TransactionHash = transaction.TransactionHash }; bool error = coinDbTransaction?.Error == true || !await _transactionService.IsTransactionExecuted(transaction.TransactionHash); var confimations = await _contractService.GetCurrentBlock() - receipt.BlockNumber; var coinTransaction = new CoinTransaction { TransactionHash = transaction.TransactionHash, Error = error, ConfirmationLevel = GetTransactionConfirmationLevel(confimations) }; await _coinTransactionRepository.InsertOrReplaceAsync(coinTransaction); return(coinTransaction); }
public async Task <string> StartCashinAsync(IHotWalletOperation operation) { await SaveOperationAsync(operation); var transactionHash = await _erc20DepositContractService.RecievePaymentFromDepositContract(operation.FromAddress, operation.TokenAddress, operation.ToAddress); await RetryPolicy.ExecuteUnlimitedAsync(async() => { await _hotWalletCashoutTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = operation.OperationId, TransactionHash = transactionHash }); }, TimeSpan.FromMinutes(1).Milliseconds, _log); CoinTransactionMessage message = new CoinTransactionMessage() { OperationId = operation.OperationId, TransactionHash = transactionHash }; return(transactionHash); }
private async Task RepeatOperationTillWin(CoinTransactionMessage message) { var operation = await GetOperationAsync(message?.TransactionHash, message?.OperationId); switch (operation.OperationType) { case HotWalletOperationType.Cashout: await _hotWalletService.RetryCashoutAsync(operation); break; case HotWalletOperationType.Cashin: await _hotWalletService.RemoveCashinLockAsync(operation.TokenAddress, operation.FromAddress); break; default: return; } }
public async Task <string> StartCashinAsync(IHotWalletOperation operation) { await SaveOperationAsync(operation); var transactionHash = await _erc20DepositContractService.RecievePaymentFromDepositContract(operation.FromAddress, operation.TokenAddress, operation.ToAddress); await _hotWalletCashoutTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = operation.OperationId, TransactionHash = transactionHash }); CoinTransactionMessage message = new CoinTransactionMessage() { OperationId = operation.OperationId, TransactionHash = transactionHash }; return(transactionHash); }
public async Task MonitoringCoinTransactionJobUnitTest_SkipTransactionThatIsStillInMemoryPool() { string operationId = "OpId-1"; QueueTriggeringContext context = new QueueTriggeringContext(DateTimeOffset.UtcNow); CoinTransactionMessage coinTransactionMessage = new CoinTransactionMessage() { PutDateTime = DateTime.UtcNow, TransactionHash = _memoryPoolTransactionHash, OperationId = operationId }; #region ArrangeMocks _ethereumTransactionService.Setup(x => x.IsTransactionInPool(_memoryPoolTransactionHash)).Returns(Task.FromResult(true)); _coinTransactionService.Setup(x => x.ProcessTransaction(coinTransactionMessage)).Verifiable(); #endregion MonitoringCoinTransactionJob job = GetJob(); await job.Execute(coinTransactionMessage, context); _coinTransactionService.Verify(x => x.ProcessTransaction(coinTransactionMessage), Times.Never); }
private async Task RepeatOperationTillWin(CoinTransactionMessage message) { ICoinEvent coinEvent = await GetCoinEvent(message.TransactionHash, message.OperationId, true); if (coinEvent == null) { await _eventTraceRepository.InsertAsync(new EventTrace() { Note = $"Operation processing is over. Put it in the garbage. With hash {message.TransactionHash}", OperationId = message.OperationId, TraceDate = DateTime.UtcNow }); return; } switch (coinEvent.CoinEventType) { case CoinEventType.CashinStarted: case CoinEventType.CashinCompleted: await UpdateUserTransferWallet(coinEvent.FromAddress, coinEvent.ToAddress); break; default: break; } await _eventTraceRepository.InsertAsync(new EventTrace() { Note = $"Operation With Id {coinEvent.OperationId} hash {message.TransactionHash} goes to {Constants.PendingOperationsQueue}", OperationId = message.OperationId, TraceDate = DateTime.UtcNow }); await _pendingOperationService.RefreshOperationByIdAsync(coinEvent.OperationId); }
public async Task Execute(LykkePayErc20TransferMessage transaction, QueueTriggeringContext context) { IHotWalletOperation operation = null; if (string.IsNullOrEmpty(transaction?.OperationId)) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", "", "Empty message skipped"); return; } try { operation = await _operationsRepository.GetAsync(transaction.OperationId); if (operation == null) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"No operation for id {transaction?.OperationId} message skipped"); return; } var transactionSenderAddress = _settings.LykkePay.LykkePayAddress; var balance = await _ercInterfaceService.GetPendingBalanceForExternalTokenAsync(operation.FromAddress, operation.TokenAddress); if (balance == 0) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"DepositAddress: {operation.FromAddress}, TokenAddress: {operation.TokenAddress}"); //TODO: Transaction Failed return; } var trHash = await Erc20SharedService.StartDepositTransferAsync(_web3, _settings.EthereumCore, transactionSenderAddress, operation.FromAddress, operation.TokenAddress, operation.ToAddress); await _hotWalletTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = transaction.OperationId, TransactionHash = trHash }); var message = new CoinTransactionMessage() { OperationId = transaction.OperationId, TransactionHash = trHash }; //Observe transaction await _transactionMonitoringQueue.PutRawMessageAsync( Newtonsoft.Json.JsonConvert.SerializeObject(message)); var notificationMessage = new LykkePayErc20TransferNotificationMessage() { OperationId = transaction.OperationId, TransactionHash = trHash, Balance = balance.ToString() //At the starting moment(may change at the end of the execution) }; await _transactionStartedNotificationQueue.PutRawMessageAsync( Newtonsoft.Json.JsonConvert.SerializeObject(notificationMessage)); } catch (ClientSideException ex) { if (operation == null) { return; } TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.Failed, WorkflowType.LykkePay, DateTime.UtcNow); await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", operation.ToJson(), ex); await _rabbitQueuePublisher.PublshEvent(@event); } catch (Exception ex) { if (transaction == null) { return; } if (ex.Message != transaction.LastError) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), "transaction.OperationId"); } transaction.LastError = ex.Message; transaction.DequeueCount++; context.MoveMessageToEnd(transaction.ToJson()); context.SetCountQueueBasedDelay(_settings.EthereumCore.MaxQueueDelay, 200); await _logger.WriteErrorAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), ex); } }
public async Task PutTransactionToQueue(CoinTransactionMessage transaction) { await _coinTransationMonitoringQueue.PutRawMessageAsync(JsonConvert.SerializeObject(transaction)); }
/// <summary> /// /// </summary> /// <param name="hotWalletCashout"></param> /// <returns>transaction hash</returns> public async Task <string> StartCashoutAsync(string operationId) { IHotWalletOperation cashout = await _hotWalletCashoutRepository.GetAsync(operationId); if (cashout == null || cashout.OperationType != HotWalletOperationType.Cashout) { await _log.WriteWarningAsync(nameof(HotWalletService), nameof(StartCashoutAsync), $"operationId - {operationId}", "No cashout info for operation", DateTime.UtcNow); return(null); } var gasPrice = await _gasPriceRepository.GetAsync(); var currentGasPriceHex = await _web3.Eth.GasPrice.SendRequestAsync(); var currentGasPrice = currentGasPriceHex.Value; BigInteger selectedGasPrice = currentGasPrice * _baseSettings.GasPricePercentage / 100; if (selectedGasPrice > gasPrice.Max) { selectedGasPrice = gasPrice.Max; } else if (selectedGasPrice < gasPrice.Min) { selectedGasPrice = gasPrice.Min; } string transactionForSigning = null; string signedTransaction = null; string transactionHash = null; bool isErc20Transfer = !string.IsNullOrEmpty(cashout.TokenAddress); SemaphoreSlim semaphore = _semaphores.GetOrAdd(cashout.FromAddress, f => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(); //Erc20 transfer if (isErc20Transfer) { transactionForSigning = await _erc20PrivateWalletService.GetTransferTransactionRaw(new Lykke.Service.EthereumCore.BusinessModels.PrivateWallet.Erc20Transaction() { FromAddress = cashout.FromAddress, GasAmount = Constants.GasForHotWalletTransaction, GasPrice = selectedGasPrice, ToAddress = cashout.ToAddress, TokenAddress = cashout.TokenAddress, TokenAmount = cashout.Amount, Value = 0, }, useTxPool : true); } //Eth transfer else { transactionForSigning = await _privateWalletService.GetTransactionForSigning(new Lykke.Service.EthereumCore.BusinessModels.PrivateWallet.EthTransaction() { FromAddress = cashout.FromAddress, GasAmount = Constants.GasForHotWalletTransaction, GasPrice = selectedGasPrice, ToAddress = cashout.ToAddress, Value = cashout.Amount }, useTxPool : true); } signedTransaction = await _signatureService.SignRawTransactionAsync(cashout.FromAddress, transactionForSigning); if (string.IsNullOrEmpty(signedTransaction)) { throw new ClientSideException(ExceptionType.WrongSign, "Wrong signature"); } var transactionExecutionCosts = await _privateWalletService.EstimateTransactionExecutionCost(cashout.FromAddress, signedTransaction); if (!transactionExecutionCosts.IsAllowed) { throw new Exception($"Transaction will not be successfull {JsonSerialisersExt.ToJson(cashout)}"); } transactionHash = isErc20Transfer ? await _erc20PrivateWalletService.SubmitSignedTransaction(cashout.FromAddress, signedTransaction) : await _privateWalletService.SubmitSignedTransaction(cashout.FromAddress, signedTransaction); } finally { semaphore.Release(); } if (string.IsNullOrEmpty(transactionHash)) { throw new Exception("Transaction was not sent"); } CoinTransactionMessage message = new CoinTransactionMessage() { OperationId = operationId, TransactionHash = transactionHash }; await _hotWalletCashoutTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = operationId, TransactionHash = transactionHash }); await _hotWalletTransactionMonitoringQueue.PutRawMessageAsync(Newtonsoft.Json.JsonConvert.SerializeObject(message)); return(transactionHash); }
public async Task IndexCashinEventsForErc20Deposits() { var indexerStatusResponse = await _indexerApi.ApiSystemIsAliveGetWithHttpMessagesAsync(); if (indexerStatusResponse.Response.IsSuccessStatusCode) { var responseContent = await indexerStatusResponse.Response.Content.ReadAsStringAsync(); var indexerStatus = JObject.Parse(responseContent); var lastIndexedBlock = BigInteger.Parse(indexerStatus["blockchainTip"].Value <string>()); var lastSyncedBlock = await GetLastSyncedBlockNumber(Erc20HotWalletMarker); while (++lastSyncedBlock <= lastIndexedBlock - _baseSettings.Level2TransactionConfirmation) { var transfersResponse = await _indexerApi.ApiErc20TransferHistoryGetErc20TransfersPostAsync ( new GetErc20TransferHistoryRequest { AssetHolder = _settingsWrapper.Ethereum.HotwalletAddress?.ToLower(), BlockNumber = (long)lastSyncedBlock, } ); switch (transfersResponse) { case IEnumerable <Erc20TransferHistoryResponse> transfers: foreach (var transfer in transfers) { // Ignore transfers from not deposit contract addresses if (!await _depositContractService.ContainsAsync(transfer.FromProperty)) { continue; } var coinTransactionMessage = new CoinTransactionMessage { TransactionHash = transfer.TransactionHash }; await _cashinEventRepository.InsertAsync(new CashinEvent { CoinAdapterAddress = Erc20HotWalletMarker, Amount = transfer.TransferAmount, TransactionHash = transfer.TransactionHash, UserAddress = transfer.FromProperty, ContractAddress = transfer.Contract }); await _cointTransactionQueue.PutRawMessageAsync(JsonConvert.SerializeObject(coinTransactionMessage)); } break; case ApiException exception: throw new Exception($"Ethereum indexer responded with error: {exception.Error.Message}"); default: throw new Exception($"Ethereum indexer returned unexpected response"); } await _blockSyncedRepository.InsertAsync(new BlockSynced { BlockNumber = lastSyncedBlock.ToString(), CoinAdapterAddress = Erc20HotWalletMarker }); } } else { throw new Exception("Can not obtain ethereum indexer status."); } }
public async Task Execute(CoinTransactionMessage transaction, QueueTriggeringContext context) { ICoinTransaction coinTransaction = null; try { bool isTransactionInMemoryPool = await _ethereumTransactionService.IsTransactionInPool(transaction.TransactionHash); if (isTransactionInMemoryPool) { SendMessageToTheQueueEnd(context, transaction, 100, "Transaction is in memory pool"); return; } coinTransaction = await _coinTransactionService.ProcessTransaction(transaction); } catch (Exception ex) { if (ex.Message != transaction.LastError) { await _log.WriteWarningAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "Execute", $"TrHash: [{transaction.TransactionHash}]", ""); } SendMessageToTheQueueEnd(context, transaction, 200, ex.Message); await _log.WriteErrorAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "Execute", "", ex); return; } if (coinTransaction == null) { await RepeatOperationTillWin(transaction); await _slackNotifier.ErrorAsync($"Airlines: Transaction with hash {transaction.TransactionHash} has ERROR. RETRY. Address is yet blocked"); } else { if (coinTransaction.ConfirmationLevel >= CoinTransactionService.Level2Confirm) { bool sentToRabbit = await SendCompleteEvent(transaction.TransactionHash, transaction.OperationId, !coinTransaction.Error, context, transaction); if (sentToRabbit) { await _log.WriteInfoAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to rabbit queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } else { await _log.WriteInfoAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } if (coinTransaction.Error) { await _slackNotifier.ErrorAsync($"EthereumCoreService: HOTWALLET - Transaction with hash {transaction.TransactionHash} has an Error. Notify Caller about fail!"); } } else { SendMessageToTheQueueEnd(context, transaction, 100); await _log.WriteInfoAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } } }
//return whether we have sent to rabbit or not private async Task <bool> SendCompleteEvent(string transactionHash, string operationId, bool success, QueueTriggeringContext context, CoinTransactionMessage transaction) { try { var operation = await GetOperationAsync(transactionHash, operationId); if (operation == null) { return(false); } string amount; Lykke.Job.EthereumCore.Contracts.Enums.HotWalletEventType type; switch (operation.OperationType) { case HotWalletOperationType.Cashout: amount = operation.Amount.ToString(); type = Lykke.Job.EthereumCore.Contracts.Enums.HotWalletEventType.CashoutCompleted; break; case HotWalletOperationType.Cashin: await _hotWalletService.RemoveCashinLockAsync(operation.TokenAddress, operation.FromAddress); amount = (await _cashinEventRepository.GetAsync(transactionHash)).Amount; type = Lykke.Job.EthereumCore.Contracts.Enums.HotWalletEventType.CashinCompleted; break; default: return(false); } HotWalletEvent @event = new HotWalletEvent(operation.OperationId, transactionHash, operation.FromAddress, operation.ToAddress, amount, operation.TokenAddress, type); await _rabbitQueuePublisher.PublshEvent(@event); return(true); } catch (Exception e) { await _log.WriteErrorAsync(nameof(HotWalletMonitoringTransactionJob), "SendCompletedCoinEvent", $"trHash: {transactionHash}", e, DateTime.UtcNow); SendMessageToTheQueueEnd(context, transaction, 100); return(false); } }
//return whether we have sent to rabbit or not private async Task <bool> SendCompleteEvent(string transactionHash, string operationId, bool success, QueueTriggeringContext context, CoinTransactionMessage transaction) { try { var operation = await GetOperationAsync(transactionHash, operationId); if (operation == null) { return(false); } (BigInteger? amount, string blockHash, ulong blockNumber)transferedInfo = (null, null, 0); string amount = operation.Amount.ToString(); switch (operation.OperationType) { case HotWalletOperationType.Cashout: break; case HotWalletOperationType.Cashin: //There will be nothing to index in failed event if (success) { transferedInfo = await _transactionEventsService.IndexCashinEventsForErc20TransactionHashAsync( transactionHash); if (transferedInfo.amount == null || transferedInfo.amount == 0) { //Not yet indexed SendMessageToTheQueueEnd(context, transaction, 10000); return(false); } amount = transferedInfo.amount.ToString(); } break; default: return(false); } EventType eventType = success ? EventType.Completed : EventType.Failed; TransferEvent @event = new TransferEvent(operation.OperationId, transactionHash, amount, operation.TokenAddress, operation.FromAddress, operation.ToAddress, transferedInfo.blockHash, transferedInfo.blockNumber, SenderType.EthereumCore, eventType, WorkflowType.Airlines, DateTime.UtcNow); await _rabbitQueuePublisher.PublshEvent(@event); return(true); } catch (Exception e) { await _log.WriteErrorAsync(nameof(AirlinesHotWalletMonitoringTransactionJob), "SendCompletedCoinEvent", $"trHash: {transactionHash}", e, DateTime.UtcNow); SendMessageToTheQueueEnd(context, transaction, 100); return(false); } }
public async Task Execute(LykkePayErc20TransferMessage transaction, QueueTriggeringContext context) { IHotWalletOperation operation = null; if (string.IsNullOrEmpty(transaction?.OperationId)) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", "", "Empty message skipped"); return; } try { operation = await _operationsRepository.GetAsync(transaction.OperationId); if (operation == null) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"No operation for id {transaction?.OperationId} message skipped"); return; } var transactionSenderAddress = _settings.Airlines.AirlinesAddress; var balance = await _ercInterfaceService.GetPendingBalanceForExternalTokenAsync(operation.FromAddress, operation.TokenAddress); if (balance < operation.Amount) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"Sendig Failed Event: DepositAddress: {operation.FromAddress}, " + $"TokenAddress: {operation.TokenAddress}, " + $"DesiredAmount: {operation.Amount} " + $"CurrentBalance {balance}"); TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.NotEnoughFunds, WorkflowType.Airlines, DateTime.UtcNow); await _rabbitQueuePublisher.PublshEvent(@event); return; } var trHash = await Erc223SharedService.StartDepositTransferAsync(_web3, _settings.EthereumCore.Erc223DepositContract.Abi, transactionSenderAddress, operation.FromAddress, operation.TokenAddress, operation.ToAddress, operation.Amount); await _hotWalletTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = transaction.OperationId, TransactionHash = trHash }); var message = new CoinTransactionMessage() { OperationId = transaction.OperationId, TransactionHash = trHash }; //Observe transaction await _transactionMonitoringQueue.PutRawMessageAsync(message.ToJson()); var notificationMessage = new LykkePayErc20TransferNotificationMessage() { OperationId = transaction.OperationId, TransactionHash = trHash, Balance = operation.Amount.ToString() }; await _transactionStartedNotificationQueue.PutRawMessageAsync(notificationMessage.ToJson()); } catch (ClientSideException ex) { if (operation == null) { return; } TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.Failed, WorkflowType.Airlines, DateTime.UtcNow); _logger.WriteWarning("Execute", operation.ToJson(), "ClientSideException", ex); await _rabbitQueuePublisher.PublshEvent(@event); } catch (Exception ex) { if (transaction == null) { return; } if (ex.Message != transaction.LastError) { _logger.WriteWarning("Execute", transaction.ToJson(), $"{transaction.OperationId}"); } transaction.LastError = ex.Message; transaction.DequeueCount++; context.MoveMessageToEnd(transaction.ToJson()); context.SetCountQueueBasedDelay(_settings.EthereumCore.MaxQueueDelay, 200); _logger.WriteError("Execute", transaction.ToJson(), ex); } }
public async Task Execute(CoinTransactionMessage transaction, QueueTriggeringContext context) { ICoinTransaction coinTransaction = null; try { bool isTransactionInMemoryPool = await _ethereumTransactionService.IsTransactionInPool(transaction.TransactionHash); if (isTransactionInMemoryPool) { SendMessageToTheQueueEnd(context, transaction, 100, "Transaction is in memory pool"); return; } coinTransaction = await _coinTransactionService.ProcessTransaction(transaction); } catch (Exception ex) { if (ex.Message != transaction.LastError) { await _log.WriteWarningAsync("MonitoringCoinTransactionJob", "Execute", $"TrHash: [{transaction.TransactionHash}]", ""); } SendMessageToTheQueueEnd(context, transaction, 200, ex.Message); await _log.WriteErrorAsync("MonitoringCoinTransactionJob", "Execute", "", ex); return; } if ((coinTransaction == null) && (DateTime.UtcNow - transaction.PutDateTime > _broadcastMonitoringPeriodSeconds)) { await RepeatOperationTillWin(transaction); await _slackNotifier.ErrorAsync($"EthereumCoreService: Transaction with hash {transaction.TransactionHash} has no confirmations." + $" Reason - unable to find transaction in txPool and in blockchain within {_broadcastMonitoringPeriodSeconds} seconds"); } else { if (coinTransaction != null && coinTransaction.ConfirmationLevel >= CoinTransactionService.Level2Confirm) { if (!coinTransaction.Error) { bool sentToRabbit = await SendCompletedCoinEvent( transaction.TransactionHash, transaction.OperationId, true, context, transaction); if (sentToRabbit) { await _log.WriteInfoAsync("CoinTransactionService", "Execute", "", $"Put coin transaction {transaction.TransactionHash} to rabbit queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } else { await _log.WriteInfoAsync("CoinTransactionService", "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } } else { ICoinEvent coinEvent = await GetCoinEvent(transaction.TransactionHash, transaction.OperationId, true); await _slackNotifier.ErrorAsync($"EthereumCoreService: Transaction with hash {transaction.TransactionHash} has an Error!({coinEvent.CoinEventType})"); if (coinEvent.CoinEventType == CoinEventType.CashoutStarted || coinEvent.CoinEventType == CoinEventType.CashoutFailed) { if (coinTransaction.ConfirmationLevel >= 2) { //SEND FAILED CASHOUTS EVENTS HERE AND FILL Black LIST await _blackListAddressesRepository.SaveAsync(new BlackListAddress() { Address = coinEvent.ToAddress }); await SendCompletedCoinEvent(transaction.TransactionHash, transaction.OperationId, false, context, transaction); } else { SendMessageToTheQueueEnd(context, transaction, 200, "Did not recieve confirmation level 3 yet"); } return; } else { await RepeatOperationTillWin(transaction); await _slackNotifier.ErrorAsync($"EthereumCoreService: Transaction with hash {transaction.TransactionHash} has an Error. RETRY!({coinEvent.CoinEventType})"); } } } else { SendMessageToTheQueueEnd(context, transaction, 100); await _log.WriteInfoAsync("CoinTransactionService", "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } } }
//return whether we have sent to rabbit or not private async Task <bool> SendCompletedCoinEvent(string transactionHash, string operationId, bool success, QueueTriggeringContext context, CoinTransactionMessage transaction) { try { ICoinEvent coinEvent = await GetCoinEvent(transactionHash, operationId, success); switch (coinEvent.CoinEventType) { case CoinEventType.CashinStarted: ICashinEvent cashinEvent = await _transactionEventsService.GetCashinEvent(transactionHash); if (cashinEvent == null) { await _transactionEventsService.IndexEventsForTransaction(coinEvent.ContractAddress, transactionHash); SendMessageToTheQueueEnd(context, transaction, 100); return(false); } //transferContract - userAddress await UpdateUserTransferWallet(coinEvent.FromAddress, coinEvent.ToAddress.ToLower()); coinEvent.Amount = cashinEvent.Amount; coinEvent.CoinEventType++; break; case CoinEventType.CashoutStarted: case CoinEventType.TransferStarted: //Say that Event Is completed coinEvent.CoinEventType++; break; default: break; } #region FailedCashout if (coinEvent.CoinEventType == CoinEventType.CashoutCompleted && !success) { coinEvent.CoinEventType = CoinEventType.CashoutFailed; await _coinEventService.InsertAsync(coinEvent); SendMessageToTheQueueEnd(context, transaction, 200, "Put Failed cashout in the end of the queue"); return(false); } if (coinEvent.CoinEventType == CoinEventType.CashoutFailed && !success) { var historycal = await _pendingOperationService.GetHistoricalAsync(operationId); if (historycal != null) { foreach (var match in historycal) { if (!string.IsNullOrEmpty(match.TransactionHash) && await _ethereumTransactionService.IsTransactionExecuted(match.TransactionHash, Constants.GasForCoinTransaction)) { var @event = await _coinEventService.GetCoinEvent(match.TransactionHash); if (@event != null && @event.TransactionHash.ToLower() == match.TransactionHash.ToLower()) { await _slackNotifier.ErrorAsync($"EthereumCoreService: Transaction with hash {coinEvent.TransactionHash} [{coinEvent.OperationId}]" + $" ({coinEvent.CoinEventType}). Previously was successfully transfered"); return(false); } } } } } #endregion await _coinEventService.PublishEvent(coinEvent, putInProcessingQueue : false); await _pendingTransactionsRepository.Delete(transactionHash); await _pendingOperationService.MatchHashToOpId(transactionHash, coinEvent.OperationId); return(true); } catch (Exception e) { await _log.WriteErrorAsync("MonitoringCoinTransactionJob", "SendCompletedCoinEvent", $"trHash: {transactionHash}", e, DateTime.UtcNow); SendMessageToTheQueueEnd(context, transaction, 100); return(false); } }
//return whether we have sent to rabbit or not private async Task <bool> SendCompletedCoinEvent(string transactionHash, string operationId, bool success, QueueTriggeringContext context, CoinTransactionMessage transaction) { try { ICoinEvent coinEvent = await GetCoinEvent(transactionHash, operationId, success); switch (coinEvent.CoinEventType) { case CoinEventType.CashinStarted: ICashinEvent cashinEvent = await _transactionEventsService.GetCashinEvent(transactionHash); if (cashinEvent == null) { SendMessageToTheQueueEnd(context, transaction, 100); return(false); } //transferContract - userAddress await UpdateUserTransferWallet(coinEvent.FromAddress, coinEvent.ToAddress.ToLower()); coinEvent.Amount = cashinEvent.Amount; coinEvent.CoinEventType++; break; case CoinEventType.CashoutStarted: case CoinEventType.TransferStarted: //Say that Event Is completed coinEvent.CoinEventType++; break; default: break; } await _coinEventService.PublishEvent(coinEvent, putInProcessingQueue : false); await _pendingTransactionsRepository.Delete(transactionHash); await _pendingOperationService.MatchHashToOpId(transactionHash, coinEvent.OperationId); return(true); } catch (Exception e) { await _log.WriteErrorAsync("MonitoringCoinTransactionJob", "SendCompletedCoinEvent", $"trHash: {transactionHash}", e, DateTime.UtcNow); SendMessageToTheQueueEnd(context, transaction, 100); return(false); } }
public async Task Execute(CoinTransactionMessage transaction, QueueTriggeringContext context) { ICoinTransaction coinTransaction = null; try { bool isTransactionInMemoryPool = await _ethereumTransactionService.IsTransactionInPool(transaction.TransactionHash); if (isTransactionInMemoryPool) { SendMessageToTheQueueEnd(context, transaction, 100, "Transaction is in memory pool"); return; } coinTransaction = await _coinTransactionService.ProcessTransaction(transaction); } catch (Exception ex) { if (ex.Message != transaction.LastError) { await _log.WriteWarningAsync(nameof(HotWalletMonitoringTransactionJob), "Execute", $"TrHash: [{transaction.TransactionHash}]", ""); } SendMessageToTheQueueEnd(context, transaction, 200, ex.Message); await _log.WriteErrorAsync(nameof(HotWalletMonitoringTransactionJob), "Execute", "", ex); return; } if (coinTransaction == null || coinTransaction.Error) { await RepeatOperationTillWin(transaction); //await _slackNotifier.ErrorAsync($"EthereumCoreService: Transaction with hash {transaction.TransactionHash} has no confirmations." + // $" Reason - unable to find transaction in txPool and in blockchain within {_broadcastMonitoringPeriodSeconds} seconds"); } else { if (coinTransaction.ConfirmationLevel >= CoinTransactionService.Level2Confirm) { if (!coinTransaction.Error) { bool sentToRabbit = await SendCompleteEvent(transaction.TransactionHash, transaction.OperationId, true, context, transaction); if (sentToRabbit) { await _log.WriteInfoAsync(nameof(HotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to rabbit queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } else { await _log.WriteInfoAsync(nameof(HotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } } else { await _slackNotifier.ErrorAsync($"EthereumCoreService: HOTWALLET - Transaction with hash {transaction.TransactionHash} has an Error!"); await RepeatOperationTillWin(transaction); await _slackNotifier.ErrorAsync($"EthereumCoreService: HOTWALLET - Transaction with hash {transaction.TransactionHash} has an Error. RETRY!"); } } else { SendMessageToTheQueueEnd(context, transaction, 100); await _log.WriteInfoAsync(nameof(HotWalletMonitoringTransactionJob), "Execute", "", $"Put coin transaction {transaction.TransactionHash} to monitoring queue with confimation level {coinTransaction?.ConfirmationLevel ?? 0}"); } } }