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)); }
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."); } } }
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"); } } }
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)); }
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); } }
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 }); }
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)))); }