Skip to content

Commit f4b603c

Browse files
committed
Merge #17993: gui: Balance/TxStatus polling update based on last block hash.
a06e845 BlockTip struct created and connected to notifyHeaderTip and notifyBlockTip signals. (furszy) 2f86720 Added best block hash to the NotifyHeaderTip and NotifyBlockTip signals. (furszy) Pull request description: Rationale: The height based polling in the GUI is an issue on chain reorgs. Any new tip signal with the same height as the one that it's cached in the model was not triggering the GUI update (interpreting it as the same same block when it could receive a different one). Ending up with bad information presented in the GUI. This PR essentially changes the last cached height to be a last cached block hash. --- Old historical information of this PR. As the tip height is cached and updated via signaling in clientModel, there is no need to continue locking `cs_main` on every balance poll (`m_node.getNumBlocks()` method call). Extra topic: Would suggest to change the `cachedNumBlocks` field inside `walletModel` to a more understandable name, maybe `nLastBalanceUpdateHeight`. And finally, this will have the equal height reorg issue mentioned [here](bitcoin/bitcoin#17905 (comment)), whatever is presented to fix it, this should use the same flow too. **[Edit - 24/01/2020]** Have added #[17905](bitcoin/bitcoin#17905 (comment)) comment fix here too. ACKs for top commit: jonasschnelli: utACK a06e845 - it would be great to have QT unit tests (in this case for a reorg) that either automatically inspect the window content based on accessibility and tests for expected values or at least allow for quick manual re-testing (screenshots, automatically create UI situations). hebasto: re-ACK a06e845, suggested style changes implemented since the [previous](bitcoin/bitcoin#17993 (review)) review. ryanofsky: Code review ACK a06e845. A lot of changes since the last review: rebase after sync_state introduction #18152 and tryGetBalances revert #18587, reverting getLastBlockTime change, fixing spacing and initializations and renaming some variables Tree-SHA512: 835e587a8296df9899cccd7b3e598a5970942b640e432e6a32de0b4eaea5b40f9271258f089ec033595311707b74a0f7187ecf8ed397c713e1153e2714072975
2 parents beb3844 + a06e845 commit f4b603c

File tree

11 files changed

+71
-40
lines changed

11 files changed

+71
-40
lines changed

src/interfaces/node.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ class NodeImpl : public Node
187187
LOCK(::cs_main);
188188
return ::ChainActive().Height();
189189
}
190+
uint256 getBestBlockHash() override
191+
{
192+
const CBlockIndex* tip = WITH_LOCK(::cs_main, return ::ChainActive().Tip());
193+
return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash();
194+
}
190195
int64_t getLastBlockTime() override
191196
{
192197
LOCK(::cs_main);
@@ -310,15 +315,15 @@ class NodeImpl : public Node
310315
std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override
311316
{
312317
return MakeHandler(::uiInterface.NotifyBlockTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
313-
fn(sync_state, block->nHeight, block->GetBlockTime(),
318+
fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
314319
GuessVerificationProgress(Params().TxData(), block));
315320
}));
316321
}
317322
std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override
318323
{
319324
return MakeHandler(
320325
::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) {
321-
fn(sync_state, block->nHeight, block->GetBlockTime(),
326+
fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()},
322327
/* verification progress is unused when a header was received */ 0);
323328
}));
324329
}

src/interfaces/node.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct bilingual_str;
3636
namespace interfaces {
3737
class Handler;
3838
class Wallet;
39+
struct BlockTip;
3940

4041
//! Top-level interface for a bitcoin node (bitcoind process).
4142
class Node
@@ -149,6 +150,9 @@ class Node
149150
//! Get num blocks.
150151
virtual int getNumBlocks() = 0;
151152

153+
//! Get best block hash.
154+
virtual uint256 getBestBlockHash() = 0;
155+
152156
//! Get last block time.
153157
virtual int64_t getLastBlockTime() = 0;
154158

@@ -250,12 +254,12 @@ class Node
250254

251255
//! Register handler for block tip messages.
252256
using NotifyBlockTipFn =
253-
std::function<void(SynchronizationState, int height, int64_t block_time, double verification_progress)>;
257+
std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>;
254258
virtual std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) = 0;
255259

256260
//! Register handler for header tip messages.
257261
using NotifyHeaderTipFn =
258-
std::function<void(SynchronizationState, int height, int64_t block_time, double verification_progress)>;
262+
std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>;
259263
virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0;
260264

261265
//! Return pointer to internal chain interface, useful for testing.
@@ -265,6 +269,13 @@ class Node
265269
//! Return implementation of Node interface.
266270
std::unique_ptr<Node> MakeNode();
267271

272+
//! Block tip (could be a header or not, depends on the subscribed signal).
273+
struct BlockTip {
274+
int block_height;
275+
int64_t block_time;
276+
uint256 block_hash;
277+
};
278+
268279
} // namespace interfaces
269280

270281
#endif // BITCOIN_INTERFACES_NODE_H

src/interfaces/wallet.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,13 @@ class WalletImpl : public Wallet
351351
}
352352
return result;
353353
}
354-
bool tryGetBalances(WalletBalances& balances, int& num_blocks) override
354+
bool tryGetBalances(WalletBalances& balances, uint256& block_hash) override
355355
{
356356
TRY_LOCK(m_wallet->cs_wallet, locked_wallet);
357357
if (!locked_wallet) {
358358
return false;
359359
}
360-
num_blocks = m_wallet->GetLastBlockHeight();
360+
block_hash = m_wallet->GetLastBlockHash();
361361
balances = getBalances();
362362
return true;
363363
}

src/interfaces/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class Wallet
203203
virtual WalletBalances getBalances() = 0;
204204

205205
//! Get balances if possible without blocking.
206-
virtual bool tryGetBalances(WalletBalances& balances, int& num_blocks) = 0;
206+
virtual bool tryGetBalances(WalletBalances& balances, uint256& block_hash) = 0;
207207

208208
//! Get balance.
209209
virtual CAmount getBalance() = 0;

src/qt/clientmodel.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ int ClientModel::getNumBlocks() const
114114
return m_cached_num_blocks;
115115
}
116116

117+
uint256 ClientModel::getBestBlockHash()
118+
{
119+
LOCK(m_cached_tip_mutex);
120+
if (m_cached_tip_blocks.IsNull()) {
121+
m_cached_tip_blocks = m_node.getBestBlockHash();
122+
}
123+
return m_cached_tip_blocks;
124+
}
125+
117126
void ClientModel::updateNumConnections(int numConnections)
118127
{
119128
Q_EMIT numConnectionsChanged(numConnections);
@@ -235,14 +244,15 @@ static void BannedListChanged(ClientModel *clientmodel)
235244
assert(invoked);
236245
}
237246

238-
static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_state, int height, int64_t blockTime, double verificationProgress, bool fHeader)
247+
static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_state, interfaces::BlockTip tip, double verificationProgress, bool fHeader)
239248
{
240249
if (fHeader) {
241250
// cache best headers time and height to reduce future cs_main locks
242-
clientmodel->cachedBestHeaderHeight = height;
243-
clientmodel->cachedBestHeaderTime = blockTime;
251+
clientmodel->cachedBestHeaderHeight = tip.block_height;
252+
clientmodel->cachedBestHeaderTime = tip.block_time;
244253
} else {
245-
clientmodel->m_cached_num_blocks = height;
254+
clientmodel->m_cached_num_blocks = tip.block_height;
255+
WITH_LOCK(clientmodel->m_cached_tip_mutex, clientmodel->m_cached_tip_blocks = tip.block_hash;);
246256
}
247257

248258
// Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex.
@@ -254,8 +264,8 @@ static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_
254264
}
255265

256266
bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection,
257-
Q_ARG(int, height),
258-
Q_ARG(QDateTime, QDateTime::fromTime_t(blockTime)),
267+
Q_ARG(int, tip.block_height),
268+
Q_ARG(QDateTime, QDateTime::fromTime_t(tip.block_time)),
259269
Q_ARG(double, verificationProgress),
260270
Q_ARG(bool, fHeader),
261271
Q_ARG(SynchronizationState, sync_state));
@@ -271,8 +281,8 @@ void ClientModel::subscribeToCoreSignals()
271281
m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1));
272282
m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this));
273283
m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this));
274-
m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, false));
275-
m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, true));
284+
m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, false));
285+
m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, true));
276286
}
277287

278288
void ClientModel::unsubscribeFromCoreSignals()

src/qt/clientmodel.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#include <atomic>
1212
#include <memory>
13+
#include <sync.h>
14+
#include <uint256.h>
1315

1416
class BanTableModel;
1517
class CBlockIndex;
@@ -57,6 +59,7 @@ class ClientModel : public QObject
5759
//! Return number of connections, default is in- and outbound (total)
5860
int getNumConnections(unsigned int flags = CONNECTIONS_ALL) const;
5961
int getNumBlocks() const;
62+
uint256 getBestBlockHash();
6063
int getHeaderTipHeight() const;
6164
int64_t getHeaderTipTime() const;
6265

@@ -74,11 +77,14 @@ class ClientModel : public QObject
7477

7578
bool getProxyInfo(std::string& ip_port) const;
7679

77-
// caches for the best header, number of blocks
80+
// caches for the best header: hash, number of blocks and block time
7881
mutable std::atomic<int> cachedBestHeaderHeight;
7982
mutable std::atomic<int64_t> cachedBestHeaderTime;
8083
mutable std::atomic<int> m_cached_num_blocks{-1};
8184

85+
Mutex m_cached_tip_mutex;
86+
uint256 m_cached_tip_blocks GUARDED_BY(m_cached_tip_mutex){};
87+
8288
private:
8389
interfaces::Node& m_node;
8490
std::unique_ptr<interfaces::Handler> m_handler_show_progress;

src/qt/transactionrecord.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
162162
return parts;
163163
}
164164

165-
void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t block_time)
165+
void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, const uint256& block_hash, int numBlocks, int64_t block_time)
166166
{
167167
// Determine transaction status
168168

@@ -174,7 +174,7 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int
174174
idx);
175175
status.countsForBalance = wtx.is_trusted && !(wtx.blocks_to_maturity > 0);
176176
status.depth = wtx.depth_in_main_chain;
177-
status.cur_num_blocks = numBlocks;
177+
status.m_cur_block_hash = block_hash;
178178

179179
const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP);
180180
if (up_to_date && !wtx.is_final) {
@@ -233,9 +233,9 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int
233233
status.needsUpdate = false;
234234
}
235235

236-
bool TransactionRecord::statusUpdateNeeded(int numBlocks) const
236+
bool TransactionRecord::statusUpdateNeeded(const uint256& block_hash) const
237237
{
238-
return status.cur_num_blocks != numBlocks || status.needsUpdate;
238+
return status.m_cur_block_hash != block_hash || status.needsUpdate;
239239
}
240240

241241
QString TransactionRecord::getTxHash() const

src/qt/transactionrecord.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ struct WalletTxStatus;
2323
class TransactionStatus
2424
{
2525
public:
26-
TransactionStatus():
27-
countsForBalance(false), sortKey(""),
28-
matures_in(0), status(Unconfirmed), depth(0), open_for(0), cur_num_blocks(-1)
26+
TransactionStatus() : countsForBalance(false), sortKey(""),
27+
matures_in(0), status(Unconfirmed), depth(0), open_for(0)
2928
{ }
3029

3130
enum Status {
@@ -61,8 +60,8 @@ class TransactionStatus
6160
finalization */
6261
/**@}*/
6362

64-
/** Current number of blocks (to know whether cached status is still valid) */
65-
int cur_num_blocks;
63+
/** Current block hash (to know whether cached status is still valid) */
64+
uint256 m_cur_block_hash{};
6665

6766
bool needsUpdate;
6867
};
@@ -138,11 +137,11 @@ class TransactionRecord
138137

139138
/** Update status from core wallet tx.
140139
*/
141-
void updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t block_time);
140+
void updateStatus(const interfaces::WalletTxStatus& wtx, const uint256& block_hash, int numBlocks, int64_t block_time);
142141

143142
/** Return whether a status update is needed.
144143
*/
145-
bool statusUpdateNeeded(int numBlocks) const;
144+
bool statusUpdateNeeded(const uint256& block_hash) const;
146145
};
147146

148147
#endif // BITCOIN_QT_TRANSACTIONRECORD_H

src/qt/transactiontablemodel.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class TransactionTablePriv
176176
return cachedWallet.size();
177177
}
178178

179-
TransactionRecord *index(interfaces::Wallet& wallet, const int cur_num_blocks, const int idx)
179+
TransactionRecord* index(interfaces::Wallet& wallet, const uint256& cur_block_hash, const int idx)
180180
{
181181
if(idx >= 0 && idx < cachedWallet.size())
182182
{
@@ -192,8 +192,8 @@ class TransactionTablePriv
192192
interfaces::WalletTxStatus wtx;
193193
int numBlocks;
194194
int64_t block_time;
195-
if (rec->statusUpdateNeeded(cur_num_blocks) && wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time)) {
196-
rec->updateStatus(wtx, numBlocks, block_time);
195+
if (rec->statusUpdateNeeded(cur_block_hash) && wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time)) {
196+
rec->updateStatus(wtx, cur_block_hash, numBlocks, block_time);
197197
}
198198
return rec;
199199
}
@@ -664,7 +664,7 @@ QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientat
664664
QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
665665
{
666666
Q_UNUSED(parent);
667-
TransactionRecord *data = priv->index(walletModel->wallet(), walletModel->getNumBlocks(), row);
667+
TransactionRecord* data = priv->index(walletModel->wallet(), walletModel->clientModel().getBestBlockHash(), row);
668668
if(data)
669669
{
670670
return createIndex(row, column, data);

src/qt/walletmodel.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel
4646
transactionTableModel(nullptr),
4747
recentRequestsTableModel(nullptr),
4848
cachedEncryptionStatus(Unencrypted),
49-
cachedNumBlocks(0),
5049
timer(new QTimer(this))
5150
{
5251
fHaveWatchOnly = m_wallet->haveWatchOnly();
@@ -88,24 +87,23 @@ void WalletModel::pollBalanceChanged()
8887
{
8988
// Avoid recomputing wallet balances unless a TransactionChanged or
9089
// BlockTip notification was received.
91-
if (!fForceCheckBalanceChanged && cachedNumBlocks == m_client_model->getNumBlocks()) return;
90+
if (!fForceCheckBalanceChanged && m_cached_last_update_tip == m_client_model->getBestBlockHash()) return;
9291

9392
// Try to get balances and return early if locks can't be acquired. This
9493
// avoids the GUI from getting stuck on periodical polls if the core is
9594
// holding the locks for a longer time - for example, during a wallet
9695
// rescan.
9796
interfaces::WalletBalances new_balances;
98-
int numBlocks = -1;
99-
if (!m_wallet->tryGetBalances(new_balances, numBlocks)) {
97+
uint256 block_hash;
98+
if (!m_wallet->tryGetBalances(new_balances, block_hash)) {
10099
return;
101100
}
102101

103-
if(fForceCheckBalanceChanged || numBlocks != cachedNumBlocks)
104-
{
102+
if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
105103
fForceCheckBalanceChanged = false;
106104

107105
// Balance and number of transactions might have changed
108-
cachedNumBlocks = numBlocks;
106+
m_cached_last_update_tip = block_hash;
109107

110108
checkBalanceChanged(new_balances);
111109
if(transactionTableModel)

0 commit comments

Comments
 (0)