#include #include #include std::vector TxList::ListAll(const isminefilter& filter_ismine) { std::vector tx_records; for (const auto& entry : m_wallet.mapWallet) { List(tx_records, entry.second, filter_ismine); } return tx_records; } std::vector TxList::List(const CWalletTx& wtx, const isminefilter& filter_ismine, const boost::optional& nMinDepth, const boost::optional& filter_label) { std::vector tx_records; List(tx_records, wtx, filter_ismine); auto iter = tx_records.begin(); while (iter != tx_records.end()) { if (iter->credit > 0) { // Filter received transactions if (nMinDepth && wtx.GetDepthInMainChain() < *nMinDepth) { iter = tx_records.erase(iter); continue; } if (filter_label) { std::string label; CTxDestination destination = DecodeDestination(iter->address); if (IsValidDestination(destination)) { const auto* address_book_entry = m_wallet.FindAddressBookEntry(destination); if (address_book_entry) { label = address_book_entry->GetLabel(); } } if (label != *filter_label) { iter = tx_records.erase(iter); continue; } } } else { // Filter sent transactions if (filter_label) { iter = tx_records.erase(iter); continue; } } iter++; } return tx_records; } void TxList::List(std::vector& tx_records, const CWalletTx& wtx, const isminefilter& filter_ismine) { CAmount nCredit = wtx.GetCredit(filter_ismine); CAmount nDebit = wtx.GetDebit(filter_ismine); CAmount nNet = nCredit - nDebit; if (!IsMine(wtx)) { return; } if (nNet > 0 || wtx.IsCoinBase() || wtx.IsHogEx()) { // Credit List_Credit(tx_records, wtx, filter_ismine); } else if (IsAllFromMe(wtx)) { if (IsAllToMe(wtx)) { // Payment to Self List_SelfSend(tx_records, wtx, filter_ismine); } else { // Debit List_Debit(tx_records, wtx, filter_ismine); } } else { // Mixed debit transaction, can't break down payees WalletTxRecord tx_record(&m_wallet, &wtx); tx_record.type = WalletTxRecord::Type::Other; tx_record.debit = nNet; tx_records.push_back(std::move(tx_record)); } } void TxList::List_Credit(std::vector& tx_records, const CWalletTx& wtx, const isminefilter& filter_ismine) { std::vector outputs = wtx.GetOutputs(); for (size_t i = 0; i < outputs.size(); i++) { const CTxOutput& output = outputs[i]; if (!output.IsMWEB() && output.GetScriptPubKey().IsMWEBPegin()) { continue; } isminetype ismine = m_wallet.IsMine(output); if (ismine & filter_ismine) { // Skip displaying hog-ex outputs when we have the MWEB transaction that contains the pegout. // The original MWEB transaction will be displayed instead. if (wtx.IsHogEx() && wtx.pegout_indices.size() > i) { mw::Hash kernel_id = wtx.pegout_indices[i].first; if (m_wallet.FindWalletTxByKernelId(kernel_id) != nullptr) { continue; } } WalletTxRecord sub(&m_wallet, &wtx, output.GetIndex()); sub.credit = m_wallet.GetValue(output); sub.involvesWatchAddress = ismine & ISMINE_WATCH_ONLY; if (IsAddressMine(output)) { // Received by Litecoin Address sub.type = WalletTxRecord::Type::RecvWithAddress; sub.address = GetAddress(output).Encode(); } else { // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction sub.type = WalletTxRecord::Type::RecvFromOther; sub.address = wtx.mapValue.count("from") > 0 ? wtx.mapValue.at("from") : ""; } if (wtx.IsCoinBase()) { sub.type = WalletTxRecord::Type::Generated; } tx_records.push_back(sub); } } // Include pegouts to addresses belonging to the wallet. if (wtx.tx->HasMWEBTx()) { for (const Kernel& kernel : wtx.tx->mweb_tx.m_transaction->GetKernels()) { for (size_t i = 0; i < kernel.GetPegOuts().size(); i++) { const PegOutCoin& pegout = kernel.GetPegOuts()[i]; if (!(m_wallet.IsMine(DestinationAddr{pegout.GetScriptPubKey()}) & filter_ismine)) { continue; } WalletTxRecord tx_record(&m_wallet, &wtx, PegoutIndex{kernel.GetKernelID(), i}); tx_record.type = WalletTxRecord::Type::RecvWithAddress; tx_record.credit = pegout.GetAmount(); tx_record.address = DestinationAddr(pegout.GetScriptPubKey()).Encode(); tx_records.push_back(std::move(tx_record)); } } } } void TxList::List_Debit(std::vector& tx_records, const CWalletTx& wtx, const isminefilter& filter_ismine) { CAmount nTxFee = wtx.GetFee(filter_ismine); for (const CTxOutput& output : wtx.GetOutputs()) { // If the output is a peg-in script, and we have the MWEB pegin tx, then ignore the peg-in script output. // If it's a peg-in script, and we don't have the MWEB pegin tx, treat the output as a spend. mw::Hash kernel_id; if (!output.IsMWEB() && output.GetScriptPubKey().IsMWEBPegin(&kernel_id)) { if (wtx.tx->HasMWEBTx() && wtx.tx->mweb_tx.GetKernelIDs().count(kernel_id) > 0) { continue; } } if (m_wallet.IsMine(output)) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } WalletTxRecord tx_record(&m_wallet, &wtx, output.GetIndex()); tx_record.debit = -m_wallet.GetValue(output); DestinationAddr address = GetAddress(output); if (!address.IsEmpty()) { // Sent to Litecoin Address tx_record.type = WalletTxRecord::Type::SendToAddress; tx_record.address = address.Encode(); } else { // Sent to IP, or other non-address transaction like OP_EVAL tx_record.type = WalletTxRecord::Type::SendToOther; tx_record.address = wtx.mapValue.count("to") > 0 ? wtx.mapValue.at("to") : ""; } /* Add fee to first output */ if (nTxFee > 0) { tx_record.fee = -nTxFee; nTxFee = 0; } tx_records.push_back(tx_record); } if (wtx.tx->HasMWEBTx()) { for (const Kernel& kernel : wtx.tx->mweb_tx.m_transaction->GetKernels()) { for (size_t i = 0; i < kernel.GetPegOuts().size(); i++) { const PegOutCoin& pegout = kernel.GetPegOuts()[i]; if (m_wallet.IsMine(DestinationAddr{pegout.GetScriptPubKey()})) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } WalletTxRecord tx_record(&m_wallet, &wtx, PegoutIndex{kernel.GetKernelID(), i}); tx_record.debit = -pegout.GetAmount(); tx_record.type = WalletTxRecord::Type::SendToAddress; tx_record.address = DestinationAddr(pegout.GetScriptPubKey()).Encode(); /* Add fee to first output */ if (nTxFee > 0) { tx_record.fee = -nTxFee; nTxFee = 0; } tx_records.push_back(std::move(tx_record)); } } } } void TxList::List_SelfSend(std::vector& tx_records, const CWalletTx& wtx, const isminefilter& filter_ismine) { std::string address; for (const CTxOutput& output : wtx.GetOutputs()) { if (!output.IsMWEB() && output.GetScriptPubKey().IsMWEBPegin()) { continue; } if (!address.empty()) address += ", "; address += GetAddress(output).Encode(); } for (const PegOutCoin& pegout : wtx.tx->mweb_tx.GetPegOuts()) { if (!address.empty()) address += ", "; address += DestinationAddr(pegout.GetScriptPubKey()).Encode(); } CAmount nCredit = wtx.GetCredit(filter_ismine); CAmount nDebit = wtx.GetDebit(filter_ismine); CAmount nChange = wtx.GetChange(); WalletTxRecord tx_record(&m_wallet, &wtx); tx_record.type = WalletTxRecord::SendToSelf; tx_record.address = address; tx_record.debit = -(nDebit - nChange); tx_record.credit = nCredit - nChange; tx_records.push_back(std::move(tx_record)); } isminetype TxList::IsAddressMine(const CTxOutput& txout) { CTxDestination dest; return m_wallet.ExtractOutputDestination(txout, dest) ? m_wallet.IsMine(dest) : ISMINE_NO; } DestinationAddr TxList::GetAddress(const CTxOutput& output) { if (!output.IsMWEB()) { return output.GetTxOut().scriptPubKey; } mw::Coin coin; if (m_wallet.GetCoin(output.ToMWEB(), coin)) { StealthAddress addr; if (m_wallet.GetMWWallet()->GetStealthAddress(coin, addr)) { return addr; } } return DestinationAddr{}; } bool TxList::IsAllFromMe(const CWalletTx& wtx) { for (const CTxInput& input : wtx.GetInputs()) { if (!m_wallet.IsMine(input)) { return false; } } return true; } bool TxList::IsAllToMe(const CWalletTx& wtx) { for (const CTxOutput& output : wtx.GetOutputs()) { // If we don't have the MWEB peg-in tx, then we treat it as an output not belonging to ourselves. mw::Hash kernel_id; if (!output.IsMWEB() && output.GetScriptPubKey().IsMWEBPegin(&kernel_id)) { if (wtx.tx->HasMWEBTx() && wtx.tx->mweb_tx.GetKernelIDs().count(kernel_id) > 0) { continue; } } if (!m_wallet.IsMine(output)) { return false; } } // Also check pegouts for (const PegOutCoin& pegout : wtx.tx->mweb_tx.GetPegOuts()) { if (!m_wallet.IsMine(DestinationAddr{pegout.GetScriptPubKey()})) { return false; } } return true; } // A few release candidates of v0.21.2 added some transactions to the wallet that didn't actually belong to it. // This is a temporary band-aid to filter out these transactions from the list. // We can consider removing it after testing, since only a limited number of testnet wallets should've been impacted. bool TxList::IsMine(const CWalletTx& wtx) { for (const CTxInput& input : wtx.GetInputs()) { if (m_wallet.IsMine(input)) { return true; } } for (const CTxOutput& output : wtx.GetOutputs()) { if (m_wallet.IsMine(output)) { return true; } } for (const PegOutCoin& pegout : wtx.tx->mweb_tx.GetPegOuts()) { if (m_wallet.IsMine(DestinationAddr{pegout.GetScriptPubKey()})) { return true; } } return false; }