UI to alert of respend attempt affecting wallet.
Respend transactions that conflict with transactions already in the wallet are added to it. They are not displayed unless they also involve the wallet, or get into a block. If they do not involve the wallet, they continue not to affect balance. Transactions that involve the wallet, and have conflicting non-equivalent transactions, are highlighted in red. When the conflict first occurs, a modal dialog is thrown. CWallet::SyncMetaData is changed to sync only to equivalent transactions. When a conflict is added to the wallet, counter nConflictsReceived is incremented. This acts like a change in active block height for the purpose of triggering UI updates.
This commit is contained in:
@@ -23,6 +23,10 @@ static const int STATUSBAR_ICONSIZE = 16;
|
||||
#define COLOR_NEGATIVE QColor(255, 0, 0)
|
||||
/* Transaction list -- bare address (without label) */
|
||||
#define COLOR_BAREADDRESS QColor(140, 140, 140)
|
||||
/* Transaction list -- has conflicting transactions */
|
||||
#define COLOR_HASCONFLICTING Qt::white;
|
||||
/* Transaction list -- has conflicting transactions - background */
|
||||
#define COLOR_HASCONFLICTING_BG QColor(192, 0, 0)
|
||||
|
||||
/* Tooltips longer than this (in characters) are converted into rich text,
|
||||
so that they can be word-wrapped.
|
||||
|
||||
@@ -24,7 +24,7 @@ TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
|
||||
typeFilter(ALL_TYPES),
|
||||
minAmount(0),
|
||||
limitRows(-1),
|
||||
showInactive(true)
|
||||
showInactive(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
|
||||
qint64 amount = llabs(index.data(TransactionTableModel::AmountRole).toLongLong());
|
||||
int status = index.data(TransactionTableModel::StatusRole).toInt();
|
||||
|
||||
if(!showInactive && status == TransactionStatus::Conflicted)
|
||||
if(!showInactive && status == TransactionStatus::Conflicted && type == TransactionRecord::Other)
|
||||
return false;
|
||||
if(!(TYPE(type) & typeFilter))
|
||||
return false;
|
||||
|
||||
@@ -170,6 +170,8 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
|
||||
status.depth = wtx.GetDepthInMainChain();
|
||||
status.cur_num_blocks = chainActive.Height();
|
||||
|
||||
status.hasConflicting = false;
|
||||
|
||||
if (!IsFinalTx(wtx, chainActive.Height() + 1))
|
||||
{
|
||||
if (wtx.nLockTime < LOCKTIME_THRESHOLD)
|
||||
@@ -213,6 +215,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
|
||||
if (status.depth < 0)
|
||||
{
|
||||
status.status = TransactionStatus::Conflicted;
|
||||
status.hasConflicting = !(wtx.GetConflicts(false).empty());
|
||||
}
|
||||
else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
|
||||
{
|
||||
@@ -221,6 +224,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
|
||||
else if (status.depth == 0)
|
||||
{
|
||||
status.status = TransactionStatus::Unconfirmed;
|
||||
status.hasConflicting = !(wtx.GetConflicts(false).empty());
|
||||
}
|
||||
else if (status.depth < RecommendedNumConfirmations)
|
||||
{
|
||||
@@ -231,13 +235,13 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
|
||||
status.status = TransactionStatus::Confirmed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TransactionRecord::statusUpdateNeeded()
|
||||
bool TransactionRecord::statusUpdateNeeded(int64_t nConflictsReceived)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
return status.cur_num_blocks != chainActive.Height();
|
||||
return (status.cur_num_blocks != chainActive.Height() ||
|
||||
status.cur_num_conflicts != nConflictsReceived);
|
||||
}
|
||||
|
||||
QString TransactionRecord::getTxID() const
|
||||
|
||||
@@ -20,7 +20,8 @@ class TransactionStatus
|
||||
public:
|
||||
TransactionStatus():
|
||||
countsForBalance(false), sortKey(""),
|
||||
matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1)
|
||||
matures_in(0), status(Offline), hasConflicting(false), depth(0), open_for(0), cur_num_blocks(-1),
|
||||
cur_num_conflicts(-1)
|
||||
{ }
|
||||
|
||||
enum Status {
|
||||
@@ -51,6 +52,10 @@ public:
|
||||
/** @name Reported status
|
||||
@{*/
|
||||
Status status;
|
||||
|
||||
// Has conflicting transactions spending same prevout
|
||||
bool hasConflicting;
|
||||
|
||||
qint64 depth;
|
||||
qint64 open_for; /**< Timestamp if status==OpenUntilDate, otherwise number
|
||||
of additional blocks that need to be mined before
|
||||
@@ -59,6 +64,10 @@ public:
|
||||
|
||||
/** Current number of blocks (to know whether cached status is still valid) */
|
||||
int cur_num_blocks;
|
||||
|
||||
/** Number of conflicts received into wallet as of last status update */
|
||||
int64_t cur_num_conflicts;
|
||||
|
||||
};
|
||||
|
||||
/** UI model for a transaction. A core transaction can be represented by multiple UI transactions if it has
|
||||
@@ -133,7 +142,7 @@ public:
|
||||
|
||||
/** Return whether a status update is needed.
|
||||
*/
|
||||
bool statusUpdateNeeded();
|
||||
bool statusUpdateNeeded(int64_t nConflictsReceived);
|
||||
};
|
||||
|
||||
#endif // TRANSACTIONRECORD_H
|
||||
|
||||
@@ -168,8 +168,7 @@ public:
|
||||
parent->endRemoveRows();
|
||||
break;
|
||||
case CT_UPDATED:
|
||||
// Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
|
||||
// visible transactions.
|
||||
emit parent->dataChanged(parent->index(lowerIndex, parent->Status), parent->index(upperIndex-1, parent->Amount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -190,20 +189,21 @@ public:
|
||||
// stuck if the core is holding the locks for a longer time - for
|
||||
// example, during a wallet rescan.
|
||||
//
|
||||
// If a status update is needed (blocks came in since last check),
|
||||
// update the status of this transaction from the wallet. Otherwise,
|
||||
// If a status update is needed (blocks or conflicts came in since last check),
|
||||
// update the status of this transaction from the wallet. Otherwise,
|
||||
// simply re-use the cached status.
|
||||
TRY_LOCK(cs_main, lockMain);
|
||||
if(lockMain)
|
||||
{
|
||||
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
||||
if(lockWallet && rec->statusUpdateNeeded())
|
||||
if(lockWallet && rec->statusUpdateNeeded(wallet->nConflictsReceived))
|
||||
{
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
|
||||
|
||||
if(mi != wallet->mapWallet.end())
|
||||
{
|
||||
rec->updateStatus(mi->second);
|
||||
rec->status.cur_num_conflicts = wallet->nConflictsReceived;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +363,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
|
||||
return tr("Payment to yourself");
|
||||
case TransactionRecord::Generated:
|
||||
return tr("Mined");
|
||||
case TransactionRecord::Other:
|
||||
return tr("Other");
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
@@ -535,7 +537,13 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
|
||||
return formatTooltip(rec);
|
||||
case Qt::TextAlignmentRole:
|
||||
return column_alignments[index.column()];
|
||||
case Qt::BackgroundColorRole:
|
||||
if (rec->status.hasConflicting)
|
||||
return COLOR_HASCONFLICTING_BG;
|
||||
break;
|
||||
case Qt::ForegroundRole:
|
||||
if (rec->status.hasConflicting)
|
||||
return COLOR_HASCONFLICTING;
|
||||
// Non-confirmed (but not immature) as transactions are grey
|
||||
if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
|
||||
{
|
||||
|
||||
@@ -138,6 +138,14 @@ void WalletModel::checkBalanceChanged()
|
||||
|
||||
void WalletModel::updateTransaction(const QString &hash, int status)
|
||||
{
|
||||
if (status == CT_GOT_CONFLICT)
|
||||
{
|
||||
emit message(tr("Conflict Received"),
|
||||
tr("WARNING: Transaction may never be confirmed. Its input was seen being spent by another transaction on the network. Wait for confirmation!"),
|
||||
CClientUIInterface::MSG_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if(transactionTableModel)
|
||||
transactionTableModel->updateTransaction(hash, status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user