public Transaction CreateColdStakingSetupTransaction(Wallet.Types.Wallet wallet, string password, HdAddress spendingAddress, PubKey destinationColdPubKey, PubKey destinationHotPubKey, HdAddress changeAddress, Money amount, Money fee) { TransactionOutputData spendingTransaction = wallet.walletStore.GetForAddress(spendingAddress.Address).ElementAt(0); var coin = new Coin(spendingTransaction.Id, (uint)spendingTransaction.Index, spendingTransaction.Amount, spendingTransaction.ScriptPubKey); Key privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network); Script script = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(destinationHotPubKey.Hash, destinationColdPubKey.Hash); var builder = new TransactionBuilder(wallet.Network); builder.Extensions.Add(new ColdStakingBuilderExtension(false)); Transaction tx = builder .AddCoins(new List <Coin> { coin }) .AddKeys(new ExtKey(privateKey, wallet.ChainCode).Derive(new KeyPath(spendingAddress.HdPath)).GetWif(wallet.Network)) .Send(script, amount) .SetChange(changeAddress.ScriptPubKey) .SendFees(fee) .BuildTransaction(true); if (!builder.Verify(tx)) { throw new WalletException("Could not build transaction, please make sure you entered the correct data."); } return(tx); }
public void GetAllTransactionsReturnsTransactionsFromWallet() { var wallet = new Types.Wallet(); wallet.walletStore = new WalletMemoryStore(); AccountRoot stratisAccountRoot = CreateAccountRootWithHdAccountHavingAddresses("StratisAccount", KnownCoinTypes.Stratis); TransactionOutputData transaction1 = CreateTransaction(new uint256(1), new Money(15000), 1); TransactionOutputData transaction2 = CreateTransaction(new uint256(2), new Money(91209), 1); transaction1.OutPoint = new OutPoint(new uint256(1), 1); transaction1.Address = stratisAccountRoot.Accounts.ElementAt(0).InternalAddresses.ElementAt(0).Address; wallet.walletStore.InsertOrUpdate(transaction1); transaction2.OutPoint = new OutPoint(new uint256(2), 1); transaction2.Address = stratisAccountRoot.Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address; wallet.walletStore.InsertOrUpdate(transaction2); wallet.AccountsRoot.Add(stratisAccountRoot); List <TransactionOutputData> result = wallet.GetAllTransactions().ToList(); Assert.Equal(2, result.Count); Assert.Contains(transaction1.OutPoint, result.Select(x => x.OutPoint)); Assert.Contains(transaction2.OutPoint, result.Select(x => x.OutPoint)); }
public static Transaction SetupValidTransaction(Features.Wallet.Types.Wallet wallet, string password, HdAddress spendingAddress, Script destinationScript, HdAddress changeAddress, Money amount, Money fee) { TransactionOutputData spendingTransaction = wallet.walletStore.GetForAddress(spendingAddress.Address).ElementAt(0); spendingTransaction.Address = spendingAddress.Address; var coin = new Coin(spendingTransaction.Id, (uint)spendingTransaction.Index, spendingTransaction.Amount, spendingTransaction.ScriptPubKey); Key privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network); var builder = new TransactionBuilder(wallet.Network); Transaction tx = builder .AddCoins(new List <Coin> { coin }) .AddKeys(new ExtKey(privateKey, wallet.ChainCode).Derive(new KeyPath(spendingAddress.HdPath)).GetWif(wallet.Network)) .Send(destinationScript, amount) .SetChange(changeAddress.ScriptPubKey) .SendFees(fee) .BuildTransaction(true); if (!builder.Verify(tx)) { throw new WalletException("Could not build transaction, please make sure you entered the correct data."); } return(tx); }
public void IsSpentWithTransactionHavingNoSpendingDetailsReturnsFalse() { var transaction = new TransactionOutputData { SpendingDetails = null }; Assert.False(transaction.IsSpent()); }
public void IsSpentWithTransactionHavingSpendingDetailsReturnsTrue() { var transaction = new TransactionOutputData { SpendingDetails = new SpendingDetails() }; Assert.True(transaction.IsSpent()); }
public void IsConfirmedWithTransactionHavingNoBlockHeightReturnsFalse() { var transaction = new TransactionOutputData { BlockHeight = null }; Assert.False(transaction.IsConfirmed()); }
public void IsConfirmedWithTransactionHavingBlockHeightReturnsTrue() { var transaction = new TransactionOutputData { BlockHeight = 15 }; Assert.True(transaction.IsConfirmed()); }
private int GetConformationCount(TransactionOutputData transaction) { if (transaction.BlockHeight.HasValue) { var blockCount = this.ConsensusManager?.Tip.Height ?? -1; // TODO: This is available in FullNodeController, should refactor and reuse the logic. return(blockCount - transaction.BlockHeight.Value); } return(-1); }
public void UnspentAmountConfirmedOnlyGivenNoSpendingDetailsReturnsZero() { var transaction = new TransactionOutputData { SpendingDetails = null }; Money result = transaction.GetUnspentAmount(true); Assert.Equal(Money.Zero, result); }
public static SpendingDetails CreateSpendingDetails(TransactionOutputData changeTransaction, PaymentDetails paymentDetails) { var spendingDetails = new SpendingDetails { TransactionId = changeTransaction.Id, CreationTime = new DateTimeOffset(new DateTime(2017, 6, 23, 1, 2, 3)), BlockHeight = changeTransaction.BlockHeight }; spendingDetails.Payments.Add(paymentDetails); return(spendingDetails); }
public void UnspentAmountNotConfirmedOnlyGivenNoSpendingDetailsReturnsTransactionAmount() { var transaction = new TransactionOutputData { SpendingDetails = null, Amount = new Money(15) }; Money result = transaction.GetUnspentAmount(false); Assert.Equal(new Money(15), result); }
public void UnspentAmountConfirmedOnlyGivenBeingUnConfirmedAndSpentUnconfirmedReturnsZero() { var transaction = new TransactionOutputData { SpendingDetails = new SpendingDetails(), Amount = new Money(15), }; Money result = transaction.GetUnspentAmount(true); Assert.Equal(Money.Zero, result); }
public void UnspentAmountConfirmedOnlyGivenSpendableAndConfirmedReturnsAmount() { var transaction = new TransactionOutputData { SpendingDetails = null, Amount = new Money(15), BlockHeight = 15 }; Money result = transaction.GetUnspentAmount(true); Assert.Equal(new Money(15), result); }
public void UnspentAmountNotConfirmedOnlyGivenBeingConfirmedAndSpentConfirmedReturnsZero() { var transaction = new TransactionOutputData { SpendingDetails = new SpendingDetails { BlockHeight = 16 }, Amount = new Money(15), BlockHeight = 15 }; Money result = transaction.GetUnspentAmount(false); Assert.Equal(Money.Zero, result); }
/// <summary> /// Determines whether or not the input's address exists in the wallet's set of addresses. /// </summary> /// <param name="addresses">The wallet's external and internal addresses.</param> /// <param name="txDictionary">The set of transactions to check against.</param> /// <param name="txIn">The input to check.</param> /// <returns><c>true</c>if the input's address exist in the wallet.</returns> private bool IsTxInMine(IEnumerable <HdAddress> addresses, Dictionary <uint256, TransactionOutputData> txDictionary, TxIn txIn) { TransactionOutputData previousTransaction = null; txDictionary.TryGetValue(txIn.PrevOut.Hash, out previousTransaction); if (previousTransaction == null) { return(false); } var previousTx = this.blockStore.GetTransactionById(previousTransaction.Id); if (txIn.PrevOut.N >= previousTx.Outputs.Count) { return(false); } // We now need to check if the scriptPubkey is in our wallet. // See https://github.com/bitcoin/bitcoin/blob/011c39c2969420d7ca8b40fbf6f3364fe72da2d0/src/script/ismine.cpp return(IsAddressMine(addresses, previousTx.Outputs[txIn.PrevOut.N].ScriptPubKey)); }
public GetTransactionModel GetTransaction(string txid) { if (!uint256.TryParse(txid, out uint256 trxid)) { throw new ArgumentException(nameof(txid)); } WalletAccountReference accountReference = this.GetWalletAccountReference(); Types.Wallet wallet = this.walletManager.GetWalletByName(accountReference.WalletName); HdAccount account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName); // Get the transaction from the wallet by looking into received and send transactions. List <HdAddress> addresses = account.GetCombinedAddresses().ToList(); List <TransactionOutputData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress()).SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.Id == trxid)).ToList(); List <TransactionOutputData> sendTransactions = addresses.SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList(); if (!receivedTransactions.Any() && !sendTransactions.Any()) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id."); } // Get the block hash from the transaction in the wallet. TransactionOutputData transactionFromWallet = null; uint256 blockHash = null; int? blockHeight, blockIndex; if (receivedTransactions.Any()) { blockHeight = receivedTransactions.First().BlockHeight; blockIndex = receivedTransactions.First().BlockIndex; blockHash = receivedTransactions.First().BlockHash; transactionFromWallet = receivedTransactions.First(); } else { blockHeight = sendTransactions.First().SpendingDetails.BlockHeight; blockIndex = sendTransactions.First().SpendingDetails.BlockIndex; blockHash = blockHeight != null?this.ChainIndexer.GetHeader(blockHeight.Value).HashBlock : null; } // Get the block containing the transaction (if it has been confirmed). ChainedHeaderBlock chainedHeaderBlock = null; if (blockHash != null) { this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> { blockHash }, b => { chainedHeaderBlock = b; }); } Block block = null; Transaction transactionFromStore = null; if (chainedHeaderBlock != null) { block = chainedHeaderBlock.Block; transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid); } DateTimeOffset transactionTime; bool isGenerated; string hex; if (transactionFromStore != null) { // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates if (transactionFromStore is IPosTransactionWithTime posTrx) { transactionTime = Utils.UnixTimeToDateTime(posTrx.Time); } else { transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time); } isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake; hex = transactionFromStore.ToHex(); } else if (transactionFromWallet != null) { transactionTime = transactionFromWallet.CreationTime; isGenerated = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true; hex = transactionFromWallet.Hex; } else { transactionTime = sendTransactions.First().SpendingDetails.CreationTime; isGenerated = false; hex = null; // TODO get from mempool } var model = new GetTransactionModel { Confirmations = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0, Isgenerated = isGenerated ? true : (bool?)null, BlockHash = blockHash, BlockIndex = blockIndex ?? block?.Transactions.FindIndex(t => t.GetHash() == trxid), BlockTime = block?.Header.BlockTime.ToUnixTimeSeconds(), TransactionId = uint256.Parse(txid), TransactionTime = transactionTime.ToUnixTimeSeconds(), TimeReceived = transactionTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = hex }; Money feeSent = Money.Zero; if (sendTransactions.Any()) { feeSent = wallet.GetSentTransactionFee(trxid); } // Send transactions details. foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments)) { // Only a single item should appear per destination address. if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null) { model.Details.Add(new GetTransactionDetailsModel { Address = paymentDetail.DestinationAddress, Category = GetTransactionDetailsCategoryModel.Send, Amount = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC), Fee = -feeSent.ToDecimal(MoneyUnit.BTC), OutputIndex = paymentDetail.OutputIndex }); } } // Get the ColdStaking script template if available. Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates(); ScriptTemplate coldStakingTemplate = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null; // Receive transactions details. foreach (TransactionOutputData trxInWallet in receivedTransactions) { // Skip the details if the script pub key is cold staking. // TODO: Verify if we actually need this any longer, after changing the internals to recognice account type! if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey)) { continue; } GetTransactionDetailsCategoryModel category; if (isGenerated) { category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature; } else { category = GetTransactionDetailsCategoryModel.Receive; } model.Details.Add(new GetTransactionDetailsModel { Address = trxInWallet.Address, Category = category, Amount = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC), OutputIndex = trxInWallet.Index }); } model.Amount = model.Details.Sum(d => d.Amount); model.Fee = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee; return(model); }
public List <ListTransactionsModel> ListTransactions(string account = "*", int count = 10, int skip = 0, bool includeWatchOnly = true) { var listTransactionsModels = new List <ListTransactionsModel>(); if (count < 0) { throw new ArgumentException("Negative count", nameof(count)); } if (skip < 0) { throw new ArgumentException("Negative skip", nameof(skip)); } // Transaction dictionary of <Transaction, isWatchOnly> // isWatchOnly to be used to specify that the transaction is watch only. var transactions = new Dictionary <TransactionOutputData, bool>(); WalletAccountReference accountReference = this.GetWalletAccountReference(); // Get all watch-only transactions. if (includeWatchOnly) { var selectedWatchOnlyTransactions = this.watchOnlyWalletManager .GetWatchedTransactions() .Values .Skip(skip) .Take(count); foreach (var watchTransactionData in selectedWatchOnlyTransactions) { var watchedTransaction = this.blockStore.GetTransactionById(watchTransactionData.Id); if (watchedTransaction == null) { continue; } var watchedOutput = watchedTransaction.Outputs.Find( txOut => txOut.ScriptPubKey.GetDestinationAddress(Network).ToString() == watchTransactionData.Address); var transactionData = new TransactionOutputData() { Address = watchTransactionData.Address, Amount = watchedOutput != null ? watchedOutput.Value : 0, Id = watchTransactionData.Id, BlockHash = watchTransactionData.BlockHash, BlockHeight = GetTransactionBlockHeader(watchTransactionData.Id).Height, MerkleProof = watchTransactionData.MerkleProof }; transactions.Add(transactionData, true); } } // Get all wallet transactions for the specified wallet. Func <IHdAccount, bool> accountFilter = account == "*" || account == null ? Wallet.Types.Wallet.AllAccounts : a => a.Name == account; Wallet.Types.Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName); IEnumerable <TransactionOutputData> selectedTransactions = wallet.GetAllTransactions(accountFilter) .Skip(skip) .Take(count); foreach (var transactionData in selectedTransactions) { transactions.Add(transactionData, false); } // Collect all related outputs for a given transaction. foreach ((TransactionOutputData transactionOutputData, bool isWatchOnly) in transactions) { listTransactionsModels.AddRange( GetListTransactionModels(transactionOutputData, isWatchOnly)); } // From bitcoin/src/wallet/rpcwallet.cpp: iterate backwards until we have nCount items to return listTransactionsModels.Sort((ListTransactionsModel x, ListTransactionsModel y) => { if (x.BlockHeight == y.BlockHeight) { return(0); } return(x.BlockHeight > y.BlockHeight ? -1 : 1); }); return(listTransactionsModels); }
private static TransactionItemModel FindSimilarReceivedTransactionOutput(List <TransactionItemModel> items, TransactionOutputData transaction) { TransactionItemModel existingTransaction = items.FirstOrDefault(i => i.Id == transaction.Id && i.Type == TransactionItemType.Received && i.ConfirmedInBlock == transaction.BlockHeight); return(existingTransaction); }
public static WalletHistoryModel GetHistory(IWalletManager walletManager, Network network, WalletHistoryRequest request) { var model = new WalletHistoryModel(); // 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 = walletManager.GetHistory(request.WalletName, request.AccountName); foreach (AccountHistory accountHistory in accountsHistory) { var transactionItems = new List <TransactionItemModel>(); var uniqueProcessedTxIds = new HashSet <uint256>(); IEnumerable <FlatHistory> query = accountHistory.History; if (!string.IsNullOrEmpty(request.Address)) { query = query.Where(x => x.Address.Address == request.Address); } // Sorting the history items by descending dates. That includes received and sent dates. List <FlatHistory> items = query .OrderBy(o => o.Transaction.IsConfirmed() ? 1 : 0) .ThenByDescending(o => o.Transaction.SpendingDetails?.CreationTime ?? o.Transaction.CreationTime) .ToList(); // Represents a sublist containing only the transactions that have already been spent. List <FlatHistory> spendingDetails = items.Where(t => t.Transaction.SpendingDetails != null).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(); // Represents a sublist of 'change' transactions. List <FlatHistory> allchange = items.Where(t => t.Address.IsChangeAddress()).ToList(); foreach (FlatHistory item in history) { // Count only unique transactions and limit it to MaxHistoryItemsPerAccount. int processedTransactions = uniqueProcessedTxIds.Count; if (processedTransactions >= WalletController.MaxHistoryItemsPerAccount) { break; } TransactionOutputData transaction = item.Transaction; HdAddress address = item.Address; // First we look for staking transaction as they require special attention. // A staking transaction spends one of our inputs into 2 outputs or more, paid to the same address. if (transaction.SpendingDetails?.IsCoinStake != null && transaction.SpendingDetails.IsCoinStake.Value) { // We look for the output(s) related to our spending input. List <FlatHistory> relatedOutputs = items.Where(h => h.Transaction.Id == transaction.SpendingDetails.TransactionId && h.Transaction.IsCoinStake != null && h.Transaction.IsCoinStake.Value).ToList(); if (relatedOutputs.Any()) { // Add staking transaction details. // The staked amount is calculated as the difference between the sum of the outputs and the input and should normally be equal to 1. var stakingItem = new TransactionItemModel { Type = TransactionItemType.Staked, ToAddress = address.Address, Amount = relatedOutputs.Sum(o => o.Transaction.Amount) - transaction.Amount, Id = transaction.SpendingDetails.TransactionId, Timestamp = transaction.SpendingDetails.CreationTime, ConfirmedInBlock = transaction.SpendingDetails.BlockHeight, BlockIndex = transaction.SpendingDetails.BlockIndex }; transactionItems.Add(stakingItem); uniqueProcessedTxIds.Add(stakingItem.Id); } // No need for further processing if the transaction itself is the output of a staking transaction. if (transaction.IsCoinStake != null) { continue; } } // If this is a normal transaction (not staking) that has been spent, add outgoing fund transaction details. if (transaction.SpendingDetails != null && transaction.SpendingDetails.IsCoinStake == null) { // Create a record for a 'send' transaction. uint256 spendingTransactionId = transaction.SpendingDetails.TransactionId; var sentItem = new TransactionItemModel { Type = TransactionItemType.Send, Id = spendingTransactionId, Timestamp = transaction.SpendingDetails.CreationTime, ConfirmedInBlock = transaction.SpendingDetails.BlockHeight, BlockIndex = transaction.SpendingDetails.BlockIndex, Amount = Money.Zero }; // If this 'send' transaction has made some external payments, i.e the funds were not sent to another address in the wallet. if (transaction.SpendingDetails.Payments != null) { sentItem.Payments = new List <PaymentDetailModel>(); foreach (PaymentDetails payment in transaction.SpendingDetails.Payments) { sentItem.Payments.Add(new PaymentDetailModel { DestinationAddress = payment.DestinationAddress, Amount = payment.Amount }); sentItem.Amount += payment.Amount; } } // Get the change address for this spending transaction. FlatHistory changeAddress = allchange.FirstOrDefault(a => a.Transaction.Id == spendingTransactionId); // Find all the spending details containing the spending transaction id and aggregate the sums. // This is our best shot at finding the total value of inputs for this transaction. var inputsAmount = new Money(spendingDetails.Where(t => t.Transaction.SpendingDetails.TransactionId == spendingTransactionId).Sum(t => t.Transaction.Amount)); // The fee is calculated as follows: funds in utxo - amount spent - amount sent as change. sentItem.Fee = inputsAmount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transaction.Amount); // Mined/staked coins add more coins to the total out. // That makes the fee negative. If that's the case ignore the fee. if (sentItem.Fee < 0) { sentItem.Fee = 0; } transactionItems.Add(sentItem); uniqueProcessedTxIds.Add(sentItem.Id); } // We don't show in history transactions that are outputs of staking transactions. if (transaction.IsCoinStake != null && transaction.IsCoinStake.Value && transaction.SpendingDetails == null) { continue; } // Create a record for a 'receive' transaction. if (transaction.IsCoinStake == null && !address.IsChangeAddress()) { // First check if we already have a similar transaction output, in which case we just sum up the amounts TransactionItemModel existingReceivedItem = FindSimilarReceivedTransactionOutput(transactionItems, transaction); if (existingReceivedItem == null) { // Add incoming fund transaction details. var receivedItem = new TransactionItemModel { Type = TransactionItemType.Received, ToAddress = address.Address, Amount = transaction.Amount, Id = transaction.Id, Timestamp = transaction.CreationTime, ConfirmedInBlock = transaction.BlockHeight, BlockIndex = transaction.BlockIndex }; transactionItems.Add(receivedItem); uniqueProcessedTxIds.Add(receivedItem.Id); } else { existingReceivedItem.Amount += transaction.Amount; } } } transactionItems = transactionItems.Distinct(new SentTransactionItemModelComparer()).Select(e => e).ToList(); // Sort and filter the history items. List <TransactionItemModel> itemsToInclude = transactionItems.OrderByDescending(t => t.Timestamp) .Where(x => String.IsNullOrEmpty(request.SearchQuery) || (x.Id.ToString() == request.SearchQuery || x.ToAddress == request.SearchQuery || x.Payments.Any(p => p.DestinationAddress == request.SearchQuery))) .Skip(request.Skip ?? 0) .Take(request.Take ?? transactionItems.Count) .ToList(); model.AccountsHistoryModel.Add(new AccountHistoryModel { TransactionsHistory = itemsToInclude, Name = accountHistory.Account.Name, CoinType = network.Consensus.CoinType, HdPath = accountHistory.Account.HdPath }); } return(model); }
public void CreateWalletData() { string walltName = "wallet-with-funds"; string path = Path.Combine(WalletOutputDataPath + @"\txdb", walltName + ".db"); if (File.Exists(path)) { File.Delete(path); } var network = new BitcoinRegTest(); var folder = new DataFolder(WalletOutputDataPath); var wallet = new Features.Wallet.Types.Wallet() { Name = walltName }; var walletStore = new WalletStore(network, folder, wallet); string dataPath = Path.Combine(DataPath, "wallet-data-table.json"); var dataTable = JsonConvert.DeserializeObject <WalletDataItem[]>(File.ReadAllText(dataPath))[0]; WalletData walletData = new WalletData { Key = dataTable.Id, WalletName = dataTable.WalletName, EncryptedSeed = dataTable.EncryptedSeed, WalletTip = new HashHeightPair(uint256.Parse(dataTable.WalletTip.Split("-")[1]), int.Parse(dataTable.WalletTip.Split("-")[0])), BlockLocator = dataTable.BlockLocator.Select(uint256.Parse).ToList() }; walletStore.SetData(walletData); dataPath = Path.Combine(DataPath, "wallet-transactions-table.json"); var transactionsTable = JsonConvert.DeserializeObject <TransactionDataItem[]>(File.ReadAllText(dataPath)); foreach (var item in transactionsTable) { var trx = new TransactionOutputData { OutPoint = new OutPoint(uint256.Parse(item.OutPoint.Split("-")[0]), int.Parse(item.OutPoint.Split("-")[1])), Address = item.Address, Id = uint256.Parse(item.Id), Amount = new Money(item.Amount), Index = item.Index, BlockHeight = item.BlockHeight, BlockHash = item.BlockHash != null?uint256.Parse(item.BlockHash) : null, CreationTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(item.CreationTime)), ScriptPubKey = new Script(Encoders.Hex.DecodeData(item.ScriptPubKey)), IsPropagated = item.IsPropagated, AccountIndex = item.AccountIndex, IsCoinStake = item.IsCoinStake, IsCoinBase = item.IsCoinBase, IsColdCoinStake = item.IsColdCoinStake, }; if (item.SpendingDetails != null) { trx.SpendingDetails = new Features.Wallet.Database.SpendingDetails { BlockHeight = item.SpendingDetails.BlockHeight, IsCoinStake = item.SpendingDetails.IsCoinStake, CreationTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(item.SpendingDetails.CreationTime)), TransactionId = uint256.Parse(item.SpendingDetails.TransactionId) }; if (item.SpendingDetails.Payments != null) { foreach (PaymentDetails spendingDetailsPayment in item.SpendingDetails.Payments) { trx.SpendingDetails.Payments.Add(new Features.Wallet.Database.PaymentDetails { Amount = new Money(spendingDetailsPayment.Amount), DestinationAddress = spendingDetailsPayment.DestinationAddress, DestinationScriptPubKey = new Script(Encoders.Hex.DecodeData(spendingDetailsPayment.DestinationScriptPubKey)), OutputIndex = spendingDetailsPayment.OutputIndex, PayToSelf = spendingDetailsPayment.PayToSelf }); } } } walletStore.InsertOrUpdate(trx); } }
public void ProcessTransactionWithValidColdStakingSetupLoadsTransactionsIntoWalletIfMatching() { DataFolder dataFolder = CreateDataFolder(this); Directory.CreateDirectory(dataFolder.WalletPath); Wallet.Types.Wallet wallet = this.walletFixture.GenerateBlankWallet("myWallet", "password"); (ExtKey ExtKey, string ExtPubKey)accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); (PubKey PubKey, BitcoinPubKeyAddress Address)spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); (PubKey PubKey, BitcoinPubKeyAddress Address)changeKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0"); Wallet.Types.Wallet coldWallet = this.walletFixture.GenerateBlankWallet("myColdWallet", "password"); (ExtKey ExtKey, string ExtPubKey)accountColdKeys = WalletTestsHelpers.GenerateAccountKeys(coldWallet, "password", $"m/44'/0'/{ColdStakingManager.ColdWalletAccountIndex}'"); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationColdKeys = WalletTestsHelpers.GenerateAddressKeys(coldWallet, accountColdKeys.ExtPubKey, "0/0"); Wallet.Types.Wallet hotWallet = this.walletFixture.GenerateBlankWallet("myHotWallet", "password"); (ExtKey ExtKey, string ExtPubKey)accountHotKeys = WalletTestsHelpers.GenerateAccountKeys(hotWallet, "password", $"m/44'/0'/{ColdStakingManager.HotWalletAccountIndex}'"); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationHotKeys = WalletTestsHelpers.GenerateAddressKeys(hotWallet, accountHotKeys.ExtPubKey, "0/0"); var spendingAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, }; var destinationColdAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/{ColdStakingManager.ColdWalletAccountIndex}'/0/0", Address = destinationColdKeys.Address.ToString(), Pubkey = destinationColdKeys.PubKey.ScriptPubKey, ScriptPubKey = destinationColdKeys.Address.ScriptPubKey, }; var destinationHotAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/{ColdStakingManager.HotWalletAccountIndex}'/0/0", Address = destinationHotKeys.Address.ToString(), Pubkey = destinationHotKeys.PubKey.ScriptPubKey, ScriptPubKey = destinationHotKeys.Address.ScriptPubKey, }; var changeAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/1/0", Address = changeKeys.Address.ToString(), Pubkey = changeKeys.PubKey.ScriptPubKey, ScriptPubKey = changeKeys.Address.ScriptPubKey, }; // Generate a spendable transaction (ChainIndexer chain, uint256 blockhash, Block block)chainInfo = WalletTestsHelpers.CreateChainAndCreateFirstBlockWithPaymentToAddress(wallet.Network, spendingAddress); TransactionOutputData spendingTransaction = WalletTestsHelpers.CreateTransactionDataFromFirstBlock(chainInfo); spendingTransaction.Address = spendingAddress.Address; wallet.walletStore.InsertOrUpdate(spendingTransaction); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account 0", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { spendingAddress }, InternalAddresses = new List <HdAddress> { changeAddress } }); coldWallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = ColdStakingManager.ColdWalletAccountIndex, Name = ColdStakingManager.ColdWalletAccountName, HdPath = $"m/44'/0'/{ColdStakingManager.ColdWalletAccountIndex}'", ExtendedPubKey = accountColdKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { destinationColdAddress }, InternalAddresses = new List <HdAddress> { } }); hotWallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = ColdStakingManager.HotWalletAccountIndex, Name = ColdStakingManager.HotWalletAccountName, HdPath = $"m/44'/0'/{ColdStakingManager.HotWalletAccountIndex}'", ExtendedPubKey = accountHotKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { destinationHotAddress }, InternalAddresses = new List <HdAddress> { } }); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetMinimumFee(258, 50)) .Returns(new Money(5000)); var walletSettings = new WalletSettings(new NodeSettings(network: this.Network)); var coldWalletManager = new ColdStakingManager(this.Network, chainInfo.chain, walletSettings, dataFolder, walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), new ScriptAddressReader(), this.LoggerFactory.Object, DateTimeProvider.Default, new Mock <ISignals>().Object, new Mock <IBroadcasterManager>().Object); coldWalletManager.Wallets.Add(wallet); coldWalletManager.Wallets.Add(coldWallet); coldWalletManager.LoadKeysLookup(); // Create another instance for the hot wallet as it is not allowed to have both wallets on the same instance. var hotWalletManager = new ColdStakingManager(this.Network, chainInfo.chain, walletSettings, dataFolder, walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), new ScriptAddressReader(), this.LoggerFactory.Object, DateTimeProvider.Default, new Mock <ISignals>().Object, new Mock <IBroadcasterManager>().Object); hotWalletManager.Wallets.Add(hotWallet); hotWalletManager.LoadKeysLookup(); // Create a cold staking setup transaction. Transaction transaction = this.CreateColdStakingSetupTransaction(wallet, "password", spendingAddress, destinationColdKeys.PubKey, destinationHotKeys.PubKey, changeAddress, new Money(7500), new Money(5000)); coldWalletManager.ProcessTransaction(transaction); hotWalletManager.ProcessTransaction(transaction); HdAddress spentAddressResult = wallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0); Assert.Equal(1, wallet.walletStore.GetForAddress(spendingAddress.Address).Count()); Assert.Equal(transaction.GetHash(), wallet.walletStore.GetForAddress(spentAddressResult.Address).ElementAt(0).SpendingDetails.TransactionId); Assert.Equal(transaction.Outputs[1].Value, wallet.walletStore.GetForAddress(spentAddressResult.Address).ElementAt(0).SpendingDetails.Payments.ElementAt(1).Amount); Assert.Equal(transaction.Outputs[1].ScriptPubKey, wallet.walletStore.GetForAddress(spentAddressResult.Address).ElementAt(0).SpendingDetails.Payments.ElementAt(1).DestinationScriptPubKey); Assert.Equal(1, wallet.walletStore.GetForAddress(wallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).InternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData changeAddressResult = wallet.walletStore.GetForAddress(wallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).InternalAddresses.ElementAt(0).Address).ElementAt(0); Assert.Equal(transaction.GetHash(), changeAddressResult.Id); Assert.Equal(transaction.Outputs[0].Value, changeAddressResult.Amount); Assert.Equal(transaction.Outputs[0].ScriptPubKey, changeAddressResult.ScriptPubKey); // Verify that the transaction has been recorded in the cold wallet. Assert.Equal(1, coldWallet.walletStore.GetForAddress(coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData destinationColdAddressResult = coldWallet.walletStore.GetForAddress(coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).ElementAt(0); Assert.Equal(transaction.GetHash(), destinationColdAddressResult.Id); Assert.Equal(transaction.Outputs[1].Value, destinationColdAddressResult.Amount); Assert.Equal(transaction.Outputs[1].ScriptPubKey, destinationColdAddressResult.ScriptPubKey); // Verify that the transaction has been recorded in the hot wallet. Assert.Equal(1, hotWallet.walletStore.GetForAddress(hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData destinationHotAddressResult = hotWallet.walletStore.GetForAddress(hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).ElementAt(0); Assert.Equal(transaction.GetHash(), destinationHotAddressResult.Id); Assert.Equal(transaction.Outputs[1].Value, destinationHotAddressResult.Amount); Assert.Equal(transaction.Outputs[1].ScriptPubKey, destinationHotAddressResult.ScriptPubKey); // Try withdrawing from the cold staking setup. Wallet.Types.Wallet withdrawalWallet = this.walletFixture.GenerateBlankWallet("myWithDrawalWallet", "password"); (ExtKey ExtKey, string ExtPubKey)withdrawalAccountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); (PubKey PubKey, BitcoinPubKeyAddress Address)withdrawalKeys = WalletTestsHelpers.GenerateAddressKeys(withdrawalWallet, withdrawalAccountKeys.ExtPubKey, "0/0"); // Withdrawing to this address. var withdrawalAddress = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = withdrawalKeys.Address.ToString(), Pubkey = withdrawalKeys.PubKey.ScriptPubKey, ScriptPubKey = withdrawalKeys.Address.ScriptPubKey, }; withdrawalWallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account 0", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { withdrawalAddress }, InternalAddresses = new List <HdAddress> { } }); // Will spend from the cold stake address and send the change back to the same address. var coldStakeAddress = coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0); Transaction withdrawalTransaction = this.CreateColdStakingWithdrawalTransaction(coldWallet, "password", coldStakeAddress, withdrawalKeys.PubKey, ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(destinationColdKeys.PubKey.Hash, destinationHotKeys.PubKey.Hash), new Money(750), new Money(262)); // Wallet manager for the wallet receiving the funds. var receivingWalletManager = new ColdStakingManager(this.Network, chainInfo.chain, walletSettings, dataFolder, walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), new ScriptAddressReader(), this.LoggerFactory.Object, DateTimeProvider.Default, new Mock <ISignals>().Object, new Mock <IBroadcasterManager>().Object); receivingWalletManager.Wallets.Add(withdrawalWallet); receivingWalletManager.LoadKeysLookup(); // Process the transaction in the cold wallet manager. coldWalletManager.ProcessTransaction(withdrawalTransaction); // Process the transaction in the hot wallet manager. hotWalletManager.ProcessTransaction(withdrawalTransaction); // Process the transaction in the receiving wallet manager. receivingWalletManager.ProcessTransaction(withdrawalTransaction); // Verify that the transaction has been recorded in the withdrawal wallet. Assert.Equal(1, withdrawalWallet.walletStore.GetForAddress(withdrawalWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData withdrawalAddressResult = withdrawalWallet.walletStore.GetForAddress(withdrawalWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).First(x => x.Id == withdrawalTransaction.GetHash()); Assert.Equal(withdrawalTransaction.GetHash(), withdrawalAddressResult.Id); Assert.Equal(withdrawalTransaction.Outputs[1].Value, withdrawalAddressResult.Amount); Assert.Equal(withdrawalTransaction.Outputs[1].ScriptPubKey, withdrawalAddressResult.ScriptPubKey); // Verify that the transaction has been recorded in the cold wallet. Assert.Equal(2, coldWallet.walletStore.GetForAddress(coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData coldAddressResult = coldWallet.walletStore.GetForAddress(coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).First(x => x.Id == withdrawalTransaction.GetHash()); Assert.Equal(withdrawalTransaction.GetHash(), coldAddressResult.Id); Assert.Equal(withdrawalTransaction.Outputs[0].Value, coldAddressResult.Amount); Assert.Equal(withdrawalTransaction.Outputs[0].ScriptPubKey, coldAddressResult.ScriptPubKey); // Verify that the transaction has been recorded in the hot wallet. Assert.Equal(2, hotWallet.walletStore.GetForAddress(hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).Count()); TransactionOutputData hotAddressResult = hotWallet.walletStore.GetForAddress(hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Address).First(x => x.Id == withdrawalTransaction.GetHash()); Assert.Equal(withdrawalTransaction.GetHash(), hotAddressResult.Id); Assert.Equal(withdrawalTransaction.Outputs[0].Value, hotAddressResult.Amount); Assert.Equal(withdrawalTransaction.Outputs[0].ScriptPubKey, hotAddressResult.ScriptPubKey); // Verify the hot amount returned by GetBalances. AccountBalance hotBalance = hotWalletManager.GetBalances("myHotWallet", ColdStakingManager.HotWalletAccountName).FirstOrDefault(); Assert.Equal(hotBalance.AmountUnconfirmed, hotAddressResult.Amount); // Verify the cold amount returned by GetBalances. AccountBalance coldBalance = coldWalletManager.GetBalances("myColdWallet", ColdStakingManager.ColdWalletAccountName).FirstOrDefault(); Assert.Equal(coldBalance.AmountUnconfirmed, coldAddressResult.Amount); }
public void WalletStore_GetBalanceForAddress_And_GetBalanceForAccount() { DataFolder dataFolder = CreateDataFolder(this); WalletStore store = new WalletStore(this.Network, dataFolder, new Types.Wallet { Name = "wallet1", EncryptedSeed = "EncryptedSeed1" }); // Create some temp data for (int indexAddress = 0; indexAddress < 3; indexAddress++) { var scriptInsert = new Key().PubKey.GetAddress(this.Network).ScriptPubKey.ToString(); for (int indexTrx = 0; indexTrx < 5; indexTrx++) { store.InsertOrUpdate(Create(new OutPoint(new uint256((ulong)indexTrx), indexAddress), scriptInsert)); } } string script = null; for (int accountIndex = 0; accountIndex < 2; accountIndex++) { script = new Key().PubKey.GetAddress(this.Network).ScriptPubKey.ToString(); TransactionOutputData trx = null; // spent trx = Create(new OutPoint(new uint256(21), accountIndex * 10), script, 2); store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(22), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(23), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; trx.BlockHeight = null; store.InsertOrUpdate(trx); // cold stake unspent spend trx = Create(new OutPoint(new uint256(3), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; trx.SpendingDetails = null; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(4), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; trx.SpendingDetails = null; store.InsertOrUpdate(trx); // cold stake unspent spend unconfirmed trx = Create(new OutPoint(new uint256(5), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; trx.BlockHeight = null; trx.SpendingDetails = null; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(6), accountIndex * 10), script, 2); trx.IsColdCoinStake = true; trx.BlockHeight = null; trx.SpendingDetails = null; store.InsertOrUpdate(trx); // unspent spend trx = Create(new OutPoint(new uint256(7), accountIndex * 10), script, 2); trx.IsColdCoinStake = false; trx.SpendingDetails = null; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(8), accountIndex * 10), script, 2); trx.IsColdCoinStake = null; trx.SpendingDetails = null; store.InsertOrUpdate(trx); // unspent spend unconfirmed trx = Create(new OutPoint(new uint256(9), accountIndex * 10), script, 2); trx.IsColdCoinStake = false; trx.BlockHeight = null; trx.SpendingDetails = null; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(10), accountIndex * 10), script, 2); trx.IsColdCoinStake = null; trx.BlockHeight = null; trx.SpendingDetails = null; store.InsertOrUpdate(trx); } var findforAddress = script; var res = store.GetBalanceForAddress(findforAddress, false); res.AmountConfirmed.Should().Be(20); res.AmountUnconfirmed.Should().Be(20); res = store.GetBalanceForAddress(findforAddress, true); res.AmountConfirmed.Should().Be(10); res.AmountUnconfirmed.Should().Be(10); res = store.GetBalanceForAccount(2, false); res.AmountConfirmed.Should().Be(40); res.AmountUnconfirmed.Should().Be(40); res = store.GetBalanceForAccount(2, true); res.AmountConfirmed.Should().Be(20); res.AmountUnconfirmed.Should().Be(20); }
public void WalletStore_GetAccountHistory() { DataFolder dataFolder = CreateDataFolder(this); WalletStore store = new WalletStore(this.Network, dataFolder, new Types.Wallet { Name = "wallet1", EncryptedSeed = "EncryptedSeed1" }); string script = null; script = new Key().PubKey.GetAddress(this.Network).ScriptPubKey.ToString(); TransactionOutputData trx = null; ulong index = 20; ulong time = 2000; var dt = DateTimeOffset.Now; // unconfirmed spent trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // with 4 outputs trx = Create(new OutPoint(new uint256(index++), 00), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0001), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0002), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0003), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails.BlockHeight = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // unconfirmed unspent trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // with 4 outputs trx = Create(new OutPoint(new uint256(index++), 00), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0001), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0002), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0003), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.BlockHeight = null; trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // confirmed spent trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // with 4 outputs trx = Create(new OutPoint(new uint256(index++), 00), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0001), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0002), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0003), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // confirmed unspent trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index++), 10), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); // with 4 outputs trx = Create(new OutPoint(new uint256(index++), 00), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0001), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = true; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0002), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); trx = Create(new OutPoint(new uint256(index), 0003), script, 2); trx.SpendingDetails.CreationTime = dt.AddMinutes(time--); trx.SpendingDetails = null; trx.IsColdCoinStake = false; store.InsertOrUpdate(trx); var res = store.GetAccountHistory(2, false); res.Should().HaveCount(22); res = store.GetAccountHistory(2, true); res.Should().HaveCount(15); }
private WalletTransactionHandlerTestContext SetupWallet() { DataFolder dataFolder = CreateDataFolder(this); Types.Wallet wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password"); (ExtKey ExtKey, string ExtPubKey)accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'"); (PubKey PubKey, BitcoinPubKeyAddress Address)spendingKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0"); (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1"); var address = new HdAddress { Index = 0, HdPath = $"m/44'/0'/0'/0/0", Address = spendingKeys.Address.ToString(), Pubkey = spendingKeys.PubKey.ScriptPubKey, ScriptPubKey = spendingKeys.Address.ScriptPubKey, // Transactions = new List<TransactionData>() }; var chain = new ChainIndexer(wallet.Network); WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.walletStore as WalletMemoryStore, wallet.Network, chain, address); TransactionOutputData addressTransaction = wallet.walletStore.GetForAddress(address.Address).First(); wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount { Index = 0, Name = "account1", HdPath = "m/44'/0'/0'", ExtendedPubKey = accountKeys.ExtPubKey, ExternalAddresses = new List <HdAddress> { address }, InternalAddresses = new List <HdAddress>() }); var walletFeePolicy = new Mock <IWalletFeePolicy>(); walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())) .Returns(new FeeRate(20000)); var walletManager = new WalletManager(this.LoggerFactory.Object, this.Network, chain, new WalletSettings(NodeSettings.Default(this.Network)), dataFolder, walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), DateTimeProvider.Default, this.scriptAddressReader); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, this.Network, this.standardTransactionPolicy); walletManager.Wallets.Add(wallet); var walletReference = new WalletAccountReference { AccountName = "account1", WalletName = "myWallet1" }; return(new WalletTransactionHandlerTestContext { Wallet = wallet, AccountKeys = accountKeys, DestinationKeys = destinationKeys, AddressTransaction = addressTransaction, WalletTransactionHandler = walletTransactionHandler, WalletReference = walletReference }); }
/** * Collects all related outputs for a given transaction. * 1. For "send" transactions, find a later transaction that spent an output. * 2. For "receive" transactions, just render the output. */ private IEnumerable <ListTransactionsModel> GetListTransactionModels( TransactionOutputData transactionOutputData, bool isWatchOnly) { var transaction = this.blockStore.GetTransactionById(transactionOutputData.Id); var chainedHeader = GetTransactionBlockHeader(transactionOutputData.Id); if (transaction == null) { return(new List <ListTransactionsModel>()); } var listTransactionModels = new List <ListTransactionsModel>(); uint blockTime = Utils.DateTimeToUnixTime(chainedHeader.Header.BlockTime); // Add all "receives" to the wallet. var listTransactionModel = new ListTransactionsModel() { InvolvesWatchOnly = isWatchOnly, Amount = transactionOutputData.Amount.ToDecimal(MoneyUnit.BTC), Address = transactionOutputData.Address, Category = ListSinceBlockTransactionCategoryModel.Receive, TransactionId = transaction.GetHash().ToString(), BlockHeight = chainedHeader.Height, BlockHash = chainedHeader.HashBlock.ToString(), BlockTime = blockTime, TransactionTime = blockTime, Confirmations = this.chainIndexer.Tip.Height - chainedHeader.Height + 1 }; if (transaction.IsCoinBase) { // COIN_MATURITY = 100 as part of bitcoin consensus. listTransactionModel.Category = listTransactionModel.Confirmations < 100 ? ListSinceBlockTransactionCategoryModel.Immature : ListSinceBlockTransactionCategoryModel.Generate; } listTransactionModels.Add(listTransactionModel); var spendingDetails = transactionOutputData.SpendingDetails; if (spendingDetails?.BlockHeight == null) { return(listTransactionModels); } var spendChainHeader = GetTransactionBlockHeader(spendingDetails.TransactionId); var spentTransactionOutput = new ListTransactionsModel() { InvolvesWatchOnly = isWatchOnly, Amount = -transactionOutputData.Amount.ToDecimal(MoneyUnit.BTC), Address = transactionOutputData.Address, Category = ListSinceBlockTransactionCategoryModel.Send, TransactionId = spendingDetails.TransactionId.ToString(), BlockHeight = spendingDetails.BlockHeight ?? -1, BlockHash = spendingDetails.IsSpentConfirmed() ? spendChainHeader.HashBlock.ToString() : "", BlockTime = spendChainHeader.Header.Time, Confirmations = this.chainIndexer.Tip.Height - spendChainHeader.Height + 1, TimeReceived = Utils.DateTimeToUnixTime(spendingDetails.CreationTime) }; listTransactionModels.Add(spentTransactionOutput); return(listTransactionModels); }