<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Connection;
using Stratis.Bitcoin.Features.SmartContracts.Models;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Features.Wallet.Broadcasting;
using Stratis.Bitcoin.Features.Wallet.Interfaces;
using Stratis.Bitcoin.Features.Wallet.Models;
using Stratis.Bitcoin.Utilities;
using Stratis.Bitcoin.Utilities.JsonErrors;
using Stratis.Bitcoin.Utilities.ModelStateErrors;
using Stratis.SmartContracts.CLR;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Receipts;
using State = Stratis.Bitcoin.Features.Wallet.Broadcasting.State;

namespace Stratis.Bitcoin.Features.SmartContracts.Wallet
{
    [Route("api/[controller]")]
    public sealed class SmartContractWalletController : Controller
    {
        private readonly IBroadcasterManager broadcasterManager;
        private readonly ICallDataSerializer callDataSerializer;
        private readonly IConnectionManager connectionManager;
        private readonly ILogger logger;
        private readonly Network network;
        private readonly IReceiptRepository receiptRepository;
        private readonly IWalletManager walletManager;
        private readonly ISmartContractTransactionService smartContractTransactionService;

        public SmartContractWalletController(
            IBroadcasterManager broadcasterManager,
            ICallDataSerializer callDataSerializer,
            IConnectionManager connectionManager,
            ILoggerFactory loggerFactory,
            Network network,
            IReceiptRepository receiptRepository,
            IWalletManager walletManager,
            ISmartContractTransactionService smartContractTransactionService)
        {
            this.broadcasterManager = broadcasterManager;
            this.callDataSerializer = callDataSerializer;
            this.connectionManager = connectionManager;
            this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
            this.network = network;
            this.receiptRepository = receiptRepository;
            this.walletManager = walletManager;
            this.smartContractTransactionService = smartContractTransactionService;
        }

        private IEnumerable<HdAddress> GetAccountAddressesWithBalance(string walletName)
        {
            return this.walletManager
                .GetSpendableTransactionsInWallet(walletName)
                .GroupBy(x => x.Address)
                .Where(grouping => grouping.Sum(x => x.Transaction.GetUnspentAmount(true)) > 0)
                .Select(grouping => grouping.Key);
        }

        /// <summary>
        /// Gets a smart contract account address.
        /// This is a single address to use for all smart contract interactions.
        /// Smart contracts send funds to and store data at this address. For example, an ERC-20 token
        /// would store tokens allocated to a user at this address, although the actual data
        /// could, in fact, be anything. The address stores a history of smart contract create/call transactions.   
        /// It also holds a UTXO list/balance based on UTXOs sent to it from smart contracts or user wallets.
        /// Once a smart contract has written data to this address, you need to use the address to
        /// provide gas and fees for smart contract calls involving that stored data (for that smart contract deployment).
        /// In the case of specific ERC-20 tokens allocated to you, using this address would be
        /// a requirement if you were to, for example, send some of the tokens to an exchange.  
        /// It is therefore recommended that in order to keep an intact history and avoid complications,
        /// you use the single smart contract address provided by this function for all interactions with smart contracts.
        /// In addition, a smart contract address can be used to identify a contract deployer.
        /// Some methods, such as a withdrawal method on an escrow smart contract, should only be executed
        /// by the deployer, and in this case, it is the smart contract account address that identifies the deployer.
        ///  
        /// Note that this account differs from "account 0", which is the "default
        /// holder of multiple addresses". Other address holding accounts can be created,
        /// but they should not be confused with the smart contract account, which is represented
        /// by a single address.
        /// </summary>
        /// 
        /// <param name="walletName">The name of the wallet to retrieve a smart contract account address for.</param>
        /// 
        /// <returns>A smart contract account address to use for the wallet.</returns>
        [Route("account-addresses")]
        [HttpGet]
        public IActionResult GetAccountAddresses(string walletName)
        {
            if (string.IsNullOrWhiteSpace(walletName))
                return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No wallet name", "No wallet name provided");

            try
            {
                IEnumerable<string> addresses = this.GetAccountAddressesWithBalance(walletName)
                    .Select(a => a.Address);

                if (!addresses.Any())
                {
                    HdAccount account = this.walletManager.GetAccounts(walletName).First();

                    var walletAccountReference = new WalletAccountReference(walletName, account.Name);

                    HdAddress nextAddress = this.walletManager.GetUnusedAddress(walletAccountReference);

                    return this.Json(new[] { nextAddress.Address });
                }

                return this.Json(addresses);
            }
            catch (WalletException e)
            {
                return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
            }
        }

        /// <summary>
        /// Gets the balance at a specific wallet address in STRAT (or the sidechain coin).
        /// This method gets the UTXOs at the address that the wallet can spend.
        /// The function can be used to query the balance at a smart contract account address
        /// supplied by /api/SmartContractWallet/account-addresses.
        /// </summary>
        ///
        /// <param name="walletName">The address at which to retrieve the balance.</param>
        /// 
        /// <returns>The balance at a specific wallet address in STRAT (or the sidechain coin).</returns>
        [Route("address-balance")]
        [HttpGet]
        public IActionResult GetAddressBalance(string address)
        {
            AddressBalance balance = this.walletManager.GetAddressBalance(address);

            return this.Json(balance.AmountConfirmed.ToUnit(MoneyUnit.Satoshi));
        }


        /// <summary>
        /// Gets the history of a specific wallet address.
        /// This includes the smart contract create and call transactions
        /// This method can be used to query the balance at a smart contract account address
        /// supplied by /api/SmartContractWallet/account-addresses. Indeed,
        /// it is advisable to use /api/SmartContractWallet/account-addresses
        /// to generate an address for all smart contract interactions.
        /// If this has been done, and that address is supplied to this method,
        /// a list of all smart contract interactions for a wallet will be returned.
        /// </summary>
        ///
        /// <param name="walletName">The name of the wallet holding the address.</param>
        /// <param name="address">The address to retrieve the history for.</param>
        /// <returns>A list of smart contract create and call transaction items as well as transaction items at a specific wallet address.</returns>
        [Route("history")]
        [HttpGet]
        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.OrderByDescending(o => o.Transaction.CreationTime).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();

                foreach (FlatHistory item in history)
                {
                    bool hasSmartContract = this.receiptRepository.Retrieve(item.Transaction.Id) != null;
                    TransactionData transaction = item.Transaction;

                    // Record a receive transaction if it has a SC
                    if (hasSmartContract)
                    {
                        transactionItems.Add(new ContractTransactionItem
                        {
                            Amount = transaction.Amount.ToUnit(MoneyUnit.Satoshi),
                            BlockHeight = transaction.BlockHeight,
                            Hash = transaction.Id,
                            Type = ReceivedTransactionType(transaction),
                            To = address
                        });
                    }

                    // Add outgoing transaction details
                    // Get if it's an SC transaction
                    PaymentDetails scPayment = transaction.SpendingDetails?.Payments?.FirstOrDefault(x => x.DestinationScriptPubKey.IsSmartContractExec());

                    if (scPayment == null) continue;

                    Receipt receipt = this.receiptRepository.Retrieve(transaction.SpendingDetails.TransactionId);
                    if (receipt == null) continue;

                    if (scPayment.DestinationScriptPubKey.IsSmartContractCreate())
                    {
                        // Create a record for a Create transaction
                        transactionItems.Add(new ContractTransactionItem
                        {
                            Amount = scPayment.Amount.ToUnit(MoneyUnit.Satoshi),
                            BlockHeight = transaction.SpendingDetails.BlockHeight,
                            Type = ContractTransactionItemType.ContractCreate,
                            Hash = transaction.SpendingDetails.TransactionId,
                            To = receipt.NewContractAddress?.ToBase58Address(this.network) ?? string.Empty
                        });
                    }
                    else
                    {
                        // Create a record for a Call transaction<commit_msg>Removed a check for receipt as we may not have it when tx is unmined (#3749)<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Connection;
using Stratis.Bitcoin.Features.SmartContracts.Models;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Features.Wallet.Broadcasting;
using Stratis.Bitcoin.Features.Wallet.Interfaces;
using Stratis.Bitcoin.Features.Wallet.Models;
using Stratis.Bitcoin.Utilities;
using Stratis.Bitcoin.Utilities.JsonErrors;
using Stratis.Bitcoin.Utilities.ModelStateErrors;
using Stratis.SmartContracts.CLR;
using Stratis.SmartContracts.Core;
using Stratis.SmartContracts.Core.Receipts;
using State = Stratis.Bitcoin.Features.Wallet.Broadcasting.State;

namespace Stratis.Bitcoin.Features.SmartContracts.Wallet
{
    [Route("api/[controller]")]
    public sealed class SmartContractWalletController : Controller
    {
        private readonly IBroadcasterManager broadcasterManager;
        private readonly ICallDataSerializer callDataSerializer;
        private readonly IConnectionManager connectionManager;
        private readonly ILogger logger;
        private readonly Network network;
        private readonly IReceiptRepository receiptRepository;
        private readonly IWalletManager walletManager;
        private readonly ISmartContractTransactionService smartContractTransactionService;

        public SmartContractWalletController(
            IBroadcasterManager broadcasterManager,
            ICallDataSerializer callDataSerializer,
            IConnectionManager connectionManager,
            ILoggerFactory loggerFactory,
            Network network,
            IReceiptRepository receiptRepository,
            IWalletManager walletManager,
            ISmartContractTransactionService smartContractTransactionService)
        {
            this.broadcasterManager = broadcasterManager;
            this.callDataSerializer = callDataSerializer;
            this.connectionManager = connectionManager;
            this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
            this.network = network;
            this.receiptRepository = receiptRepository;
            this.walletManager = walletManager;
            this.smartContractTransactionService = smartContractTransactionService;
        }

        private IEnumerable<HdAddress> GetAccountAddressesWithBalance(string walletName)
        {
            return this.walletManager
                .GetSpendableTransactionsInWallet(walletName)
                .GroupBy(x => x.Address)
                .Where(grouping => grouping.Sum(x => x.Transaction.GetUnspentAmount(true)) > 0)
                .Select(grouping => grouping.Key);
        }

        /// <summary>
        /// Gets a smart contract account address.
        /// This is a single address to use for all smart contract interactions.
        /// Smart contracts send funds to and store data at this address. For example, an ERC-20 token
        /// would store tokens allocated to a user at this address, although the actual data
        /// could, in fact, be anything. The address stores a history of smart contract create/call transactions.   
        /// It also holds a UTXO list/balance based on UTXOs sent to it from smart contracts or user wallets.
        /// Once a smart contract has written data to this address, you need to use the address to
        /// provide gas and fees for smart contract calls involving that stored data (for that smart contract deployment).
        /// In the case of specific ERC-20 tokens allocated to you, using this address would be
        /// a requirement if you were to, for example, send some of the tokens to an exchange.  
        /// It is therefore recommended that in order to keep an intact history and avoid complications,
        /// you use the single smart contract address provided by this function for all interactions with smart contracts.
        /// In addition, a smart contract address can be used to identify a contract deployer.
        /// Some methods, such as a withdrawal method on an escrow smart contract, should only be executed
        /// by the deployer, and in this case, it is the smart contract account address that identifies the deployer.
        ///  
        /// Note that this account differs from "account 0", which is the "default
        /// holder of multiple addresses". Other address holding accounts can be created,
        /// but they should not be confused with the smart contract account, which is represented
        /// by a single address.
        /// </summary>
        /// 
        /// <param name="walletName">The name of the wallet to retrieve a smart contract account address for.</param>
        /// 
        /// <returns>A smart contract account address to use for the wallet.</returns>
        [Route("account-addresses")]
        [HttpGet]
        public IActionResult GetAccountAddresses(string walletName)
        {
            if (string.IsNullOrWhiteSpace(walletName))
                return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No wallet name", "No wallet name provided");

            try
            {
                IEnumerable<string> addresses = this.GetAccountAddressesWithBalance(walletName)
                    .Select(a => a.Address);

                if (!addresses.Any())
                {
                    HdAccount account = this.walletManager.GetAccounts(walletName).First();

                    var walletAccountReference = new WalletAccountReference(walletName, account.Name);

                    HdAddress nextAddress = this.walletManager.GetUnusedAddress(walletAccountReference);

                    return this.Json(new[] { nextAddress.Address });
                }

                return this.Json(addresses);
            }
            catch (WalletException e)
            {
                return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString());
            }
        }

        /// <summary>
        /// Gets the balance at a specific wallet address in STRAT (or the sidechain coin).
        /// This method gets the UTXOs at the address that the wallet can spend.
        /// The function can be used to query the balance at a smart contract account address
        /// supplied by /api/SmartContractWallet/account-addresses.
        /// </summary>
        ///
        /// <param name="walletName">The address at which to retrieve the balance.</param>
        /// 
        /// <returns>The balance at a specific wallet address in STRAT (or the sidechain coin).</returns>
        [Route("address-balance")]
        [HttpGet]
        public IActionResult GetAddressBalance(string address)
        {
            AddressBalance balance = this.walletManager.GetAddressBalance(address);

            return this.Json(balance.AmountConfirmed.ToUnit(MoneyUnit.Satoshi));
        }


        /// <summary>
        /// Gets the history of a specific wallet address.
        /// This includes the smart contract create and call transactions
        /// This method can be used to query the balance at a smart contract account address
        /// supplied by /api/SmartContractWallet/account-addresses. Indeed,
        /// it is advisable to use /api/SmartContractWallet/account-addresses
        /// to generate an address for all smart contract interactions.
        /// If this has been done, and that address is supplied to this method,
        /// a list of all smart contract interactions for a wallet will be returned.
        /// </summary>
        ///
        /// <param name="walletName">The name of the wallet holding the address.</param>
        /// <param name="address">The address to retrieve the history for.</param>
        /// <returns>A list of smart contract create and call transaction items as well as transaction items at a specific wallet address.</returns>
        [Route("history")]
        [HttpGet]
        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.OrderByDescending(o => o.Transaction.CreationTime).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();

                foreach (FlatHistory item in history)
                {
                    bool hasSmartContract = this.receiptRepository.Retrieve(item.Transaction.Id) != null;
                    TransactionData transaction = item.Transaction;

                    // Record a receive transaction if it has a SC
                    if (hasSmartContract)
                    {
                        transactionItems.Add(new ContractTransactionItem
                        {
                            Amount = transaction.Amount.ToUnit(MoneyUnit.Satoshi),
                            BlockHeight = transaction.BlockHeight,
                            Hash = transaction.Id,
                            Type = ReceivedTransactionType(transaction),
                            To = address
                        });
                    }

                    // Add outgoing transaction details
                    // Get if it's an SC transaction
                    PaymentDetails scPayment = transaction.SpendingDetails?.Payments?.FirstOrDefault(x => x.DestinationScriptPubKey.IsSmartContractExec());

                    if (scPayment == null) continue;

                    Receipt receipt = this.receiptRepository.Retrieve(transaction.SpendingDetails.TransactionId);

                    if (scPayment.DestinationScriptPubKey.IsSmartContractCreate())
                    {
                        // Create a record for a Create transaction
                        transactionItems.Add(new ContractTransactionItem
                        {
                            Amount = scPayment.Amount.ToUnit(MoneyUnit.Satoshi),
                            BlockHeight = transaction.SpendingDetails.BlockHeight,
                            Type = ContractTransactionItemType.ContractCreate,
                            Hash = transaction.SpendingDetails.TransactionId,
                            To = receipt?.NewContractAddress?.ToBase58Address(this.network) ?? string.Empty
                        });
                    }
                    else
                    {
                        // Create a record for a Call transaction
{'fraction_non_alphanumeric': 0.07331353770231275, 'fraction_numerical': 0.0009200786249006734, 'mean_word_length': 2.175142743327579, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6206455', 'n_tokens_mistral': 5429, 'n_tokens_neox': 5264, 'n_words': 1794}