From 003fb1933eb1d95e9e4fa440c2ba20b61472fc22 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Thu, 11 Apr 2019 14:47:14 -0400 Subject: [PATCH] QT: Carbon copy 0.14-based transaction accounting --- src/interfaces/wallet.cpp | 1 + src/interfaces/wallet.h | 1 + src/qt/transactionrecord.cpp | 276 +++++++++++++++-------------------- 3 files changed, 120 insertions(+), 158 deletions(-) diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index bc1ea2287e..af06b8d7a7 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.push_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 d67861a257..3ad1fc667f 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 ecc0f852db..e20899c89b 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -36,16 +36,116 @@ QList TransactionRecord::decomposeTransaction(const interface uint256 hash = wtx.tx->GetHash(); std::map mapValue = wtx.value_map; - if (nNet > 0 || wtx.is_coinbase) + 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 { - // - // Credit - // + 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 + continue; + } + isminetype mine = wtx.txout_address_is_mine[i]; + if (!mine) { + assets_received_by_me_only[asset] = -1; + } else { + assets_received_by_me_only[asset] += wtx.txout_amounts[i]; + } + } + + 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; + 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 (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 (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_change[i]) { + continue; + } + + // + // Debit + // + TransactionRecord sub(hash, nTime); + sub.idx = i; + sub.involvesWatchAddress = involvesWatchAddress; + sub.amount = -wtx.txout_amounts[i]; + sub.asset = asset; + + if (!boost::get(&wtx.txout_address[i])) + { + // Sent to Bitcoin Address + sub.type = TransactionRecord::SendToAddress; + sub.address = EncodeDestination(wtx.txout_address[i]); + } + else + { + // Sent to IP, or other non-address transaction like OP_EVAL + sub.type = TransactionRecord::SendToOther; + sub.address = mapValue["to"]; + } + parts.append(sub); + } + isminetype mine = wtx.txout_is_mine[i]; if(mine) { + // + // Credit + // + TransactionRecord sub(hash, nTime); CTxDestination address; sub.idx = i; // vout index @@ -56,7 +156,7 @@ QList TransactionRecord::decomposeTransaction(const interface // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeDestination(wtx.txout_address[i]); - sub.asset = wtx.txout_assets[i]; + sub.asset = asset; } else { @@ -71,158 +171,18 @@ QList TransactionRecord::decomposeTransaction(const interface sub.type = TransactionRecord::Generated; sub.asset = wtx.txout_assets[i]; } - - parts.append(sub); - } - } - } - 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++) - { - 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 - continue; - } - isminetype mine = wtx.txout_address_is_mine[i]; - if (!mine) { - assets_received_by_me_only[asset] = -1; - } else { - assets_received_by_me_only[asset] += wtx.txout_amounts[i]; - } - } - - for (size_t i = 0; i < wtx.tx->vin.size(); ++i) - { - 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 (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 (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(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) { - continue; - } - - TransactionRecord sub(hash, nTime); - sub.idx = nOut; - sub.involvesWatchAddress = involvesWatchAddress; - sub.amount = -wtx.txout_amounts[nOut]; - sub.asset = asset; - - if (!boost::get(&wtx.txout_address[nOut])) - { - // Sent to Bitcoin Address - sub.type = TransactionRecord::SendToAddress; - sub.address = EncodeDestination(wtx.txout_address[nOut]); - } - else - { - // Sent to IP, or other non-address transaction like OP_EVAL - sub.type = TransactionRecord::SendToOther; - sub.address = mapValue["to"]; - } - if (assets_issued_to_me_only.count(asset)) { + 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;