Esempio n. 1
0
        private async Task <int> QueryAndProcessTransactions(string address, TransactionContext context, Func <TxDirectionType, TxHistory, Task <bool> > process)
        {
            var transactions = await _horizonService.GetTransactions(address, OrderDirection.ASC, context.Cursor);

            var count = 0;

            context.Cursor = null;
            foreach (var transaction in transactions)
            {
                try
                {
                    context.Cursor = transaction.PagingToken;
                    count++;

                    var xdr        = Convert.FromBase64String(transaction.EnvelopeXdr);
                    var reader     = new XdrDataInputStream(xdr);
                    var txEnvelope = TransactionEnvelope.Decode(reader);
                    var tx         = txEnvelope.V1;
                    var operations = txEnvelope?.V1?.Tx?.Operations ?? txEnvelope.V0.Tx.Operations;

                    for (short i = 0; i < operations.Length; i++)
                    {
                        var operation     = operations[i];
                        var operationType = operation.Body.Discriminant.InnerValue;

                        DateTime.TryParse(transaction.CreatedAt, out var createdAt);
                        var history = new TxHistory
                        {
                            FromAddress    = transaction.SourceAccount,
                            AssetId        = _blockchainAssetsService.GetNativeAsset().Id,
                            Hash           = transaction.Hash,
                            OperationIndex = i,
                            PagingToken    = transaction.PagingToken,
                            CreatedAt      = createdAt,
                            Memo           = _horizonService.GetMemo(transaction)
                        };

                        // ReSharper disable once SwitchStatementMissingSomeCases
                        switch (operationType)
                        {
                        case OperationType.OperationTypeEnum.CREATE_ACCOUNT:
                        {
                            var op      = operation.Body.CreateAccountOp;
                            var keyPair = KeyPair.FromXdrPublicKey(op.Destination.InnerValue);
                            history.ToAddress   = keyPair.Address;
                            history.Amount      = op.StartingBalance.InnerValue;
                            history.PaymentType = PaymentType.CreateAccount;
                            break;
                        }

                        case OperationType.OperationTypeEnum.PAYMENT:
                        {
                            var op = operation.Body.PaymentOp;
                            if (op.Asset.Discriminant.InnerValue == AssetType.AssetTypeEnum.ASSET_TYPE_NATIVE)
                            {
                                var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                                history.ToAddress   = keyPair.Address;
                                history.Amount      = op.Amount.InnerValue;
                                history.PaymentType = PaymentType.Payment;
                            }
                            break;
                        }

                        case OperationType.OperationTypeEnum.ACCOUNT_MERGE:
                        {
                            var op      = operation.Body;
                            var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                            history.ToAddress   = keyPair.Address;
                            history.Amount      = _horizonService.GetAccountMergeAmount(transaction.ResultXdr, i);
                            history.PaymentType = PaymentType.AccountMerge;
                            break;
                        }

                        case OperationType.OperationTypeEnum.PATH_PAYMENT_STRICT_RECEIVE:
                        {
                            var op = operation.Body.PathPaymentStrictReceiveOp;
                            if (op.DestAsset.Discriminant.InnerValue == AssetType.AssetTypeEnum.ASSET_TYPE_NATIVE)
                            {
                                var keyPair = KeyPair.FromPublicKey(op.Destination.Ed25519.InnerValue);
                                history.ToAddress   = keyPair.Address;
                                history.Amount      = op.DestAmount.InnerValue;
                                history.PaymentType = PaymentType.PathPayment;
                            }
                            break;
                        }

                        default:
                            continue;
                        }

                        if (!ForbiddenCharacterAzureStorageUtils.IsValidRowKey(history.Memo))
                        {
                            await _log.WriteErrorAsync(nameof(TransactionHistoryService),
                                                       nameof(QueryAndProcessTransactions),
                                                       history.Memo,
                                                       new Exception("Possible cashin skipped. It has forbiddden characters in memo."));

                            continue;
                        }

                        var cancel = false;
                        if (address.Equals(history.ToAddress, StringComparison.OrdinalIgnoreCase))
                        {
                            cancel = await process(TxDirectionType.Incoming, history);
                        }
                        if (address.Equals(history.FromAddress, StringComparison.OrdinalIgnoreCase))
                        {
                            cancel = await process(TxDirectionType.Outgoing, history);
                        }
                        if (cancel)
                        {
                            return(count);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new BusinessException($"Failed to process transaction. hash={transaction?.Hash}", ex);
                }
            }

            return(count);
        }
        private async Task <(int Count, string Cursor)> ProcessDeposits(string cursor)
        {
            var transactions = await _horizonService.GetTransactions(_depositBaseAddress, OrderDirection.ASC, cursor);

            var count            = 0;
            var walletsToRefresh = new HashSet <(string assetId, string address)>();
            var asset            = _blockchainAssetsService.GetNativeAsset();

            cursor = null;
            foreach (var transaction in transactions)
            {
                try
                {
                    cursor = transaction.PagingToken;
                    count++;

                    // skip outgoing transactions and transactions without memo
                    var memo = _horizonService.GetMemo(transaction);
                    if (_depositBaseAddress.Equals(transaction.SourceAccount, StringComparison.OrdinalIgnoreCase) ||
                        string.IsNullOrWhiteSpace(memo))
                    {
                        continue;
                    }

                    // transaction XDR doesn't contain operation IDs,
                    // make a dedicated request to get operations
                    var operations = await _horizonService.GetTransactionOperations(transaction.Hash);

                    if (operations == null)
                    {
                        continue;
                    }

                    foreach (var op in operations)
                    {
                        string toAddress = null;
                        long   amount    = 0;

                        switch (op.Type.ToLower())
                        {
                        case "payment":
                            var payment = (PaymentOperationResponse)op;
                            if (payment.AssetType == "native")
                            {
                                toAddress = payment.To;
                                amount    = asset.ParseDecimal(payment.Amount);
                            }
                            break;

                        case "account_merge":
                            var accountMerge = (AccountMergeOperationResponse)op;
                            toAddress = accountMerge.Into;
                            amount    = _horizonService.GetAccountMergeAmount(transaction.ResultMetaXdr, accountMerge.SourceAccount);
                            break;

                        case "path_payment":
                            var pathPayment = (PathPaymentStrictReceiveOperationResponse)op;
                            if (pathPayment.AssetType == "native")
                            {
                                toAddress = pathPayment.To;
                                amount    = asset.ParseDecimal(pathPayment.Amount);
                            }
                            break;

                        default:
                            continue;
                        }

                        var addressWithExtension = $"{toAddress}{Constants.PublicAddressExtension.Separator}{memo.ToLower()}";
                        if (!ForbiddenCharacterAzureStorageUtils.IsValidRowKey(memo))
                        {
                            await _log.WriteErrorAsync(nameof(BalanceService),
                                                       nameof(ProcessDeposits),
                                                       addressWithExtension,
                                                       new Exception("Possible cashin skipped. It has forbiddden characters in memo."));

                            continue;
                        }

                        var observation = await _observationRepository.GetAsync(addressWithExtension);

                        if (observation == null)
                        {
                            continue;
                        }

                        await _walletBalanceRepository.RecordOperationAsync(asset.Id, addressWithExtension, transaction.Ledger * 10, op.Id, transaction.Hash, amount);

                        walletsToRefresh.Add((asset.Id, addressWithExtension));
                    }
                }
                catch (Exception ex)
                {
                    throw new BusinessException($"Failed to process transaction. hash={transaction?.Hash}", ex);
                }
            }

            await _walletBalanceRepository.RefreshBalance(walletsToRefresh);

            if (!string.IsNullOrEmpty(cursor))
            {
                await _keyValueStoreRepository.SetAsync(GetPagingTokenKey, cursor);
            }

            return(count, cursor);
        }