Beispiel #1
0
        public async Task <ActionResult <FeeQuoteViewModelGet> > GetFeeQuote()
        {
            if (!IdentityProviderStore.GetUserAndIssuer(User, Request.Headers, out var identity))
            {
                return(Unauthorized("Incorrectly formatted token"));
            }

            logger.LogInformation($"Get FeeQuote for user { ((identity == null) ? "/" : identity.ToString() )} ...");
            FeeQuote feeQuote = feeQuoteRepository.GetCurrentFeeQuoteByIdentity(identity);


            if (feeQuote == null)
            {
                logger.LogInformation($"There are no active feeQuotes.");

                return(NotFound());
            }

            var feeQuoteViewModelGet = new FeeQuoteViewModelGet(feeQuote)
            {
                Timestamp = clock.UtcNow(),
            };

            feeQuoteViewModelGet.ExpiryTime = feeQuoteViewModelGet.Timestamp.Add(TimeSpan.FromMinutes(quoteExpiryMinutes));

            var info = blockChainInfo.GetInfo();

            feeQuoteViewModelGet.MinerId = await minerId.GetCurrentMinerIdAsync();

            feeQuoteViewModelGet.CurrentHighestBlockHash   = info.BestBlockHash;
            feeQuoteViewModelGet.CurrentHighestBlockHeight = info.BestBlockHeight;

            logger.LogInformation($"Returning feeQuote with ExpiryTime: {feeQuoteViewModelGet.ExpiryTime}.");

            return(await SignIfRequiredAsync(feeQuoteViewModelGet, feeQuoteViewModelGet.MinerId));
        }
Beispiel #2
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);
            }
        }