コード例 #1
0
        public IActionResult GetHistory(string walletName, string address)
        {
            if (string.IsNullOrWhiteSpace(walletName))
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No wallet name", "No wallet name provided"));
            }

            if (string.IsNullOrWhiteSpace(address))
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No address", "No address provided"));
            }

            try
            {
                var transactionItems = new List <ContractTransactionItem>();

                HdAccount account = this.walletManager.GetAccounts(walletName).First();

                // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them.
                IEnumerable <AccountHistory> accountsHistory = this.walletManager.GetHistory(walletName, account.Name);

                // Wallet manager returns only 1 when an account name is specified.
                AccountHistory accountHistory = accountsHistory.First();

                List <FlatHistory> items = accountHistory.History.Where(x => x.Address.Address == address).ToList();

                // Represents a sublist of transactions associated with receive addresses + a sublist of already spent transactions associated with change addresses.
                // In effect, we filter out 'change' transactions that are not spent, as we don't want to show these in the history.
                List <FlatHistory> history = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && t.Transaction.IsSpent())).ToList();

                // TransactionData in history is confusingly named. A "TransactionData" actually represents an input, and the outputs that spend it are "SpendingDetails".
                // There can be multiple "TransactionData" which have the same "SpendingDetails".
                // For SCs we need to group spending details by their transaction ID, to get all the inputs related to the same outputs.
                // Each group represents 1 SC transaction.
                // Each item.Transaction in a group is an input.
                // Each item.Transaction.SpendingDetails in the group represent the outputs, and they should all be the same so we can pick any one.
                var scTransactions = history
                                     .Where(item => item.Transaction.SpendingDetails != null)
                                     .Where(item => item.Transaction.SpendingDetails.Payments.Any(x => x.DestinationScriptPubKey.IsSmartContractExec()))
                                     .GroupBy(item => item.Transaction.SpendingDetails.TransactionId)
                                     .Select(g => new
                {
                    TransactionId = g.Key,
                    InputAmount   = g.Sum(i => i.Transaction.Amount),                 // Sum the inputs to the SC transaction.
                    Outputs       = g.First().Transaction.SpendingDetails.Payments,   // Each item in the group will have the same outputs.
                    OutputAmount  = g.First().Transaction.SpendingDetails.Payments.Sum(o => o.Amount),
                    BlockHeight   = g.First().Transaction.SpendingDetails.BlockHeight // Each item in the group will have the same block height.
                })
                                     .ToList();

                foreach (var scTransaction in scTransactions)
                {
                    // Consensus rules state that each transaction can have only one smart contract exec output, so FirstOrDefault is correct.
                    PaymentDetails scPayment = scTransaction.Outputs?.FirstOrDefault(x => x.DestinationScriptPubKey.IsSmartContractExec());

                    if (scPayment == null)
                    {
                        continue;
                    }

                    Receipt receipt = this.receiptRepository.Retrieve(scTransaction.TransactionId);

                    Result <ContractTxData> txDataResult = this.callDataSerializer.Deserialize(scPayment.DestinationScriptPubKey.ToBytes());

                    if (txDataResult.IsFailure)
                    {
                        continue;
                    }

                    ContractTxData txData = txDataResult.Value;

                    // If the receipt is not available yet, we don't know how much gas was consumed so use the full gas budget.
                    ulong gasFee = receipt != null
                        ? receipt.GasUsed * receipt.GasPrice
                        : txData.GasCostBudget;

                    long  totalFees      = scTransaction.InputAmount - scTransaction.OutputAmount;
                    Money transactionFee = Money.FromUnit(totalFees, MoneyUnit.Satoshi) - Money.FromUnit(txData.GasCostBudget, MoneyUnit.Satoshi);

                    var result = new ContractTransactionItem
                    {
                        Amount         = scPayment.Amount.ToUnit(MoneyUnit.Satoshi),
                        BlockHeight    = scTransaction.BlockHeight,
                        Hash           = scTransaction.TransactionId,
                        TransactionFee = transactionFee.ToUnit(MoneyUnit.Satoshi),
                        GasFee         = gasFee
                    };

                    if (scPayment.DestinationScriptPubKey.IsSmartContractCreate())
                    {
                        result.Type = ContractTransactionItemType.ContractCreate;
                        result.To   = receipt?.NewContractAddress?.ToBase58Address(this.network) ?? string.Empty;
                    }
                    else if (scPayment.DestinationScriptPubKey.IsSmartContractCall())
                    {
                        result.Type = ContractTransactionItemType.ContractCall;
                        result.To   = txData.ContractAddress.ToBase58Address(this.network);
                    }

                    transactionItems.Add(result);
                }

                return(this.Json(transactionItems.OrderByDescending(x => x.BlockHeight ?? Int32.MaxValue)));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
コード例 #2
0
        public IActionResult GetHistory(GetHistoryRequest request)
        {
            if (string.IsNullOrWhiteSpace(request.WalletName))
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No wallet name", "No wallet name provided"));
            }

            if (string.IsNullOrWhiteSpace(request.Address))
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No address", "No address provided"));
            }

            try
            {
                var transactionItems = new List <ContractTransactionItem>();

                HdAccount account = this.walletManager.GetAccounts(request.WalletName).First();

                // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them.
                IEnumerable <AccountHistory> accountsHistory = this.walletManager.GetHistory(request.WalletName, account.Name, null, offset: request.Skip ?? 0, limit: request.Take ?? int.MaxValue, accountAddress: request.Address, forSmartContracts: true);

                // Wallet manager returns only 1 when an account name is specified.
                AccountHistory accountHistory = accountsHistory.First();

                var scTransactions = accountHistory.History.Select(h => new
                {
                    TransactionId      = uint256.Parse(h.Id),
                    Fee                = h.Fee,
                    SendToScriptPubKey = Script.FromHex(h.SendToScriptPubkey),
                    OutputAmount       = h.Amount,
                    BlockHeight        = h.BlockHeight
                }).ToList();

                // Get all receipts in one transaction
                IList <Receipt> receipts = this.receiptRepository.RetrieveMany(scTransactions.Select(x => x.TransactionId).ToList());

                for (int i = 0; i < scTransactions.Count; i++)
                {
                    var     scTransaction = scTransactions[i];
                    Receipt receipt       = receipts[i];

                    // This will always give us a value - the transaction has to be serializable to get past consensus.
                    Result <ContractTxData> txDataResult = this.callDataSerializer.Deserialize(scTransaction.SendToScriptPubKey.ToBytes());
                    ContractTxData          txData       = txDataResult.Value;

                    // If the receipt is not available yet, we don't know how much gas was consumed so use the full gas budget.
                    ulong gasFee = receipt != null
                        ? receipt.GasUsed * receipt.GasPrice
                        : txData.GasCostBudget;

                    long  totalFees      = scTransaction.Fee;
                    Money transactionFee = Money.FromUnit(totalFees, MoneyUnit.Satoshi) - Money.FromUnit(txData.GasCostBudget, MoneyUnit.Satoshi);

                    var result = new ContractTransactionItem
                    {
                        Amount         = new Money(scTransaction.OutputAmount).ToUnit(MoneyUnit.Satoshi),
                        BlockHeight    = scTransaction.BlockHeight,
                        Hash           = scTransaction.TransactionId,
                        TransactionFee = transactionFee.ToUnit(MoneyUnit.Satoshi),
                        GasFee         = gasFee
                    };

                    if (scTransaction.SendToScriptPubKey.IsSmartContractCreate())
                    {
                        result.Type = ContractTransactionItemType.ContractCreate;
                        result.To   = receipt?.NewContractAddress?.ToBase58Address(this.network) ?? string.Empty;
                    }
                    else if (scTransaction.SendToScriptPubKey.IsSmartContractCall())
                    {
                        result.Type = ContractTransactionItemType.ContractCall;
                        result.To   = txData.ContractAddress.ToBase58Address(this.network);
                    }

                    transactionItems.Add(result);
                }

                return(this.Json(transactionItems.OrderByDescending(x => x.BlockHeight ?? int.MaxValue)));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }