Skip to content
Bitcoin Roots

v29.4-roots.4 · Commit 24: [PATCH 24/37] feat(qt): add guarded private key sweep dialog

src/qt/walletmodel.cpp

Before: src/qt/walletmodel.cpp · After: src/qt/walletmodel.cpp

File metadata
index 0a01c0a45b1923d39a1635ee94fa52234dc49f83..e051aae59971f7336bbc222bb35552ac293c10b0 100644

Download original patch · Release notes

Diff rows 1–99 of 99. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/qt/walletmodel.cpp CHANGED
@@ -22,6 +22,8 @@
22
22
  #include <node/types.h>
23
23
  #include <psbt.h>
24
24
  #include <util/translation.h>
25
+ #include <util/threadnames.h>
26
+ #include <util/rbf.h>
25
27
  #include <wallet/coincontrol.h>
26
28
  #include <wallet/wallet.h> // for CRecipient
27
29
 
@@ -50,14 +52,52 @@ WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel
50
52
  transactionTableModel = new TransactionTableModel(platformStyle, this);
51
53
  recentRequestsTableModel = new RecentRequestsTableModel(this);
52
54
 
55
+ m_sweep_context.moveToThread(&m_sweep_thread);
56
+ m_sweep_thread.start();
57
+ QTimer::singleShot(0, &m_sweep_context, [] { util::ThreadRename("qt-sweep"); });
58
+
53
59
  subscribeToCoreSignals();
54
60
  }
55
61
 
56
62
  WalletModel::~WalletModel()
57
63
  {
64
+ invalidateSweepRequests();
65
+ m_sweep_thread.quit();
66
+ m_sweep_thread.wait();
58
67
  unsubscribeFromCoreSignals();
59
68
  }
60
69
 
70
+ uint64_t WalletModel::invalidateSweepRequests()
71
+ {
72
+ ++m_sweep_request_generation;
73
+ Q_EMIT sweepRequestInvalidated(m_sweep_request_generation);
74
+ return m_sweep_request_generation;
75
+ }
76
+
77
+ uint64_t WalletModel::requestSweep(SecureString private_key, CTxDestination destination, bool broadcast,
78
+ std::optional<uint256> expected_txid)
79
+ {
80
+ const uint64_t generation{++m_sweep_request_generation};
81
+ QMetaObject::invokeMethod(&m_sweep_context, [this, generation, private_key = std::move(private_key), destination = std::move(destination), broadcast, expected_txid]() mutable {
82
+ auto result{m_wallet->sweepPrivateKey(private_key, destination, broadcast, expected_txid)};
83
+ private_key.clear();
84
+
85
+ const bool success{result.has_value()};
86
+ const QString error{success ? QString{} : QString::fromStdString(util::ErrorString(result).original)};
87
+ const qint64 amount{success ? result->amount : 0};
88
+ const qint64 fee{success ? result->fee : 0};
89
+ const quint64 inputs{success ? result->inputs : 0};
90
+ const qint64 vsize{success ? result->vsize : 0};
91
+ const bool rbf{success && result->tx && SignalsOptInRBF(*result->tx)};
92
+ const QString txid{success && result->tx ? QString::fromStdString(result->tx->GetHash().ToString()) : QString{}};
93
+ QMetaObject::invokeMethod(this, [this, generation, broadcast, success, error, amount, fee, inputs, vsize, rbf, txid] {
94
+ if (generation != m_sweep_request_generation) return;
95
+ Q_EMIT sweepFinished(generation, broadcast, success, error, amount, fee, inputs, vsize, rbf, txid);
96
+ }, Qt::QueuedConnection);
97
+ }, Qt::QueuedConnection);
98
+ return generation;
99
+ }
100
+
61
101
  void WalletModel::startPollBalance()
62
102
  {
63
103
  // Update the cached balance right away, so every view can make use of it,
@@ -104,6 +144,12 @@ void WalletModel::pollBalanceChanged()
104
144
  }
105
145
 
106
146
  if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
147
+ if (!m_cached_last_update_tip.IsNull() && block_hash != m_cached_last_update_tip) {
148
+ // A sweep preview commits to the currently observed chain state.
149
+ // Do not leave a key or a broadcastable preview around after a tip
150
+ // change; the dialog must collect fresh input and preview again.
151
+ invalidateSweepRequests();
152
+ }
107
153
  fForceCheckBalanceChanged = false;
108
154
 
109
155
  // Balance and number of transactions might have changed
@@ -132,6 +178,9 @@ void WalletModel::updateTransaction()
132
178
  {
133
179
  // Balance and number of transactions might have changed
134
180
  fForceCheckBalanceChanged = true;
181
+ // A competing spend or wallet update can invalidate a sweep before the
182
+ // next periodic balance refresh observes it.
183
+ invalidateSweepRequests();
135
184
  }
136
185
 
137
186
  void WalletModel::updateAddressBook(const QString &address, const QString &label,
@@ -360,7 +409,10 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri
360
409
  static void NotifyUnload(WalletModel* walletModel)
361
410
  {
362
411
  qDebug() << "NotifyUnload";
363
- bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
412
+ bool invoked = QMetaObject::invokeMethod(walletModel, [walletModel] {
413
+ walletModel->invalidateSweepRequests();
414
+ Q_EMIT walletModel->unload();
415
+ });
364
416
  assert(invoked);
365
417
  }
366
418
 
@@ -432,6 +484,7 @@ void WalletModel::subscribeToCoreSignals()
432
484
 
433
485
  void WalletModel::unsubscribeFromCoreSignals()
434
486
  {
487
+ invalidateSweepRequests();
435
488
  // Disconnect signals from wallet
436
489
  m_handler_unload->disconnect();
437
490
  m_handler_status_changed->disconnect();