public void VerifyThatColdStakeTransactionCanBeFiltered() { this.Initialize(); this.CreateMempoolManager(); this.coldStakingManager.CreateWallet(walletPassword, walletName1, walletPassphrase, new Mnemonic(walletMnemonic1)); Wallet.Wallet wallet1 = this.coldStakingManager.GetWalletByName(walletName1); Transaction prevTran = this.AddSpendableTransactionToWallet(wallet1); IActionResult result = this.coldStakingController.SetupColdStaking(new SetupColdStakingRequest { HotWalletAddress = hotWalletAddress1, ColdWalletAddress = coldWalletAddress2, WalletName = walletName1, WalletAccount = walletAccount, WalletPassword = walletPassword, Amount = "100", Fees = "0.01" }); var jsonResult = Assert.IsType <JsonResult>(result); var response = Assert.IsType <SetupColdStakingResponse>(jsonResult.Value); var transaction = Assert.IsType <PosTransaction>(this.Network.CreateTransaction(response.TransactionHex)); Assert.Equal("OP_DUP OP_HASH160 OP_ROT OP_IF OP_CHECKCOLDSTAKEVERIFY 90c582cb91d6b6d777c31c891d4943fed4edac3a OP_ELSE 92dfb829d31cefe6a0731f3432dea41596a278d9 OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG", transaction.Outputs[1].ScriptPubKey.ToString()); // Get the ColdStaking script template if available. Dictionary <string, ScriptTemplate> templates = this.coldStakingManager.GetValidStakingTemplates(); ScriptTemplate coldStakingTemplate = templates["ColdStaking"]; Assert.True(coldStakingTemplate.CheckScriptPubKey(transaction.Outputs[1].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 GetTransactionModel GetTransaction(string txid) { if (!uint256.TryParse(txid, out uint256 trxid)) { throw new ArgumentException(nameof(txid)); } WalletAccountReference accountReference = this.GetWalletAccountReference(); Wallet hdWallet = this.walletManager.WalletRepository.GetWallet(accountReference.WalletName); HdAccount hdAccount = this.walletManager.WalletRepository.GetAccounts(hdWallet, accountReference.AccountName).First(); IWalletAddressReadOnlyLookup addressLookup = this.walletManager.WalletRepository.GetWalletAddressLookup(accountReference.WalletName); bool IsChangeAddress(Script scriptPubKey) { return(addressLookup.Contains(scriptPubKey, out AddressIdentifier addressIdentifier) && addressIdentifier.AddressType == 1); } // Get the transaction from the wallet by looking into received and send transactions. List <TransactionData> receivedTransactions = this.walletManager.WalletRepository.GetTransactionOutputs(hdAccount, null, trxid, true) .Where(td => !IsChangeAddress(td.ScriptPubKey)).ToList(); List <TransactionData> sentTransactions = this.walletManager.WalletRepository.GetTransactionInputs(hdAccount, null, trxid, true).ToList(); TransactionData firstReceivedTransaction = receivedTransactions.FirstOrDefault(); TransactionData firstSendTransaction = sentTransactions.FirstOrDefault(); if (firstReceivedTransaction == null && firstSendTransaction == null) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id."); } uint256 blockHash = null; int? blockHeight, blockIndex; DateTimeOffset transactionTime; SpendingDetails spendingDetails = firstSendTransaction?.SpendingDetails; if (firstReceivedTransaction != null) { blockHeight = firstReceivedTransaction.BlockHeight; blockIndex = firstReceivedTransaction.BlockIndex; blockHash = firstReceivedTransaction.BlockHash; transactionTime = firstReceivedTransaction.CreationTime; } else { blockHeight = spendingDetails.BlockHeight; blockIndex = spendingDetails.BlockIndex; blockHash = spendingDetails.BlockHash; transactionTime = spendingDetails.CreationTime; } // 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; if (block != null) { if (blockIndex == null) { blockIndex = block.Transactions.FindIndex(t => t.GetHash() == trxid); } transactionFromStore = block.Transactions[(int)blockIndex]; } } bool isGenerated; string hex; if (transactionFromStore != null) { transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time); isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake; hex = transactionFromStore.ToHex(); } else { 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, BlockTime = block?.Header.BlockTime.ToUnixTimeSeconds(), TransactionId = uint256.Parse(txid), TransactionTime = transactionTime.ToUnixTimeSeconds(), TimeReceived = transactionTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = hex }; // Send transactions details. if (spendingDetails != null) { Money feeSent = Money.Zero; if (firstSendTransaction != null) { // Get the change. long change = spendingDetails.Change.Sum(o => o.Amount); Money inputsAmount = new Money(sentTransactions.Sum(i => i.Amount)); Money outputsAmount = new Money(spendingDetails.Payments.Sum(p => p.Amount) + change); feeSent = inputsAmount - outputsAmount; } var details = spendingDetails.Payments .GroupBy(detail => detail.DestinationAddress) .Select(p => new GetTransactionDetailsModel() { Address = p.Key, Category = GetTransactionDetailsCategoryModel.Send, OutputIndex = p.First().OutputIndex, Amount = 0 - p.Sum(detail => detail.Amount.ToDecimal(MoneyUnit.BTC)), Fee = -feeSent.ToDecimal(MoneyUnit.BTC) }); model.Details.AddRange(details); } // 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. IScriptAddressReader scriptAddressReader = this.FullNode.NodeService <IScriptAddressReader>(); foreach (TransactionData 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 recognize 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; } string address = scriptAddressReader.GetAddressFromScriptPubKey(this.FullNode.Network, trxInWallet.ScriptPubKey); model.Details.Add(new GetTransactionDetailsModel { Address = 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); }