Exemplo n.º 1
0
        public string UrlForValidFeeQuotesKey(UserAndIssuer userAndIssuer, bool anonymous = false)
        {
            string url = GetBaseUrl() + $"?valid=true";

            if (anonymous)
            {
                url += $"&anonymous=true";
            }
            return(UrlWithIdentity(url, userAndIssuer));
        }
        public FeeQuote GetCurrentFeeQuoteByIdentity(UserAndIssuer identity)
        {
            string file     = Path.Combine(GetFunctionalTestSrcRoot(), "Mock", "MockQuotes", FeeFileName);
            string jsonData = File.ReadAllText(file);

            // check json
            List <FeeQuote> feeQuotes = JsonConvert.DeserializeObject <List <FeeQuote> >(jsonData);

            feeQuotes.Where(x => x.CreatedAt == DateTime.MinValue).ToList().ForEach(x => x.CreatedAt = x.ValidFrom = clock.UtcNow());
            _feeQuotes = new List <FeeQuote>();
            _feeQuotes.AddRange(feeQuotes.OrderBy(x => x.CreatedAt));
            return(GetCurrentFeeQuoteByIdentityFromLoadedFeeQuotes(identity));
        }
Exemplo n.º 3
0
        protected void SetPoliciesForCurrentFeeQuote(string policiesJsonString, UserAndIssuer userAndIssuer = null)
        {
            var feeQuote = FeeQuoteRepository.GetCurrentFeeQuoteByIdentity(userAndIssuer);

            FeeQuoteRepositoryPostgres.EmptyRepository(DbConnectionStringDDL);

            feeQuote.Policies = policiesJsonString;

            using (MockedClock.NowIs(DateTime.UtcNow.AddMinutes(-1)))
            {
                if (FeeQuoteRepository.InsertFeeQuoteAsync(feeQuote).Result == null)
                {
                    throw new Exception("Can not insert test fee quote with policies.");
                }
            }
        }
Exemplo n.º 4
0
 protected void InsertFeeQuote(UserAndIssuer userAndIssuer = null)
 {
     using (MockedClock.NowIs(DateTime.UtcNow.AddMinutes(-1)))
     {
         var feeQuote = new FeeQuote
         {
             Id               = 1,
             CreatedAt        = MockedClock.UtcNow,
             ValidFrom        = MockedClock.UtcNow,
             Identity         = userAndIssuer?.Identity,
             IdentityProvider = userAndIssuer?.IdentityProvider,
             Fees             = new[] {
                 new Fee {
                     FeeType   = Const.FeeType.Standard,
                     MiningFee = new FeeAmount {
                         Satoshis      = 500,
                         Bytes         = 1000,
                         FeeAmountType = Const.AmountType.MiningFee
                     },
                     RelayFee = new FeeAmount {
                         Satoshis      = 250,
                         Bytes         = 1000,
                         FeeAmountType = Const.AmountType.RelayFee
                     },
                 },
                 new Fee {
                     FeeType   = Const.FeeType.Data,
                     MiningFee = new FeeAmount {
                         Satoshis      = 500,
                         Bytes         = 1000,
                         FeeAmountType = Const.AmountType.MiningFee
                     },
                     RelayFee = new FeeAmount {
                         Satoshis      = 250,
                         Bytes         = 1000,
                         FeeAmountType = Const.AmountType.RelayFee
                     },
                 },
             }
         };
         if (FeeQuoteRepository.InsertFeeQuoteAsync(feeQuote).Result == null)
         {
             throw new Exception("Can not insert test fee quote");
         }
     }
 }
Exemplo n.º 5
0
        private string UrlWithIdentity(string url, UserAndIssuer userAndIssuer)
        {
            if (userAndIssuer == null)
            {
                return(url);
            }
            url = (!url.Contains("?")) ? url += "?" : url += "&";
            List <string> userParams = new List <string>();

            if (userAndIssuer.Identity != null)
            {
                userParams.Add($"identity={HttpUtility.UrlEncode(userAndIssuer.Identity)}");
            }
            if (userAndIssuer.IdentityProvider != null)
            {
                userParams.Add($"identityProvider={HttpUtility.UrlEncode(userAndIssuer.IdentityProvider)}");
            }
            return(url + String.Join("&", userParams));
        }
        /// <summary>
        /// Extract user and issuer from authenticated user. Returns null if not found or if user is not authenticated
        /// </summary>
        /// <param name="user"></param>
        /// <returns>
        /// false: if we were not able to perform authentication because token was not formatted correctly and there was exception during authorization
        /// When false is returned the request should be rejected
        /// true: if there was no token (anonymous user) or we were able to extract user identity
        /// </returns>
        public static bool GetUserAndIssuer(ClaimsPrincipal user, IHeaderDictionary headers, out UserAndIssuer result)
        {
            result = null;
            bool tokenPresentInRequest = headers.TryGetValue("Authorization", out var authorizationHeader) && authorizationHeader.Any(x => x.StartsWith("Bearer"));

            // User can have multiple identities. Find the right one
            var theIdentity = user.Identities.FirstOrDefault(ci => ci.IsAuthenticated && ci.HasClaim(c => c.Type == UnifiedIdentityClaimName));

            if (theIdentity == null)
            {
                return(!tokenPresentInRequest);
            }

            result = new UserAndIssuer
            {
                Identity         = theIdentity.FindFirst(c => c.Type == UnifiedIdentityClaimName).Value,
                IdentityProvider = theIdentity.FindFirst(c => c.Type == JwtRegisteredClaimNames.Iss).Value,
            };
            return(true);
        }
        public IEnumerable <FeeQuote> GetValidFeeQuotesByIdentity(UserAndIssuer feeQuoteIdentity)
        {
            if (_feeQuotes == null)
            {
                GetCurrentFeeQuoteByIdentity(feeQuoteIdentity); // fill from filename
            }
            var filtered = _feeQuotes.Where(x => x.Identity == feeQuoteIdentity?.Identity &&
                                            x.IdentityProvider == feeQuoteIdentity?.IdentityProvider &&
                                            x.ValidFrom <= MockedClock.UtcNow &&
                                            x.ValidFrom >= MockedClock.UtcNow.AddMinutes(-quoteExpiryMinutes)).ToArray();

            if (!filtered.Any())
            {
                var quote = GetCurrentFeeQuoteByIdentityFromLoadedFeeQuotes(feeQuoteIdentity);
                return(new List <FeeQuote>()
                {
                    quote
                });
            }
            return(filtered);
        }
 public IEnumerable <FeeQuote> GetFeeQuotesByIdentity(UserAndIssuer identity)
 {
     throw new NotImplementedException();
 }
 private FeeQuote GetCurrentFeeQuoteByIdentityFromLoadedFeeQuotes(UserAndIssuer identity)
 {
     return(_feeQuotes.LastOrDefault(x =>
                                     identity?.Identity == x?.Identity && identity?.IdentityProvider == x?.IdentityProvider));
 }
Exemplo n.º 10
0
        public async Task <SubmitTransactionsResponse> SubmitTransactionsAsync(IEnumerable <SubmitTransaction> requestEnum, UserAndIssuer user)
        {
            var request = requestEnum.ToArray();

            logger.LogInformation($"Processing {request.Length} incoming transactions");
            // Take snapshot of current metadata and use use it for all transactions
            var info           = blockChainInfo.GetInfo();
            var currentMinerId = await minerId.GetCurrentMinerIdAsync();

            var consolidationParameters = info.ConsolidationTxParameters;

            // Use the same quotes for all transactions in single request
            var quotes = feeQuoteRepository.GetValidFeeQuotesByIdentity(user).ToArray();

            if (quotes == null || !quotes.Any())
            {
                throw new Exception("No fee quotes available");
            }

            var responses            = new List <SubmitTransactionOneResponse>();
            var transactionsToSubmit = new List <(string transactionId, SubmitTransaction transaction, bool allowhighfees, bool dontCheckFees)>();
            int failureCount         = 0;

            IDictionary <uint256, byte[]> allTxs = new Dictionary <uint256, byte[]>();

            foreach (var oneTx in request)
            {
                if ((oneTx.RawTx == null || oneTx.RawTx.Length == 0) && string.IsNullOrEmpty(oneTx.RawTxString))
                {
                    AddFailureResponse(null, $"{nameof(SubmitTransaction.RawTx)} is required", ref responses);

                    failureCount++;
                    continue;
                }

                if (oneTx.RawTx == null)
                {
                    try
                    {
                        oneTx.RawTx = HelperTools.HexStringToByteArray(oneTx.RawTxString);
                    }
                    catch (Exception ex)
                    {
                        AddFailureResponse(null, ex.Message, ref responses);

                        failureCount++;
                        continue;
                    }
                }
                uint256 txId       = Hashes.DoubleSHA256(oneTx.RawTx);
                string  txIdString = txId.ToString();

                if (allTxs.ContainsKey(txId))
                {
                    AddFailureResponse(txIdString, "Transaction with this id occurs more than once within request", ref responses);

                    failureCount++;
                    continue;
                }

                var vc     = new ValidationContext(oneTx);
                var errors = oneTx.Validate(vc);
                if (errors.Count() > 0)
                {
                    AddFailureResponse(txIdString, string.Join(",", errors.Select(x => x.ErrorMessage)), ref responses);

                    failureCount++;
                    continue;
                }
                allTxs.Add(txId, oneTx.RawTx);
                bool okToMine  = false;
                bool okToRelay = false;
                if (await txRepository.TransactionExistsAsync(txId.ToBytes()))
                {
                    AddFailureResponse(txIdString, "Transaction already known", ref responses);

                    failureCount++;
                    continue;
                }

                Transaction    transaction    = null;
                CollidedWith[] colidedWith    = {};
                Exception      exception      = null;
                string[]       prevOutsErrors = { };
                try
                {
                    transaction = HelperTools.ParseBytesToTransaction(oneTx.RawTx);

                    if (transaction.IsCoinBase)
                    {
                        throw new ExceptionWithSafeErrorMessage("Invalid transaction - coinbase transactions are not accepted");
                    }
                    var(sumPrevOuputs, prevOuts) = await CollectPreviousOuputs(transaction, new ReadOnlyDictionary <uint256, byte[]>(allTxs), rpcMultiClient);

                    prevOutsErrors = prevOuts.Where(x => !string.IsNullOrEmpty(x.Error)).Select(x => x.Error).ToArray();
                    colidedWith    = prevOuts.Where(x => x.CollidedWith != null).Select(x => x.CollidedWith).ToArray();

                    if (IsConsolidationTxn(transaction, consolidationParameters, prevOuts))
                    {
                        (okToMine, okToRelay) = (true, true);
                    }
                    else
                    {
                        foreach (var feeQuote in quotes)
                        {
                            var(okToMineTmp, okToRelayTmp) =
                                CheckFees(transaction, oneTx.RawTx.LongLength, sumPrevOuputs, feeQuote);
                            if (GetCheckFeesValue(okToMineTmp, okToRelayTmp) > GetCheckFeesValue(okToMine, okToRelay))
                            {
                                // save best combination
                                (okToMine, okToRelay) = (okToMineTmp, okToRelayTmp);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    exception = ex;
                }

                if (exception != null || colidedWith.Any() || transaction == null || prevOutsErrors.Any())
                {
                    var oneResponse = new SubmitTransactionOneResponse
                    {
                        Txid         = txIdString,
                        ReturnResult = ResultCodes.Failure,
                        // Include non null ConflictedWith only if a collision has been detected
                        ConflictedWith = !colidedWith.Any() ? null : colidedWith.Select(
                            x => new SubmitTransactionConflictedTxResponse
                        {
                            Txid = x.TxId,
                            Size = x.Size,
                            Hex  = x.Hex,
                        }).ToArray()
                    };

                    if (transaction is null)
                    {
                        oneResponse.ResultDescription = "Can not parse transaction";
                    }
                    else if (exception is ExceptionWithSafeErrorMessage)
                    {
                        oneResponse.ResultDescription = exception.Message;
                    }
                    else if (exception != null)
                    {
                        oneResponse.ResultDescription = "Error fetching inputs";
                    }
                    else // colidedWith !=null and there is no exception or prevOutsErrors is not empty
                    {
                        // return "Missing inputs" regardless of error returned from gettxouts (which is usually "missing")
                        oneResponse.ResultDescription = "Missing inputs";
                    }
                    logger.LogError($"Can not calculate fee for {txIdString}. Error: {oneResponse.ResultDescription} Exception: {exception?.ToString() ?? ""}");


                    responses.Add(oneResponse);
                    failureCount++;
                    continue;
                }

                // Transactions  was successfully analyzed
                if (!okToMine && !okToRelay)
                {
                    AddFailureResponse(txIdString, "Not enough fees", ref responses);

                    failureCount++;
                }
                else
                {
                    bool allowHighFees = false;
                    bool dontcheckfee  = okToMine;

                    oneTx.TransactionInputs = transaction.Inputs.AsIndexedInputs().Select(x => new TxInput
                    {
                        N        = x.Index,
                        PrevN    = x.PrevOut.N,
                        PrevTxId = x.PrevOut.Hash.ToBytes()
                    }).ToList();
                    transactionsToSubmit.Add((txIdString, oneTx, allowHighFees, dontcheckfee));
                }
            }

            RpcSendTransactions rpcResponse;

            Exception submitException = null;

            if (transactionsToSubmit.Any())
            {
                // Submit all collected transactions in one call
                try
                {
                    rpcResponse = await rpcMultiClient.SendRawTransactionsAsync(
                        transactionsToSubmit.Select(x => (x.transaction.RawTx, x.allowhighfees, x.dontCheckFees))
                        .ToArray());
                }
                catch (Exception ex)
                {
                    submitException = ex;
                    rpcResponse     = null;
                }
            }
            else
            {
                // Simulate empty response
                rpcResponse = new RpcSendTransactions();
            }


            // Initialize common fields
            var result = new SubmitTransactionsResponse
            {
                Timestamp = clock.UtcNow(),
                MinerId   = currentMinerId,
                CurrentHighestBlockHash   = info.BestBlockHash,
                CurrentHighestBlockHeight = info.BestBlockHeight,
                // TxSecondMempoolExpiry
                // Remaining of the fields are initialized bellow
            };

            if (submitException != null)
            {
                var unableToSubmit = transactionsToSubmit.Select(x =>
                                                                 new SubmitTransactionOneResponse
                {
                    Txid              = x.transactionId,
                    ReturnResult      = ResultCodes.Failure,
                    ResultDescription = "Error while submitting transactions to the node" // do not expose detailed error message. It might contain internal IPS etc
                });

                logger.LogError($"Error while submitting transactions to the node {submitException}");
                responses.AddRange(unableToSubmit);
                result.Txs          = responses.ToArray();
                result.FailureCount = result.Txs.Length; // all of the transactions have failed

                return(result);
            }
            else // submitted without error
            {
                var(submitFailureCount, transformed) = TransformRpcResponse(rpcResponse,
                                                                            transactionsToSubmit.Select(x => x.transactionId).ToArray());
                responses.AddRange(transformed);
                result.Txs          = responses.ToArray();
                result.FailureCount = failureCount + submitFailureCount;

                var successfullTxs = transactionsToSubmit.Where(x => transformed.Any(y => y.ReturnResult == ResultCodes.Success && y.Txid == x.transactionId));
                await txRepository.InsertTxsAsync(successfullTxs.Select(x => new Tx
                {
                    CallbackToken = x.transaction.CallbackToken,
                    CallbackUrl = x.transaction.CallbackUrl,
                    CallbackEncryption = x.transaction.CallbackEncryption,
                    DSCheck = x.transaction.DsCheck,
                    MerkleProof = x.transaction.MerkleProof,
                    TxExternalId = new uint256(x.transactionId),
                    TxPayload = x.transaction.RawTx,
                    ReceivedAt = clock.UtcNow(),
                    TxIn = x.transaction.TransactionInputs
                }).ToList());

                return(result);
            }
        }
Exemplo n.º 11
0
        public async Task <SubmitTransactionResponse> SubmitTransactionAsync(SubmitTransaction request, UserAndIssuer user)
        {
            var responseMulti = await SubmitTransactionsAsync(new [] { request }, user);

            if (responseMulti.Txs.Length != 1)
            {
                throw new Exception("Internal error. Expected exactly 1 transaction in response but got {responseMulti.Txs.Length}");
            }

            var tx = responseMulti.Txs[0];

            return(new SubmitTransactionResponse
            {
                Txid = tx.Txid,
                ReturnResult = tx.ReturnResult,
                ResultDescription = tx.ResultDescription,
                ConflictedWith = tx.ConflictedWith,
                Timestamp = responseMulti.Timestamp,
                MinerId = responseMulti.MinerId,
                CurrentHighestBlockHash = responseMulti.CurrentHighestBlockHash,
                CurrentHighestBlockHeight = responseMulti.CurrentHighestBlockHeight,
                TxSecondMempoolExpiry = responseMulti.TxSecondMempoolExpiry
            });
        }
Exemplo n.º 12
0
        public async Task <SubmitTransactionsResponse> SubmitTransactionsAsync(IEnumerable <SubmitTransaction> requestEnum, UserAndIssuer user)
        {
            var request = requestEnum.ToArray();

            logger.LogInformation($"Processing {request.Length} incoming transactions");
            // Take snapshot of current metadata and use use it for all transactions
            var info = await blockChainInfo.GetInfoAsync();

            var currentMinerId = await minerId.GetCurrentMinerIdAsync();

            var consolidationParameters = info.ConsolidationTxParameters;

            // Use the same quotes for all transactions in single request
            var quotes = feeQuoteRepository.GetValidFeeQuotesByIdentity(user).ToArray();

            if (quotes == null || !quotes.Any())
            {
                throw new Exception("No fee quotes available");
            }

            var responses            = new List <SubmitTransactionOneResponse>();
            var transactionsToSubmit = new List <(string transactionId, SubmitTransaction transaction, bool allowhighfees, bool dontCheckFees, bool listUnconfirmedAncestors, Dictionary <string, object> config)>();
            int failureCount         = 0;

            IDictionary <uint256, byte[]> allTxs = new Dictionary <uint256, byte[]>();

            foreach (var oneTx in request)
            {
                if (!string.IsNullOrEmpty(oneTx.MerkleFormat) && !MerkleFormat.ValidFormats.Any(x => x == oneTx.MerkleFormat))
                {
                    AddFailureResponse(null, $"Invalid merkle format {oneTx.MerkleFormat}. Supported formats: {String.Join(",", MerkleFormat.ValidFormats)}.", ref responses);

                    failureCount++;
                    continue;
                }

                if ((oneTx.RawTx == null || oneTx.RawTx.Length == 0) && string.IsNullOrEmpty(oneTx.RawTxString))
                {
                    AddFailureResponse(null, $"{nameof(SubmitTransaction.RawTx)} is required", ref responses);

                    failureCount++;
                    continue;
                }

                if (oneTx.RawTx == null)
                {
                    try
                    {
                        oneTx.RawTx = HelperTools.HexStringToByteArray(oneTx.RawTxString);
                    }
                    catch (Exception ex)
                    {
                        AddFailureResponse(null, ex.Message, ref responses);

                        failureCount++;
                        continue;
                    }
                }
                uint256 txId       = Hashes.DoubleSHA256(oneTx.RawTx);
                string  txIdString = txId.ToString();
                logger.LogInformation($"Processing transaction: { txIdString }");

                if (oneTx.MerkleProof && (appSettings.DontParseBlocks.Value || appSettings.DontInsertTransactions.Value))
                {
                    AddFailureResponse(txIdString, $"Transaction requires merkle proof notification but this instance of mAPI does not support callbacks", ref responses);

                    failureCount++;
                    continue;
                }

                if (oneTx.DsCheck && (appSettings.DontParseBlocks.Value || appSettings.DontInsertTransactions.Value))
                {
                    AddFailureResponse(txIdString, $"Transaction requires double spend notification but this instance of mAPI does not support callbacks", ref responses);

                    failureCount++;
                    continue;
                }

                if (allTxs.ContainsKey(txId))
                {
                    AddFailureResponse(txIdString, "Transaction with this id occurs more than once within request", ref responses);

                    failureCount++;
                    continue;
                }

                var vc     = new ValidationContext(oneTx);
                var errors = oneTx.Validate(vc);
                if (errors.Any())
                {
                    AddFailureResponse(txIdString, string.Join(",", errors.Select(x => x.ErrorMessage)), ref responses);

                    failureCount++;
                    continue;
                }
                allTxs.Add(txId, oneTx.RawTx);
                bool okToMine  = false;
                bool okToRelay = false;
                Dictionary <string, object> policies = null;
                if (await txRepository.TransactionExistsAsync(txId.ToBytes()))
                {
                    AddFailureResponse(txIdString, "Transaction already known", ref responses);

                    failureCount++;
                    continue;
                }

                Transaction    transaction    = null;
                CollidedWith[] colidedWith    = Array.Empty <CollidedWith>();
                Exception      exception      = null;
                string[]       prevOutsErrors = Array.Empty <string>();
                try
                {
                    transaction = HelperTools.ParseBytesToTransaction(oneTx.RawTx);

                    if (transaction.IsCoinBase)
                    {
                        throw new ExceptionWithSafeErrorMessage("Invalid transaction - coinbase transactions are not accepted");
                    }
                    var(sumPrevOuputs, prevOuts) = await CollectPreviousOuputs(transaction, new ReadOnlyDictionary <uint256, byte[]>(allTxs), rpcMultiClient);

                    prevOutsErrors = prevOuts.Where(x => !string.IsNullOrEmpty(x.Error)).Select(x => x.Error).ToArray();
                    colidedWith    = prevOuts.Where(x => x.CollidedWith != null).Select(x => x.CollidedWith).ToArray();
                    logger.LogInformation($"CollectPreviousOuputs for {txIdString} returned { prevOuts.Length } prevOuts ({prevOutsErrors.Length } prevOutsErrors, {colidedWith.Length} colidedWith).");

                    if (appSettings.CheckFeeDisabled.Value || IsConsolidationTxn(transaction, consolidationParameters, prevOuts))
                    {
                        logger.LogInformation($"{txIdString}: appSettings.CheckFeeDisabled { appSettings.CheckFeeDisabled }");
                        (okToMine, okToRelay) = (true, true);
                    }
                    else
                    {
                        logger.LogInformation($"Starting with CheckFees calculation for {txIdString} and { quotes.Length} quotes.");
                        foreach (var feeQuote in quotes)
                        {
                            var(okToMineTmp, okToRelayTmp) =
                                CheckFees(transaction, oneTx.RawTx.LongLength, sumPrevOuputs, feeQuote);
                            if (GetCheckFeesValue(okToMineTmp, okToRelayTmp) > GetCheckFeesValue(okToMine, okToRelay))
                            {
                                // save best combination
                                (okToMine, okToRelay, policies) = (okToMineTmp, okToRelayTmp, feeQuote.PoliciesDict);
                            }
                        }
                        logger.LogInformation($"Finished with CheckFees calculation for {txIdString} and { quotes.Length} quotes: { (okToMine, okToRelay, policies == null ? "" : string.Join(";", policies.Select(x => x.Key + "=" + x.Value)) )}.");
                    }
                }
                catch (Exception ex)
                {
                    exception = ex;
                }

                if (exception != null || colidedWith.Any() || transaction == null || prevOutsErrors.Any())
                {
                    var oneResponse = new SubmitTransactionOneResponse
                    {
                        Txid         = txIdString,
                        ReturnResult = ResultCodes.Failure,
                        // Include non null ConflictedWith only if a collision has been detected
                        ConflictedWith = !colidedWith.Any() ? null : colidedWith.Select(
                            x => new SubmitTransactionConflictedTxResponse
                        {
                            Txid = x.TxId,
                            Size = x.Size,
                            Hex  = x.Hex,
                        }).ToArray()
                    };

                    if (transaction is null)
                    {
                        oneResponse.ResultDescription = "Can not parse transaction";
                    }
                    else if (exception is ExceptionWithSafeErrorMessage)
                    {
                        oneResponse.ResultDescription = exception.Message;
                    }
                    else if (exception != null)
                    {
                        oneResponse.ResultDescription = "Error fetching inputs";
                    }
                    else if (oneResponse.ConflictedWith != null && oneResponse.ConflictedWith.Any(c => c.Txid == oneResponse.Txid))
                    {
                        oneResponse.ResultDescription = "Transaction already in the mempool";
                        oneResponse.ConflictedWith    = null;
                    }
                    else
                    {
                        // return "Missing inputs" regardless of error returned from gettxouts (which is usually "missing")
                        oneResponse.ResultDescription = "Missing inputs";
                    }
                    logger.LogError($"Can not calculate fee for {txIdString}. Error: {oneResponse.ResultDescription} Exception: {exception?.ToString() ?? ""}");


                    responses.Add(oneResponse);
                    failureCount++;
                    continue;
                }

                // Transactions  was successfully analyzed
                if (!okToMine && !okToRelay)
                {
                    AddFailureResponse(txIdString, "Not enough fees", ref responses);

                    failureCount++;
                }
                else
                {
                    bool allowHighFees            = false;
                    bool dontcheckfee             = okToMine;
                    bool listUnconfirmedAncestors = false;

                    oneTx.TransactionInputs = transaction.Inputs.AsIndexedInputs().Select(x => new TxInput
                    {
                        N        = x.Index,
                        PrevN    = x.PrevOut.N,
                        PrevTxId = x.PrevOut.Hash.ToBytes()
                    }).ToList();
                    if (oneTx.DsCheck)
                    {
                        foreach (TxInput txInput in oneTx.TransactionInputs)
                        {
                            var prevOut = await txRepository.GetPrevOutAsync(txInput.PrevTxId, txInput.PrevN);

                            if (prevOut == null)
                            {
                                listUnconfirmedAncestors = true;
                                break;
                            }
                        }
                    }
                    transactionsToSubmit.Add((txIdString, oneTx, allowHighFees, dontcheckfee, listUnconfirmedAncestors, policies));
                }
            }

            logger.LogInformation($"TransactionsToSubmit: { transactionsToSubmit.Count }: { string.Join("; ", transactionsToSubmit.Select(x => x.transactionId))} ");

            RpcSendTransactions rpcResponse;

            Exception submitException = null;

            if (transactionsToSubmit.Any())
            {
                // Submit all collected transactions in one call
                try
                {
                    rpcResponse = await rpcMultiClient.SendRawTransactionsAsync(
                        transactionsToSubmit.Select(x => (x.transaction.RawTx, x.allowhighfees, x.dontCheckFees, x.listUnconfirmedAncestors, x.config))
                        .ToArray());
                }
                catch (Exception ex)
                {
                    submitException = ex;
                    rpcResponse     = null;
                }
            }
            else
            {
                // Simulate empty response
                rpcResponse = new RpcSendTransactions();
            }


            // Initialize common fields
            var result = new SubmitTransactionsResponse
            {
                Timestamp = clock.UtcNow(),
                MinerId   = currentMinerId,
                CurrentHighestBlockHash   = info.BestBlockHash,
                CurrentHighestBlockHeight = info.BestBlockHeight,
                // TxSecondMempoolExpiry
                // Remaining of the fields are initialized bellow
            };

            if (submitException != null)
            {
                var unableToSubmit = transactionsToSubmit.Select(x =>
                                                                 new SubmitTransactionOneResponse
                {
                    Txid              = x.transactionId,
                    ReturnResult      = ResultCodes.Failure,
                    ResultDescription = "Error while submitting transactions to the node" // do not expose detailed error message. It might contain internal IPS etc
                });

                logger.LogError($"Error while submitting transactions to the node {submitException}");
                responses.AddRange(unableToSubmit);
                result.Txs          = responses.ToArray();
                result.FailureCount = result.Txs.Length; // all of the transactions have failed

                return(result);
            }
            else // submitted without error
            {
                var(submitFailureCount, transformed) = TransformRpcResponse(rpcResponse,
                                                                            transactionsToSubmit.Select(x => x.transactionId).ToArray());
                responses.AddRange(transformed);
                result.Txs          = responses.ToArray();
                result.FailureCount = failureCount + submitFailureCount;


                if (!appSettings.DontInsertTransactions.Value)
                {
                    var successfullTxs = transactionsToSubmit.Where(x => transformed.Any(y => y.ReturnResult == ResultCodes.Success && y.Txid == x.transactionId));
                    logger.LogInformation($"Starting with InsertTxsAsync: { successfullTxs.Count() }: { string.Join("; ", successfullTxs.Select(x => x.transactionId))} (TransactionsToSubmit: { transactionsToSubmit.Count })");
                    var watch = System.Diagnostics.Stopwatch.StartNew();
                    await txRepository.InsertTxsAsync(successfullTxs.Select(x => new Tx
                    {
                        CallbackToken = x.transaction.CallbackToken,
                        CallbackUrl = x.transaction.CallbackUrl,
                        CallbackEncryption = x.transaction.CallbackEncryption,
                        DSCheck = x.transaction.DsCheck,
                        MerkleProof = x.transaction.MerkleProof,
                        MerkleFormat = x.transaction.MerkleFormat,
                        TxExternalId = new uint256(x.transactionId),
                        TxPayload = x.transaction.RawTx,
                        ReceivedAt = clock.UtcNow(),
                        TxIn = x.transaction.TransactionInputs
                    }).ToList(), false);

                    long unconfirmedAncestorsCount = 0;
                    if (rpcResponse.Unconfirmed != null)
                    {
                        List <Tx> unconfirmedAncestors = new();
                        foreach (var unconfirmed in rpcResponse.Unconfirmed)
                        {
                            unconfirmedAncestors.AddRange(unconfirmed.Ancestors.Select(u => new Tx
                            {
                                TxExternalId = new uint256(u.Txid),
                                ReceivedAt   = clock.UtcNow(),
                                TxIn         = u.Vin.Select(i => new TxInput()
                                {
                                    PrevTxId = (new uint256(i.Txid)).ToBytes(),
                                    PrevN    = i.Vout
                                }).ToList()
                            })
                                                          );
                        }
                        await txRepository.InsertTxsAsync(unconfirmedAncestors, true);

                        unconfirmedAncestorsCount = unconfirmedAncestors.Count;
                    }
                    watch.Stop();
                    logger.LogInformation($"Finished with InsertTxsAsync: { successfullTxs.Count() } found unconfirmedAncestors { unconfirmedAncestorsCount } took {watch.ElapsedMilliseconds} ms.");
                }

                return(result);
            }
        }
        public ActionResult <IEnumerable <FeeQuoteConfigViewModelGet> > Get(
            [FromQuery]
            string identity,
            [FromQuery]
            string identityProvider,
            [FromQuery]
            bool anonymous,
            [FromQuery]
            bool current,
            [FromQuery]
            bool valid)
        {
            UserAndIssuer userAndIssuer = null;

            if (identity != null || identityProvider != null)
            {
                userAndIssuer = new UserAndIssuer()
                {
                    Identity = identity, IdentityProvider = identityProvider
                };
            }

            IEnumerable <FeeQuote> result = new List <FeeQuote>();

            if (valid)
            {
                logger.LogInformation($"GetValidFeeQuotes for user { ((userAndIssuer == null) ? "/" : userAndIssuer.ToString())} ...");
                if (userAndIssuer != null)
                {
                    result = feeQuoteRepository.GetValidFeeQuotesByIdentity(userAndIssuer);
                }
                if (anonymous)
                {
                    result = result.Concat(feeQuoteRepository.GetValidFeeQuotesByIdentity(null));
                }
                if (!anonymous && userAndIssuer == null)
                {
                    result = feeQuoteRepository.GetValidFeeQuotes();
                }
            }
            else if (current)
            {
                logger.LogInformation($"GetCurrentFeeQuotes for user { ((userAndIssuer == null) ? "/" : userAndIssuer.ToString())} ...");
                if (userAndIssuer != null)
                {
                    var feeQuote = feeQuoteRepository.GetCurrentFeeQuoteByIdentity(userAndIssuer);
                    if (feeQuote != null)
                    {
                        result = result.Append(feeQuote);
                    }
                }
                if (anonymous)
                {
                    var feeQuote = feeQuoteRepository.GetCurrentFeeQuoteByIdentity(null);
                    if (feeQuote != null)
                    {
                        result = result.Append(feeQuote);
                    }
                }
                if (!anonymous && userAndIssuer == null)
                {
                    result = feeQuoteRepository.GetCurrentFeeQuotes();
                }
            }
            else
            {
                logger.LogInformation($"GetFeeQuotes for user { ((userAndIssuer == null) ? "/" : userAndIssuer.ToString())} ...");
                if (userAndIssuer != null)
                {
                    result = feeQuoteRepository.GetFeeQuotesByIdentity(userAndIssuer);
                }
                if (anonymous)
                {
                    result = result.Concat(feeQuoteRepository.GetFeeQuotesByIdentity(null));
                }
                if (!anonymous && userAndIssuer == null)
                {
                    result = feeQuoteRepository.GetFeeQuotes();
                }
            }
            return(Ok(result.Select(x => new FeeQuoteConfigViewModelGet(x))));
        }