예제 #1
0
        public async Task RegisterWalletAsync(DepositWalletKey key)
        {
            var client = _blockchainApiClientProvider.Get(key.BlockchainId);
            await _enrolledBalanceRepository.SetBalanceAsync(key, 0, 0);

            await client.StartBalanceObservationAsync(key.WalletAddress);
        }
        public async Task <string> BuildTransactionAsync(
            Guid operationId,
            string blockchainId,
            string blockchainAssetId,
            string fromAddress,
            string fromAddressContext,
            string toAddress,
            string amount,
            bool includeFee
            )
        {
            var client = _blockchainApiClientProvider.Get(blockchainId);
            var asset  = await _assetService.GetBlockchainAsset(blockchainId, blockchainAssetId);

            var amountDecimal      = ConverterExtensions.ConvertFromString(amount, asset.Accuracy, asset.Accuracy);
            var buildedTransaction = await client.BuildSingleTransactionAsync(
                operationId,
                fromAddress,
                fromAddressContext,
                toAddress,
                asset,
                amountDecimal,
                includeFee
                );

            return(buildedTransaction.TransactionContext);
        }
        public async Task <BlockchainAsset> GetBlockchainAsset(string blockchainId, string blockchainAssetId)
        {
            if (!_assets.TryGetValue(blockchainAssetId, out var asset))
            {
                return(null);
            }

            //if (_dictionary.TryGetValue(asset.BlockchainId, out var value))
            //{
            //    if (value.TryGetValue(blockchainAssetId, out var blockchainAsset))
            //    {
            //        return blockchainAsset;
            //    }
            //}

            var client = _blockchainApiClientProvider.Get(blockchainId);
            var assets = await client.GetAllAssetsAsync(100);

            if (!assets.TryGetValue(asset.BlockchainAssetId, out var blockchainAsset))
            {
                return(null);
            }

            return(blockchainAsset);
        }
        public DepositWalletsBalanceProcessingPeriodicalHandler(
            ILogFactory logFactory,
            TimeSpan period,
            int batchSize,
            string blockchainType,
            IBlockchainApiClientProvider blockchainApiClientProvider,
            ICqrsEngine cqrsEngine,
            IAssetsServiceWithCache assetsService,
            IEnrolledBalanceRepository enrolledBalanceRepository,
            IHotWalletsProvider hotWalletsProvider,
            ICashinRepository cashinRepository,
            IDepositWalletLockRepository depositWalletLockRepository,
            IChaosKitty chaosKitty)
        {
            _logFactory                  = logFactory;
            _batchSize                   = batchSize;
            _blockchainType              = blockchainType;
            _blockchainApiClient         = blockchainApiClientProvider.Get(blockchainType);
            _cqrsEngine                  = cqrsEngine;
            _assetsService               = assetsService;
            _enrolledBalanceRepository   = enrolledBalanceRepository;
            _hotWalletsProvider          = hotWalletsProvider;
            _cashinRepository            = cashinRepository;
            _depositWalletLockRepository = depositWalletLockRepository;
            _chaosKitty                  = chaosKitty;

            _timer = new TimerTrigger(
                $"{nameof(DepositWalletsBalanceProcessingPeriodicalHandler)} : {blockchainType}",
                period,
                _logFactory);

            _timer.Triggered += ProcessBalancesAsync;
        }
예제 #5
0
        /// <exception cref="ArgumentValidationException"></exception>
        private Task <IBlockchainApiClient> ThrowOnNotSupportedBlockchainType(string blockchainType)
        {
            IBlockchainApiClient blockchainClient;

            try
            {
                blockchainClient = _blockchainApiClientProvider.Get(blockchainType); //throws
            }
            catch (ArgumentValidationException)
            {
                throw new ArgumentValidationException($"{blockchainType} is not a valid type", "blockchainType");
            }


            return(Task.FromResult(blockchainClient));
        }
        public async Task<CommandHandlingResult> Handle(ClearBroadcastedTransactionCommand command, IEventPublisher publisher)
        {
            var apiClient = _apiClientProvider.Get(command.BlockchainType);

            await apiClient.ForgetBroadcastedTransactionsAsync(command.TransactionId);

            _chaosKitty.Meow(command.TransactionId);

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

            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."
                      );
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="cashoutModel"></param>
        /// <returns>
        /// ValidationError - client error
        /// ArgumentValidationException - developer error
        /// </returns>
        public async Task <IReadOnlyCollection <ValidationError> > ValidateAsync(CashoutModel cashoutModel)
        {
            var errors = new List <ValidationError>(1);

            if (cashoutModel == null)
            {
                return(FieldNotValidResult("cashoutModel can't be null"));
            }

            if (string.IsNullOrEmpty(cashoutModel.AssetId))
            {
                return(FieldNotValidResult("cashoutModel.AssetId can't be null or empty"));
            }

            Asset asset;

            try
            {
                asset = await _assetsService.TryGetAssetAsync(cashoutModel.AssetId);
            }
            catch (Exception)
            {
                throw new ArgumentValidationException($"Asset with Id-{cashoutModel.AssetId} does not exists",
                                                      "assetId");
            }

            if (asset.IsDisabled)
            {
                errors.Add(ValidationError.Create(ValidationErrorType.None, $"Asset {asset.Id} is disabled"));
            }

            if (asset == null)
            {
                throw new ArgumentValidationException($"Asset with Id-{cashoutModel.AssetId} does not exists",
                                                      "assetId");
            }

            var isAddressValid = true;
            IBlockchainApiClient blockchainClient = null;

            if (asset.Id != LykkeConstants.SolarAssetId)
            {
                if (string.IsNullOrEmpty(asset.BlockchainIntegrationLayerId))
                {
                    throw new ArgumentValidationException(
                              $"Given asset Id-{cashoutModel.AssetId} is not a part of Blockchain Integration Layer",
                              "assetId");
                }

                blockchainClient = _blockchainApiClientProvider.Get(asset.BlockchainIntegrationLayerId);
            }

            if (string.IsNullOrEmpty(cashoutModel.DestinationAddress) ||
                !cashoutModel.DestinationAddress.IsValidPartitionOrRowKey() ||
                asset.Id != LykkeConstants.SolarAssetId && blockchainClient != null &&
                !await blockchainClient.IsAddressValidAsync(cashoutModel.DestinationAddress) ||
                asset.Id == LykkeConstants.SolarAssetId &&
                !SolarCoinValidation.ValidateAddress(cashoutModel.DestinationAddress)
                )
            {
                isAddressValid = false;
                errors.Add(ValidationError.Create(ValidationErrorType.AddressIsNotValid, "Address is not valid"));
            }

            if (isAddressValid)
            {
                if (asset.Id != LykkeConstants.SolarAssetId)
                {
                    var isBlocked = await _blackListService.IsBlockedWithoutAddressValidationAsync
                                    (
                        asset.BlockchainIntegrationLayerId,
                        cashoutModel.DestinationAddress
                                    );

                    if (isBlocked)
                    {
                        errors.Add(ValidationError.Create(ValidationErrorType.BlackListedAddress, "Address is in the black list"));
                    }
                }

                if (cashoutModel.Volume.HasValue && Math.Abs(cashoutModel.Volume.Value) < (decimal)asset.CashoutMinimalAmount)
                {
                    var minimalAmount = asset.CashoutMinimalAmount.GetFixedAsString(asset.Accuracy).TrimEnd('0');

                    errors.Add(ValidationError.Create(ValidationErrorType.LessThanMinCashout,
                                                      $"Please enter an amount greater than {minimalAmount}"));
                }

                if (asset.Id != LykkeConstants.SolarAssetId)
                {
                    var blockchainSettings = _blockchainSettingsProvider.Get(asset.BlockchainIntegrationLayerId);

                    if (cashoutModel.DestinationAddress == blockchainSettings.HotWalletAddress)
                    {
                        errors.Add(ValidationError.Create(ValidationErrorType.HotwalletTargetProhibited,
                                                          "Hot wallet as destitnation address prohibited"));
                    }

                    var isPublicExtensionRequired =
                        _blockchainWalletsCacheService.IsPublicExtensionRequired(asset.BlockchainIntegrationLayerId);
                    if (isPublicExtensionRequired)
                    {
                        var hotWalletParseResult = await _blockchainWalletsClient.ParseAddressAsync(
                            asset.BlockchainIntegrationLayerId,
                            blockchainSettings.HotWalletAddress);

                        var destAddressParseResult = await _blockchainWalletsClient.ParseAddressAsync(
                            asset.BlockchainIntegrationLayerId,
                            cashoutModel.DestinationAddress);

                        if (hotWalletParseResult.BaseAddress == destAddressParseResult.BaseAddress)
                        {
                            var existedClientIdAsDestination = await _blockchainWalletsClient.TryGetClientIdAsync(
                                asset.BlockchainIntegrationLayerId,
                                cashoutModel.DestinationAddress);

                            if (existedClientIdAsDestination == null)
                            {
                                errors.Add(ValidationError.Create(ValidationErrorType.DepositAddressNotFound,
                                                                  $"Deposit address {cashoutModel.DestinationAddress} not found"));
                            }
                        }

                        var forbiddenCharacterErrors = await ValidateForForbiddenCharsAsync(
                            destAddressParseResult.BaseAddress,
                            destAddressParseResult.AddressExtension,
                            asset.BlockchainIntegrationLayerId);

                        if (forbiddenCharacterErrors != null)
                        {
                            errors.AddRange(forbiddenCharacterErrors);
                        }

                        if (!string.IsNullOrEmpty(destAddressParseResult.BaseAddress))
                        {
                            if (!cashoutModel.DestinationAddress.Contains(destAddressParseResult.BaseAddress))
                            {
                                errors.Add(ValidationError.Create(ValidationErrorType.FieldIsNotValid,
                                                                  "Base Address should be part of destination address"));
                            }

                            // full address is already checked by integration,
                            // we don't need to validate it again,
                            // just ensure that base address is not black-listed
                            var isBlockedBase = await _blackListService.IsBlockedWithoutAddressValidationAsync(
                                asset.BlockchainIntegrationLayerId,
                                destAddressParseResult.BaseAddress);

                            if (isBlockedBase)
                            {
                                errors.Add(ValidationError.Create(ValidationErrorType.BlackListedAddress,
                                                                  "Base Address is in the black list"));
                            }
                        }
                    }
                }

                if (cashoutModel.ClientId.HasValue)
                {
                    var destinationClientId = await _blockchainWalletsClient.TryGetClientIdAsync
                                              (
                        asset.BlockchainIntegrationLayerId,
                        cashoutModel.DestinationAddress
                                              );

                    if (destinationClientId.HasValue && destinationClientId == cashoutModel.ClientId.Value)
                    {
                        var error = ValidationError.Create
                                    (
                            ValidationErrorType.CashoutToSelfAddress,
                            "Withdrawals to the deposit wallet owned by the customer himself prohibited"
                                    );

                        errors.Add(error);
                    }
                }
            }

            return(errors);
        }