Skip to content

Commit da2f503

Browse files
codablockUdjinM6
authored andcommitted
Fix largest part of GUI lockups with large wallets (#3155)
* [Qt] make sure transaction table entry gets updated after bump * Remove unnecessary tracking of IS lock count * Track lockedByChainLocks in TransactionRecord * Only update record when the TX was not ChainLocked before * Emit dataChanged for CT_UPDATED transactions * Use plain seconds since epoch comparison in TransactionFilterProxy::filterAcceptsRow The QDateTime::operator< calls inside TransactionFilterProxy::filterAcceptsRow turned out to be the slowest part in the UI when many TXs are inside the wallet. DateRoleInt allows us to request the plain seconds since epoch which we then use to compare against dateFrom/dateTo, which are also both stored as seconds since epoch now. * Don't invoke updateConfirmations directly and let pollBalanceChanged handle it * Implement AddressTableModel::labelForDestination This one avoids converting from string to CBitcoinAddress and calling .Get() on the result. * Also store CBitcoinAddress object and CTxDestination in TransactionRecord This avoids frequent and slow conversion * Use labelForDestination when possible This avoids unnecessary conversions * Don't set fForceCheckBalanceChanged to true when IS lock is received We already do this through updateTransaction(), which is also called when an IS lock is received for one of our own TXs. * Only update lockedByChainLocks and lockedByInstantSend when a change is possible lockedByChainLocks can never get back to false, so no need to re-check it. Same with lockedByInstantSend, except when a ChainLock overrides it. * Hold and update label in TransactionRecord Instead of looking it up in data() * Review suggestions * Use proper columns in dataChanged call in updateAddressBook
1 parent 3c6b5f9 commit da2f503

11 files changed

+155
-66
lines changed

src/qt/addresstablemodel.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,11 +421,21 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent
421421
/* Look up label for address in address book, if not found return empty string.
422422
*/
423423
QString AddressTableModel::labelForAddress(const QString &address) const
424+
{
425+
CBitcoinAddress address_parsed(address.toStdString());
426+
return labelForAddress(address_parsed);
427+
}
428+
429+
QString AddressTableModel::labelForAddress(const CBitcoinAddress &address) const
430+
{
431+
return labelForDestination(address.Get());
432+
}
433+
434+
QString AddressTableModel::labelForDestination(const CTxDestination &dest) const
424435
{
425436
{
426437
LOCK(wallet->cs_wallet);
427-
CBitcoinAddress address_parsed(address.toStdString());
428-
std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
438+
std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest);
429439
if (mi != wallet->mapAddressBook.end())
430440
{
431441
return QString::fromStdString(mi->second.name);

src/qt/addresstablemodel.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BITCOIN_QT_ADDRESSTABLEMODEL_H
66
#define BITCOIN_QT_ADDRESSTABLEMODEL_H
77

8+
#include "base58.h"
9+
810
#include <QAbstractTableModel>
911
#include <QStringList>
1012

@@ -66,6 +68,8 @@ class AddressTableModel : public QAbstractTableModel
6668
/* Look up label for address in address book, if not found return empty string.
6769
*/
6870
QString labelForAddress(const QString &address) const;
71+
QString labelForAddress(const CBitcoinAddress &address) const;
72+
QString labelForDestination(const CTxDestination &dest) const;
6973

7074
/* Look up row index of an address in the model.
7175
Return -1 if not found.

src/qt/transactiondesc.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
107107
if (nNet > 0)
108108
{
109109
// Credit
110-
if (CBitcoinAddress(rec->address).IsValid())
110+
if (rec->address.IsValid())
111111
{
112-
CTxDestination address = CBitcoinAddress(rec->address).Get();
112+
CTxDestination address = rec->txDest;
113113
if (wallet->mapAddressBook.count(address))
114114
{
115115
strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
116116
strHTML += "<b>" + tr("To") + ":</b> ";
117-
strHTML += GUIUtil::HtmlEscape(rec->address);
117+
strHTML += GUIUtil::HtmlEscape(rec->strAddress);
118118
QString addressOwned = (::IsMine(*wallet, address) == ISMINE_SPENDABLE) ? tr("own address") : tr("watch-only");
119119
if (!wallet->mapAddressBook[address].name.empty())
120120
strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")";

src/qt/transactionfilterproxy.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const QDateTime TransactionFilterProxy::MAX_DATE = QDateTime::fromTime_t(0xFFFFF
1818

1919
TransactionFilterProxy::TransactionFilterProxy(QObject *parent) :
2020
QSortFilterProxyModel(parent),
21-
dateFrom(MIN_DATE),
22-
dateTo(MAX_DATE),
21+
dateFrom(MIN_DATE.toTime_t()),
22+
dateTo(MAX_DATE.toTime_t()),
2323
addrPrefix(),
2424
typeFilter(COMMON_TYPES),
2525
watchOnlyFilter(WatchOnlyFilter_All),
@@ -35,7 +35,7 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
3535
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
3636

3737
int type = index.data(TransactionTableModel::TypeRole).toInt();
38-
QDateTime datetime = index.data(TransactionTableModel::DateRole).toDateTime();
38+
qint64 datetime = index.data(TransactionTableModel::DateRoleInt).toLongLong();
3939
bool involvesWatchAddress = index.data(TransactionTableModel::WatchonlyRole).toBool();
4040
bool lockedByInstantSend = index.data(TransactionTableModel::InstantSendRole).toBool();
4141
QString address = index.data(TransactionTableModel::AddressRole).toString();
@@ -67,8 +67,8 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
6767

6868
void TransactionFilterProxy::setDateRange(const QDateTime &from, const QDateTime &to)
6969
{
70-
this->dateFrom = from;
71-
this->dateTo = to;
70+
this->dateFrom = from.toTime_t();
71+
this->dateTo = to.toTime_t();
7272
invalidateFilter();
7373
}
7474

src/qt/transactionfilterproxy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ class TransactionFilterProxy : public QSortFilterProxyModel
6565
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const;
6666

6767
private:
68-
QDateTime dateFrom;
69-
QDateTime dateTo;
68+
qint64 dateFrom;
69+
qint64 dateTo;
7070
QString addrPrefix;
7171
quint32 typeFilter;
7272
WatchOnlyFilter watchOnlyFilter;

src/qt/transactionrecord.cpp

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,22 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
5858
{
5959
// Received by Dash Address
6060
sub.type = TransactionRecord::RecvWithAddress;
61-
sub.address = CBitcoinAddress(address).ToString();
61+
sub.strAddress = CBitcoinAddress(address).ToString();
6262
}
6363
else
6464
{
6565
// Received by IP connection (deprecated features), or a multisignature or other non-simple transaction
6666
sub.type = TransactionRecord::RecvFromOther;
67-
sub.address = mapValue["from"];
67+
sub.strAddress = mapValue["from"];
6868
}
6969
if (wtx.IsCoinBase())
7070
{
7171
// Generated
7272
sub.type = TransactionRecord::Generated;
7373
}
7474

75+
sub.address.SetString(sub.strAddress);
76+
sub.txDest = sub.address.Get();
7577
parts.append(sub);
7678
}
7779
}
@@ -119,7 +121,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
119121
TransactionRecord sub(hash, nTime);
120122
// Payment to self by default
121123
sub.type = TransactionRecord::SendToSelf;
122-
sub.address = "";
124+
sub.strAddress = "";
123125

124126
if(mapValue["DS"] == "1")
125127
{
@@ -128,12 +130,12 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
128130
if (ExtractDestination(wtx.tx->vout[0].scriptPubKey, address))
129131
{
130132
// Sent to Dash Address
131-
sub.address = CBitcoinAddress(address).ToString();
133+
sub.strAddress = CBitcoinAddress(address).ToString();
132134
}
133135
else
134136
{
135137
// Sent to IP, or other non-address transaction like OP_EVAL
136-
sub.address = mapValue["to"];
138+
sub.strAddress = mapValue["to"];
137139
}
138140
}
139141
else
@@ -162,6 +164,8 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
162164

163165
sub.debit = -(nDebit - nChange);
164166
sub.credit = nCredit - nChange;
167+
sub.address.SetString(sub.strAddress);
168+
sub.txDest = sub.address.Get();
165169
parts.append(sub);
166170
parts.last().involvesWatchAddress = involvesWatchAddress; // maybe pass to TransactionRecord as constructor argument
167171
}
@@ -205,13 +209,13 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
205209
{
206210
// Sent to Dash Address
207211
sub.type = TransactionRecord::SendToAddress;
208-
sub.address = CBitcoinAddress(address).ToString();
212+
sub.strAddress = CBitcoinAddress(address).ToString();
209213
}
210214
else
211215
{
212216
// Sent to IP, or other non-address transaction like OP_EVAL
213217
sub.type = TransactionRecord::SendToOther;
214-
sub.address = mapValue["to"];
218+
sub.strAddress = mapValue["to"];
215219
}
216220

217221
if(mapValue["DS"] == "1")
@@ -228,6 +232,9 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
228232
}
229233
sub.debit = -nValue;
230234

235+
sub.address.SetString(sub.strAddress);
236+
sub.txDest = sub.address.Get();
237+
231238
parts.append(sub);
232239
}
233240
}
@@ -244,9 +251,10 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
244251
return parts;
245252
}
246253

247-
void TransactionRecord::updateStatus(const CWalletTx &wtx, int numISLocks, int chainLockHeight)
254+
void TransactionRecord::updateStatus(const CWalletTx &wtx, int chainLockHeight)
248255
{
249256
AssertLockHeld(cs_main);
257+
AssertLockHeld(wtx.GetWallet()->cs_wallet);
250258
// Determine transaction status
251259

252260
// Find the block the tx is in
@@ -264,9 +272,20 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int numISLocks, int c
264272
status.countsForBalance = wtx.IsTrusted() && !(wtx.GetBlocksToMaturity() > 0);
265273
status.depth = wtx.GetDepthInMainChain();
266274
status.cur_num_blocks = chainActive.Height();
267-
status.cachedNumISLocks = numISLocks;
268275
status.cachedChainLockHeight = chainLockHeight;
269276

277+
bool oldLockedByChainLocks = status.lockedByChainLocks;
278+
if (!status.lockedByChainLocks) {
279+
status.lockedByChainLocks = wtx.IsChainLocked();
280+
}
281+
282+
auto addrBookIt = wtx.GetWallet()->mapAddressBook.find(this->txDest);
283+
if (addrBookIt == wtx.GetWallet()->mapAddressBook.end()) {
284+
status.label = "";
285+
} else {
286+
status.label = QString::fromStdString(addrBookIt->second.name);
287+
}
288+
270289
if (!CheckFinalTx(wtx))
271290
{
272291
if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD)
@@ -307,7 +326,17 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int numISLocks, int c
307326
}
308327
else
309328
{
310-
status.lockedByInstantSend = wtx.IsLockedByInstantSend();
329+
// The IsLockedByInstantSend call is quite expensive, so we only do it when a state change is actually possible.
330+
if (status.lockedByChainLocks) {
331+
if (oldLockedByChainLocks != status.lockedByChainLocks) {
332+
status.lockedByInstantSend = wtx.IsLockedByInstantSend();
333+
} else {
334+
status.lockedByInstantSend = false;
335+
}
336+
} else if (!status.lockedByInstantSend) {
337+
status.lockedByInstantSend = wtx.IsLockedByInstantSend();
338+
}
339+
311340
if (status.depth < 0)
312341
{
313342
status.status = TransactionStatus::Conflicted;
@@ -322,7 +351,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int numISLocks, int c
322351
if (wtx.isAbandoned())
323352
status.status = TransactionStatus::Abandoned;
324353
}
325-
else if (status.depth < RecommendedNumConfirmations && !wtx.IsChainLocked())
354+
else if (status.depth < RecommendedNumConfirmations && !status.lockedByChainLocks)
326355
{
327356
status.status = TransactionStatus::Confirming;
328357
}
@@ -331,15 +360,14 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int numISLocks, int c
331360
status.status = TransactionStatus::Confirmed;
332361
}
333362
}
334-
363+
status.needsUpdate = false;
335364
}
336365

337-
bool TransactionRecord::statusUpdateNeeded(int numISLocks, int chainLockHeight)
366+
bool TransactionRecord::statusUpdateNeeded(int chainLockHeight)
338367
{
339368
AssertLockHeld(cs_main);
340-
return status.cur_num_blocks != chainActive.Height()
341-
|| status.cachedNumISLocks != numISLocks
342-
|| status.cachedChainLockHeight != chainLockHeight;
369+
return status.cur_num_blocks != chainActive.Height() || status.needsUpdate
370+
|| (!status.lockedByChainLocks && status.cachedChainLockHeight != chainLockHeight);
343371
}
344372

345373
QString TransactionRecord::getTxID() const

src/qt/transactionrecord.h

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "amount.h"
99
#include "uint256.h"
10+
#include "base58.h"
1011

1112
#include <QList>
1213
#include <QString>
@@ -20,9 +21,9 @@ class TransactionStatus
2021
{
2122
public:
2223
TransactionStatus():
23-
countsForBalance(false), lockedByInstantSend(false), sortKey(""),
24+
countsForBalance(false), lockedByInstantSend(false), lockedByChainLocks(false), sortKey(""),
2425
matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1),
25-
cachedNumISLocks(-1), cachedChainLockHeight(-1)
26+
cachedChainLockHeight(-1), needsUpdate(false)
2627
{ }
2728

2829
enum Status {
@@ -45,8 +46,12 @@ class TransactionStatus
4546
bool countsForBalance;
4647
/// Transaction was locked via InstantSend
4748
bool lockedByInstantSend;
49+
/// Transaction was locked via ChainLocks
50+
bool lockedByChainLocks;
4851
/// Sorting key based on status
4952
std::string sortKey;
53+
/// Label
54+
QString label;
5055

5156
/** @name Generated (mined) transactions
5257
@{*/
@@ -65,10 +70,10 @@ class TransactionStatus
6570
/** Current number of blocks (to know whether cached status is still valid) */
6671
int cur_num_blocks;
6772

68-
//** Know when to update transaction for IS-locks **/
69-
int cachedNumISLocks;
7073
//** Know when to update transaction for chainlocks **/
7174
int cachedChainLockHeight;
75+
76+
bool needsUpdate;
7277
};
7378

7479
/** UI model for a transaction. A core transaction can be represented by multiple UI transactions if it has
@@ -98,22 +103,28 @@ class TransactionRecord
98103
static const int RecommendedNumConfirmations = 6;
99104

100105
TransactionRecord():
101-
hash(), time(0), type(Other), address(""), debit(0), credit(0), idx(0)
106+
hash(), time(0), type(Other), strAddress(""), debit(0), credit(0), idx(0)
102107
{
108+
address = CBitcoinAddress(strAddress);
109+
txDest = address.Get();
103110
}
104111

105112
TransactionRecord(uint256 _hash, qint64 _time):
106-
hash(_hash), time(_time), type(Other), address(""), debit(0),
113+
hash(_hash), time(_time), type(Other), strAddress(""), debit(0),
107114
credit(0), idx(0)
108115
{
116+
address = CBitcoinAddress(strAddress);
117+
txDest = address.Get();
109118
}
110119

111120
TransactionRecord(uint256 _hash, qint64 _time,
112-
Type _type, const std::string &_address,
121+
Type _type, const std::string &_strAddress,
113122
const CAmount& _debit, const CAmount& _credit):
114-
hash(_hash), time(_time), type(_type), address(_address), debit(_debit), credit(_credit),
123+
hash(_hash), time(_time), type(_type), strAddress(_strAddress), debit(_debit), credit(_credit),
115124
idx(0)
116125
{
126+
address = CBitcoinAddress(strAddress);
127+
txDest = address.Get();
117128
}
118129

119130
/** Decompose CWallet transaction to model transaction records.
@@ -126,7 +137,10 @@ class TransactionRecord
126137
uint256 hash;
127138
qint64 time;
128139
Type type;
129-
std::string address;
140+
std::string strAddress;
141+
CBitcoinAddress address;
142+
CTxDestination txDest;
143+
130144
CAmount debit;
131145
CAmount credit;
132146
/**@}*/
@@ -148,11 +162,11 @@ class TransactionRecord
148162

149163
/** Update status from core wallet tx.
150164
*/
151-
void updateStatus(const CWalletTx &wtx, int numISLocks, int chainLockHeight);
165+
void updateStatus(const CWalletTx &wtx, int chainLockHeight);
152166

153167
/** Return whether a status update is needed.
154168
*/
155-
bool statusUpdateNeeded(int numISLocks, int chainLockHeight);
169+
bool statusUpdateNeeded(int chainLockHeight);
156170
};
157171

158172
#endif // BITCOIN_QT_TRANSACTIONRECORD_H

0 commit comments

Comments
 (0)