diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index bc1ea2287e6..d1f67e530dd 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -79,6 +79,7 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO result.txout_address_is_mine.emplace_back(ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ? IsMine(wallet, result.txout_address.back()) : ISMINE_NO); + result.txout_is_change.emplace_back(wallet.IsChange(txout)); } // ELEMENTS: Retrieve unblinded information about outputs for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index d67861a2570..3ad1fc667fa 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -337,6 +337,7 @@ struct WalletTx CTransactionRef tx; std::vector txin_is_mine; std::vector txout_is_mine; + std::vector txout_is_change; std::vector txout_address; std::vector txout_address_is_mine; std::vector txout_amounts; diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index ecc0f852db2..e20899c89b1 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -36,54 +36,21 @@ QList TransactionRecord::decomposeTransaction(const interface uint256 hash = wtx.tx->GetHash(); std::map mapValue = wtx.value_map; - if (nNet > 0 || wtx.is_coinbase) - { - // - // Credit - // - for(unsigned int i = 0; i < wtx.tx->vout.size(); i++) - { - isminetype mine = wtx.txout_is_mine[i]; - if(mine) - { - TransactionRecord sub(hash, nTime); - CTxDestination address; - sub.idx = i; // vout index - sub.amount = wtx.txout_amounts[i]; - sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; - if (wtx.txout_address_is_mine[i]) - { - // Received by Bitcoin Address - sub.type = TransactionRecord::RecvWithAddress; - sub.address = EncodeDestination(wtx.txout_address[i]); - sub.asset = wtx.txout_assets[i]; - } - else - { - // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction - sub.type = TransactionRecord::RecvFromOther; - sub.address = mapValue["from"]; - sub.asset = wtx.txout_assets[i]; - } - if (wtx.is_coinbase) - { - // Generated - sub.type = TransactionRecord::Generated; - sub.asset = wtx.txout_assets[i]; - } - - parts.append(sub); - } - } + bool involvesWatchAddress = false; + isminetype fAllFromMe = ISMINE_SPENDABLE; + bool any_from_me = false; + std::set assets_issued_to_me_only; + if (wtx.is_coinbase) { + fAllFromMe = ISMINE_NO; } else { - bool involvesWatchAddress = false; - isminetype fAllFromMe = ISMINE_SPENDABLE; - std::set assets_issued_to_me_only; CAmountMap assets_received_by_me_only; for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + if (wtx.tx->vout[i].IsFee()) { + continue; + } const CAsset& asset = wtx.txout_assets[i]; if (assets_received_by_me_only.count(asset) && assets_received_by_me_only.at(asset) < 0) { // Already known to be received by not-me @@ -97,117 +64,71 @@ QList TransactionRecord::decomposeTransaction(const interface } } + any_from_me = false; for (size_t i = 0; i < wtx.tx->vin.size(); ++i) { + /* Issuance detection */ isminetype mine = wtx.txin_is_mine[i]; if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(fAllFromMe > mine) fAllFromMe = mine; - const CAsset& asset = wtx.txin_issuance_asset[i]; - const CAmount& asset_amount = wtx.txin_issuance_asset_amount[i]; - const CAsset& token = wtx.txin_issuance_token[i]; - const CAmount& token_amount = wtx.txin_issuance_token_amount[i]; - if (!asset.IsNull()) { - if (assets_received_by_me_only.count(asset) == 0) { - continue; - } - if (asset_amount == assets_received_by_me_only.at(asset)) { - // Special case: collapse the chain of issue, send, receive to just an issue - assets_issued_to_me_only.insert(asset); - continue; - } - - TransactionRecord sub(hash, nTime); - sub.involvesWatchAddress = involvesWatchAddress; - sub.asset = asset; - sub.amount = asset_amount; - sub.type = TransactionRecord::IssuedAsset; - parts.append(sub); - } - if (!token.IsNull()) { - if (assets_received_by_me_only.count(token) == 0) { - continue; + if (mine) any_from_me = true; + CAmountMap assets; + assets[wtx.txin_issuance_asset[i]] = wtx.txin_issuance_asset_amount[i]; + assets[wtx.txin_issuance_token[i]] = wtx.txin_issuance_token_amount[i]; + for (const auto& asset : assets) { + if (!asset.first.IsNull()) { + if (assets_received_by_me_only.count(asset.first) == 0) { + continue; + } + if (asset.second == assets_received_by_me_only.at(asset.first)) { + // Special case: collapse the chain of issue, send, receive to just an issue + assets_issued_to_me_only.insert(asset.first); + continue; + } else { + TransactionRecord sub(hash, nTime); + sub.involvesWatchAddress = involvesWatchAddress; + sub.asset = asset.first; + sub.amount = asset.second; + sub.type = TransactionRecord::IssuedAsset; + parts.append(sub); + } } - if (token_amount == assets_received_by_me_only.at(asset)) { - // Special case: collapse the chain of issue, send, receive to just an issue - assets_issued_to_me_only.insert(asset); - continue; - } - - TransactionRecord sub(hash, nTime); - sub.involvesWatchAddress = involvesWatchAddress; - sub.asset = token; - sub.amount = token_amount; - sub.type = TransactionRecord::IssuedAsset; - parts.append(sub); - } - - if (!wtx.txin_issuance_token[i].IsNull()) { - TransactionRecord sub(hash, nTime); - sub.involvesWatchAddress = involvesWatchAddress; - sub.asset = wtx.txin_issuance_token[i]; - sub.amount = wtx.txin_issuance_token_amount[i]; - sub.type = TransactionRecord::IssuedAsset; - parts.append(sub); } } + } - isminetype fAllToMe = ISMINE_SPENDABLE; - for (unsigned int i = 0; i < wtx.txout_is_mine.size(); ++i) { - const isminetype mine = wtx.txout_is_mine[i]; - const CTxOut txout = wtx.tx->vout[i]; + if (fAllFromMe || !any_from_me) { + for(unsigned int i = 0; i < wtx.tx->vout.size(); i++) + { + const CTxOut& txout = wtx.tx->vout[i]; + const CAsset& asset = wtx.txout_assets[i]; if (txout.IsFee()) { // explicit fee; ignore continue; } - if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; - if(fAllToMe > mine) fAllToMe = mine; - } - - if (fAllFromMe && fAllToMe) - { - // Payment to self - CAmount nChange = valueFor(wtx.change, ::policyAsset); - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange) + (nCredit - nChange), ::policyAsset)); - parts.last().involvesWatchAddress = involvesWatchAddress; // maybe pass to TransactionRecord as constructor argument - } - else if (fAllFromMe) - { - - // - // Debit - // - - for (unsigned int nOut = 0; nOut < wtx.tx->vout.size(); nOut++) - { - - const CTxOut& txout = wtx.tx->vout[nOut]; - const CAsset& asset = wtx.txout_assets[nOut]; + if (fAllFromMe && assets_issued_to_me_only.count(asset) == 0) { + // Change is only really possible if we're the sender + // Otherwise, someone just sent bitcoins to a change address, which should be shown - if(wtx.txout_is_mine[nOut] || txout.IsFee()) - { - // Ignore parts sent to self, as this is usually the change - // from a transaction sent back to our own address. - continue; - } - - // Short-circuit when it's an issuance to self - if (assets_issued_to_me_only.count(asset) != 0) { + if (wtx.txout_is_change[i]) { continue; } + // + // Debit + // TransactionRecord sub(hash, nTime); - sub.idx = nOut; + sub.idx = i; sub.involvesWatchAddress = involvesWatchAddress; - sub.amount = -wtx.txout_amounts[nOut]; + sub.amount = -wtx.txout_amounts[i]; sub.asset = asset; - if (!boost::get(&wtx.txout_address[nOut])) + if (!boost::get(&wtx.txout_address[i])) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; - sub.address = EncodeDestination(wtx.txout_address[nOut]); + sub.address = EncodeDestination(wtx.txout_address[i]); } else { @@ -215,14 +136,53 @@ QList TransactionRecord::decomposeTransaction(const interface sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } - if (assets_issued_to_me_only.count(asset)) { + parts.append(sub); + } + + isminetype mine = wtx.txout_is_mine[i]; + if(mine) + { + // + // Credit + // + + TransactionRecord sub(hash, nTime); + CTxDestination address; + sub.idx = i; // vout index + sub.amount = wtx.txout_amounts[i]; + sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; + if (wtx.txout_address_is_mine[i]) + { + // Received by Bitcoin Address + sub.type = TransactionRecord::RecvWithAddress; + sub.address = EncodeDestination(wtx.txout_address[i]); + sub.asset = asset; + } + else + { + // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction + sub.type = TransactionRecord::RecvFromOther; + sub.address = mapValue["from"]; + sub.asset = wtx.txout_assets[i]; + } + if (wtx.is_coinbase) + { + // Generated + sub.type = TransactionRecord::Generated; + sub.asset = wtx.txout_assets[i]; + } + if (assets_issued_to_me_only.count(wtx.txout_assets[i])) { sub.type = TransactionRecord::IssuedAsset; } + parts.append(sub); } + } + if (fAllFromMe) { for (const auto& tx_fee : GetFeeMap(*wtx.tx)) { if (!tx_fee.second) continue; + TransactionRecord sub(hash, nTime); sub.type = TransactionRecord::Fee; sub.asset = tx_fee.first; @@ -230,14 +190,14 @@ QList TransactionRecord::decomposeTransaction(const interface parts.append(sub); } } - else - { - // - // Mixed debit transaction, can't break down payees - // - parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, CAsset())); - parts.last().involvesWatchAddress = involvesWatchAddress; - } + } + else + { + // + // Mixed debit transaction, can't break down payees + // + parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, CAsset())); + parts.last().involvesWatchAddress = involvesWatchAddress; } return parts;