Handle "conflicted" transactions properly
authorMASM fan <masmfan@gmail.com>
Sun, 16 Feb 2014 00:47:42 +0000 (04:47 +0400)
committerMASM fan <masmfan@gmail.com>
Sun, 16 Feb 2014 00:47:42 +0000 (04:47 +0400)
- Extend CMerkleTx::GetDepthInMainChain with the concept of a "conflicted" transaction - a transaction generated by the wallet that is not in the main chain or in the mempool, and, therefore, will likely never be confirmed.

- Exclamation mark icon for conflicted transactions
- Show mouseover status for conflicted transactions as "conflicted"
- Don't show inactive transactions on overview page overview

16 files changed:
src/main.cpp
src/qt/bitcoin.qrc
src/qt/overviewpage.cpp
src/qt/res/icons/transaction_conflicted.png [new file with mode: 0644]
src/qt/transactiondesc.cpp
src/qt/transactionfilterproxy.cpp
src/qt/transactionfilterproxy.h
src/qt/transactionrecord.cpp
src/qt/transactionrecord.h
src/qt/transactiontablemodel.cpp
src/qt/transactiontablemodel.h
src/qt/walletmodel.cpp
src/rpcwallet.cpp
src/version.h
src/wallet.cpp
src/wallet.h

index 1f62d80..f3682e7 100644 (file)
@@ -874,27 +874,34 @@ void CTxMemPool::queryHashes(std::vector<uint256>& vtxid)
         vtxid.push_back((*mi).first);
 }
 
-
-
-
+// Return depth of transaction in blockchain:
+// -1  : not in blockchain, and not in memory pool (conflicted transaction)
+//  0  : in memory pool, waiting to be included in a block
+// >=1 : this many blocks deep in the main chain
 int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const
 {
-    if (hashBlock == 0 || nIndex == -1)
-        return 0;
+    bool fInMemPool = mempool.exists(GetHash());
+
+    if (hashBlock == 0 || nIndex == -1) {
+        return fInMemPool ? 0 : -1;
+    }
 
     // Find the block it claims to be in
     map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
-    if (mi == mapBlockIndex.end())
-        return 0;
+    if (mi == mapBlockIndex.end()) {
+        return fInMemPool ? 0 : -1;
+    }
     CBlockIndex* pindex = (*mi).second;
-    if (!pindex || !pindex->IsInMainChain())
-        return 0;
+    if (!pindex || !pindex->IsInMainChain()) {
+        return fInMemPool ? 0 : -1;
+    }
 
     // Make sure the merkle branch connects to this block
     if (!fMerkleVerified)
     {
-        if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
-            return 0;
+        if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot) {
+            return fInMemPool ? 0 : -1;
+        }
         fMerkleVerified = true;
     }
 
index f5ea25c..137eed9 100644 (file)
@@ -12,6 +12,7 @@
         <file alias="connect_4">res/icons/connect4_16.png</file>
         <file alias="transaction_0">res/icons/transaction0.png</file>
         <file alias="transaction_confirmed">res/icons/transaction2.png</file>
+        <file alias="transaction_conflicted">res/icons/transaction_conflicted.png</file>
         <file alias="transaction_1">res/icons/clock1.png</file>
         <file alias="transaction_2">res/icons/clock2.png</file>
         <file alias="transaction_3">res/icons/clock3.png</file>
index f176151..590bc0e 100644 (file)
@@ -162,8 +162,8 @@ void OverviewPage::setModel(WalletModel *model)
         filter->setSourceModel(model->getTransactionTableModel());
         filter->setLimit(NUM_ITEMS);
         filter->setDynamicSortFilter(true);
-//        filter->setSortRole(Qt::EditRole);
         filter->setSortRole(TransactionTableModel::DateRole);
+        filter->setShowInactive(false);
         filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
 
         ui->listTransactions->setModel(filter);
diff --git a/src/qt/res/icons/transaction_conflicted.png b/src/qt/res/icons/transaction_conflicted.png
new file mode 100644 (file)
index 0000000..51fff64
Binary files /dev/null and b/src/qt/res/icons/transaction_conflicted.png differ
index ccd5ad1..7a16c87 100644 (file)
@@ -21,7 +21,9 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
     else
     {
         int nDepth = wtx.GetDepthInMainChain();
-        if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
+        if (nDepth < 0)
+            return tr("conflicted");
+        else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
             return tr("%1/offline").arg(nDepth);
         else if (nDepth < 6)
             return tr("%1/unconfirmed").arg(nDepth);
index 16fb4da..cf6a7b5 100644 (file)
@@ -1,5 +1,6 @@
 #include "transactionfilterproxy.h"
 #include "transactiontablemodel.h"
+#include "transactionrecord.h"
 
 #include <QDateTime>
 
@@ -17,7 +18,8 @@ TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
     addrPrefix(),
     typeFilter(ALL_TYPES),
     minAmount(0),
-    limitRows(-1)
+    limitRows(-1),
+    showInactive(true)
 {
 }
 
@@ -30,6 +32,10 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
     QString address = index.data(TransactionTableModel::AddressRole).toString();
     QString label = index.data(TransactionTableModel::LabelRole).toString();
     qint64 amount = llabs(index.data(TransactionTableModel::AmountRole).toLongLong());
+    int status = index.data(TransactionTableModel::StatusRole).toInt();
+
+    if(!showInactive && status == TransactionStatus::Conflicted)
+        return false;
 
     if(!(TYPE(type) & typeFilter))
         return false;
@@ -73,6 +79,12 @@ void TransactionFilterProxy::setLimit(int limit)
     this->limitRows = limit;
 }
 
+void TransactionFilterProxy::setShowInactive(bool showInactive)
+{
+    this->showInactive = showInactive;
+    invalidateFilter();
+}
+
 int TransactionFilterProxy::rowCount(const QModelIndex &parent) const
 {
     if(limitRows != -1)
index 30b9858..2c86f62 100644 (file)
@@ -31,6 +31,9 @@ public:
     /** Set maximum number of rows returned, -1 if unlimited. */
     void setLimit(int limit);
 
+    /** Set whether to show conflicted transactions. */
+    void setShowInactive(bool showInactive);
+
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
 protected:
     bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const;
@@ -42,6 +45,7 @@ private:
     quint32 typeFilter;
     qint64 minAmount;
     int limitRows;
+    bool showInactive;
 
 signals:
 
index 8962d18..8147d97 100644 (file)
@@ -175,29 +175,27 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
 
     if (!wtx.IsFinal())
     {
-        if (wtx.nLockTime < LOCKTIME_THRESHOLD)
-        {
+        if (wtx.nLockTime < LOCKTIME_THRESHOLD) {
             status.status = TransactionStatus::OpenUntilBlock;
             status.open_for = nBestHeight - wtx.nLockTime;
         }
-        else
-        {
+        else {
             status.status = TransactionStatus::OpenUntilDate;
             status.open_for = wtx.nLockTime;
         }
     }
     else
     {
-        if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
-        {
+        if (status.depth < 0) {
+            status.status = TransactionStatus::Conflicted;
+        }
+        else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) {
             status.status = TransactionStatus::Offline;
         }
-        else if (status.depth < NumConfirmations)
-        {
+        else if (status.depth < NumConfirmations) {
             status.status = TransactionStatus::Unconfirmed;
         }
-        else
-        {
+        else {
             status.status = TransactionStatus::HaveConfirmations;
         }
     }
index db06374..8d24f37 100644 (file)
@@ -31,7 +31,8 @@ public:
         OpenUntilBlock,
         Offline,
         Unconfirmed,
-        HaveConfirmations
+        HaveConfirmations,
+        Conflicted
     };
 
     bool confirmed;
index f0ec309..e53794d 100644 (file)
@@ -294,6 +294,9 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
     case TransactionStatus::HaveConfirmations:
         status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
         break;
+    case TransactionStatus::Conflicted:
+        status = tr("Conflicted");
+        break;
     }
 
     if(wtx->type == TransactionRecord::Generated)
@@ -477,6 +480,8 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
             };
         case TransactionStatus::HaveConfirmations:
             return QIcon(":/icons/transaction_confirmed");
+        case TransactionStatus::Conflicted:
+            return QIcon(":/icons/transaction_conflicted");
         }
     }
     return QColor(0,0,0);
@@ -577,6 +582,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
         return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
     case FormattedAmountRole:
         return formatTxAmount(rec, false);
+    case StatusRole:
+        return rec->status.status;
     }
     return QVariant();
 }
index fd321ce..475fdd0 100644 (file)
@@ -47,7 +47,9 @@ public:
         /** Is transaction confirmed? */
         ConfirmedRole,
         /** Formatted amount, without brackets when unconfirmed */
-        FormattedAmountRole
+        FormattedAmountRole,
+        /** Transaction status (TransactionRecord::Status) */
+        StatusRole
     };
 
     int rowCount(const QModelIndex &parent) const;
index df006a3..4b31b98 100644 (file)
@@ -400,7 +400,9 @@ void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vect
     BOOST_FOREACH(const COutPoint& outpoint, vOutpoints)
     {
         if (!wallet->mapWallet.count(outpoint.hash)) continue;
-        COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain());
+        int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
+        if (nDepth < 0) continue;
+        COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth);
         vOutputs.push_back(out);
     }
 }
@@ -416,7 +418,9 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
     BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins)
     {
         if (!wallet->mapWallet.count(outpoint.hash)) continue;
-        COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain());
+        int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
+        if (nDepth < 0) continue;
+        COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth);
         vCoins.push_back(out);
     }
 
index 72c010b..828d11d 100644 (file)
@@ -38,7 +38,7 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
     entry.push_back(Pair("confirmations", confirms));
     if (wtx.IsCoinBase() || wtx.IsCoinStake())
         entry.push_back(Pair("generated", true));
-    if (confirms)
+    if (confirms > 0)
     {
         entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
         entry.push_back(Pair("blockindex", wtx.nIndex));
@@ -1025,7 +1025,13 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
             Object entry;
             entry.push_back(Pair("account", strSentAccount));
             entry.push_back(Pair("address", CBitcoinAddress(s.first).ToString()));
-            entry.push_back(Pair("category", "send"));
+
+            if (wtx.GetDepthInMainChain() < 0) {
+                entry.push_back(Pair("category", "conflicted"));
+            } else {
+                entry.push_back(Pair("category", "send"));
+            }
+
             entry.push_back(Pair("amount", ValueFromAmount(-s.second)));
             entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
             if (fLong)
@@ -1056,8 +1062,13 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
                     else
                         entry.push_back(Pair("category", "generate"));
                 }
-                else
-                    entry.push_back(Pair("category", "receive"));
+                else {
+                    if (wtx.GetDepthInMainChain() < 0) {
+                        entry.push_back(Pair("category", "conflicted"));
+                    } else {
+                        entry.push_back(Pair("category", "receive"));
+                    }
+                }
                 entry.push_back(Pair("amount", ValueFromAmount(r.second)));
                 if (fLong)
                     WalletTxToJSON(wtx, entry);
@@ -1288,6 +1299,11 @@ Value gettransaction(const Array& params, bool fHelp)
         Array details;
         ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details);
         entry.push_back(Pair("details", details));
+
+        CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+        ssTx << wtx;
+        string strHex = HexStr(ssTx.begin(), ssTx.end());
+        entry.push_back(Pair("hex", strHex));
     }
     else
     {
index 6a519db..01d8be7 100644 (file)
@@ -24,13 +24,13 @@ extern const std::string CLIENT_DATE;
 //
 // database format versioning
 //
-static const int DATABASE_VERSION = 70505;
+static const int DATABASE_VERSION = 70508;
 
 //
 // network protocol versioning
 //
 
-static const int PROTOCOL_VERSION = 60011;
+static const int PROTOCOL_VERSION = 60013;
 
 // earlier versions not supported as of Feb 2012, and are disconnected
 static const int MIN_PROTO_VERSION = 209;
index 91b8aee..201d8a0 100644 (file)
@@ -1022,23 +1022,32 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
         {
             const CWalletTx* pcoin = &(*it).second;
 
-            if (!pcoin->IsFinal())
+            if (!pcoin->IsFinal()) {
                 continue;
+            }
 
-            if (fOnlyConfirmed && !pcoin->IsTrusted())
+            if (fOnlyConfirmed && !pcoin->IsTrusted()) {
                 continue;
+            }
 
-            if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+            if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) {
                 continue;
+            }
 
-            if(pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0)
+            int nDepth = pcoin->GetDepthInMainChain();
+            if (nDepth < 0) {
                 continue;
+            }
 
-            for (unsigned int i = 0; i < pcoin->vout.size(); i++)
+            if(pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0) {
+                continue;
+            }
+
+            for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
                 if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue >= nMinimumInputValue &&
                 (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
-                    vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain()));
-
+                    vCoins.push_back(COutput(pcoin, i, nDepth));
+            }
         }
     }
 }
@@ -1046,7 +1055,6 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
 void CWallet::AvailableCoinsMinConf(vector<COutput>& vCoins, int nConf) const
 {
     vCoins.clear();
-
     {
         LOCK(cs_wallet);
         for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
index 2f5082a..91a5811 100644 (file)
@@ -647,8 +647,11 @@ public:
         // Quick answer in most cases
         if (!IsFinal())
             return false;
-        if (GetDepthInMainChain() >= 1)
+        int nDepth = GetDepthInMainChain();
+        if (nDepth >= 1)
             return true;
+        if (nDepth < 0)
+            return false;
         if (fConfChange || !IsFromMe()) // using wtx's cached debit
             return false;
 
@@ -664,8 +667,11 @@ public:
 
             if (!ptx->IsFinal())
                 return false;
-            if (ptx->GetDepthInMainChain() >= 1)
+            int nPDepth = ptx->GetDepthInMainChain();
+            if (nPDepth >= 1)
                 continue;
+            if (nPDepth < 0)
+                return false;
             if (!pwallet->IsFromMe(*ptx))
                 return false;