Bitcoin Core  27.99.0
P2P Digital Currency
walletcontroller.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2022 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #include <qt/walletcontroller.h>
6 
8 #include <qt/clientmodel.h>
10 #include <qt/guiconstants.h>
11 #include <qt/guiutil.h>
12 #include <qt/walletmodel.h>
13 
14 #include <external_signer.h>
15 #include <interfaces/handler.h>
16 #include <interfaces/node.h>
17 #include <util/string.h>
18 #include <util/threadnames.h>
19 #include <util/translation.h>
20 #include <wallet/wallet.h>
21 
22 #include <algorithm>
23 #include <chrono>
24 
25 #include <QApplication>
26 #include <QMessageBox>
27 #include <QMetaObject>
28 #include <QMutexLocker>
29 #include <QThread>
30 #include <QTimer>
31 #include <QWindow>
32 
33 using util::Join;
38 
39 WalletController::WalletController(ClientModel& client_model, const PlatformStyle* platform_style, QObject* parent)
40  : QObject(parent)
41  , m_activity_thread(new QThread(this))
42  , m_activity_worker(new QObject)
43  , m_client_model(client_model)
44  , m_node(client_model.node())
45  , m_platform_style(platform_style)
46  , m_options_model(client_model.getOptionsModel())
47 {
48  m_handler_load_wallet = m_node.walletLoader().handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
49  getOrCreateWallet(std::move(wallet));
50  });
51 
52  m_activity_worker->moveToThread(m_activity_thread);
53  m_activity_thread->start();
54  QTimer::singleShot(0, m_activity_worker, []() {
55  util::ThreadRename("qt-walletctrl");
56  });
57 }
58 
59 // Not using the default destructor because not all member types definitions are
60 // available in the header, just forward declared.
62 {
63  m_activity_thread->quit();
64  m_activity_thread->wait();
65  delete m_activity_worker;
66 }
67 
68 std::map<std::string, bool> WalletController::listWalletDir() const
69 {
70  QMutexLocker locker(&m_mutex);
71  std::map<std::string, bool> wallets;
72  for (const std::string& name : m_node.walletLoader().listWalletDir()) {
73  wallets[name] = false;
74  }
75  for (WalletModel* wallet_model : m_wallets) {
76  auto it = wallets.find(wallet_model->wallet().getWalletName());
77  if (it != wallets.end()) it->second = true;
78  }
79  return wallets;
80 }
81 
82 void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
83 {
84  QMessageBox box(parent);
85  box.setWindowTitle(tr("Close wallet"));
86  box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
87  box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
88  box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
89  box.setDefaultButton(QMessageBox::Yes);
90  if (box.exec() != QMessageBox::Yes) return;
91 
92  // First remove wallet from node.
93  wallet_model->wallet().remove();
94  // Now release the model.
95  removeAndDeleteWallet(wallet_model);
96 }
97 
98 void WalletController::closeAllWallets(QWidget* parent)
99 {
100  QMessageBox::StandardButton button = QMessageBox::question(parent, tr("Close all wallets"),
101  tr("Are you sure you wish to close all wallets?"),
102  QMessageBox::Yes|QMessageBox::Cancel,
103  QMessageBox::Yes);
104  if (button != QMessageBox::Yes) return;
105 
106  QMutexLocker locker(&m_mutex);
107  for (WalletModel* wallet_model : m_wallets) {
108  wallet_model->wallet().remove();
109  Q_EMIT walletRemoved(wallet_model);
110  delete wallet_model;
111  }
112  m_wallets.clear();
113 }
114 
115 WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
116 {
117  QMutexLocker locker(&m_mutex);
118 
119  // Return model instance if exists.
120  if (!m_wallets.empty()) {
121  std::string name = wallet->getWalletName();
122  for (WalletModel* wallet_model : m_wallets) {
123  if (wallet_model->wallet().getWalletName() == name) {
124  return wallet_model;
125  }
126  }
127  }
128 
129  // Instantiate model and register it.
130  WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model, m_platform_style,
131  nullptr /* required for the following moveToThread() call */);
132 
133  // Move WalletModel object to the thread that created the WalletController
134  // object (GUI main thread), instead of the current thread, which could be
135  // an outside wallet thread or RPC thread sending a LoadWallet notification.
136  // This ensures queued signals sent to the WalletModel object will be
137  // handled on the GUI event loop.
138  wallet_model->moveToThread(thread());
139  // setParent(parent) must be called in the thread which created the parent object. More details in #18948.
140  QMetaObject::invokeMethod(this, [wallet_model, this] {
141  wallet_model->setParent(this);
143 
144  m_wallets.push_back(wallet_model);
145 
146  // WalletModel::startPollBalance needs to be called in a thread managed by
147  // Qt because of startTimer. Considering the current thread can be a RPC
148  // thread, better delegate the calling to Qt with Qt::AutoConnection.
149  const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance");
150  assert(called);
151 
152  connect(wallet_model, &WalletModel::unload, this, [this, wallet_model] {
153  // Defer removeAndDeleteWallet when no modal widget is active.
154  // TODO: remove this workaround by removing usage of QDialog::exec.
155  if (QApplication::activeModalWidget()) {
156  connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
157  if (!QApplication::activeModalWidget()) {
158  removeAndDeleteWallet(wallet_model);
159  }
160  }, Qt::QueuedConnection);
161  } else {
162  removeAndDeleteWallet(wallet_model);
163  }
164  }, Qt::QueuedConnection);
165 
166  // Re-emit coinsSent signal from wallet model.
167  connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
168 
169  Q_EMIT walletAdded(wallet_model);
170 
171  return wallet_model;
172 }
173 
175 {
176  // Unregister wallet model.
177  {
178  QMutexLocker locker(&m_mutex);
179  m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
180  }
181  Q_EMIT walletRemoved(wallet_model);
182  // Currently this can trigger the unload since the model can hold the last
183  // CWallet shared pointer.
184  delete wallet_model;
185 }
186 
187 WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget)
188  : QObject(wallet_controller)
189  , m_wallet_controller(wallet_controller)
190  , m_parent_widget(parent_widget)
191 {
192  connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
193 }
194 
195 void WalletControllerActivity::showProgressDialog(const QString& title_text, const QString& label_text, bool show_minimized)
196 {
197  auto progress_dialog = new QProgressDialog(m_parent_widget);
198  progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
199  connect(this, &WalletControllerActivity::finished, progress_dialog, &QWidget::close);
200 
201  progress_dialog->setWindowTitle(title_text);
202  progress_dialog->setLabelText(label_text);
203  progress_dialog->setRange(0, 0);
204  progress_dialog->setCancelButton(nullptr);
205  progress_dialog->setWindowModality(Qt::ApplicationModal);
206  GUIUtil::PolishProgressDialog(progress_dialog);
207  // The setValue call forces QProgressDialog to start the internal duration estimation.
208  // See details in https://bugreports.qt.io/browse/QTBUG-47042.
209  progress_dialog->setValue(0);
210  // When requested, launch dialog minimized
211  if (show_minimized) progress_dialog->showMinimized();
212 }
213 
214 CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
215  : WalletControllerActivity(wallet_controller, parent_widget)
216 {
218 }
219 
221 {
222  delete m_create_wallet_dialog;
223  delete m_passphrase_dialog;
224 }
225 
227 {
229  m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
230  m_passphrase_dialog->show();
231 
232  connect(m_passphrase_dialog, &QObject::destroyed, [this] {
233  m_passphrase_dialog = nullptr;
234  });
235  connect(m_passphrase_dialog, &QDialog::accepted, [this] {
236  createWallet();
237  });
238  connect(m_passphrase_dialog, &QDialog::rejected, [this] {
239  Q_EMIT finished();
240  });
241 }
242 
244 {
246  //: Title of window indicating the progress of creation of a new wallet.
247  tr("Create Wallet"),
248  /*: Descriptive text of the create wallet progress window which indicates
249  to the user which wallet is currently being created. */
250  tr("Creating Wallet <b>%1</b>…").arg(m_create_wallet_dialog->walletName().toHtmlEscaped()));
251 
252  std::string name = m_create_wallet_dialog->walletName().toStdString();
253  uint64_t flags = 0;
254  // Enable descriptors by default.
258  }
261  }
264  }
265 
266  QTimer::singleShot(500ms, worker(), [this, name, flags] {
268 
269  if (wallet) {
271  } else {
273  }
274 
275  QTimer::singleShot(500ms, this, &CreateWalletActivity::finish);
276  });
277 }
278 
280 {
281  if (!m_error_message.empty()) {
282  QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated));
283  } else if (!m_warning_message.empty()) {
284  QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
285  }
286 
288 
289  Q_EMIT finished();
290 }
291 
293 {
295 
296  std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
297  try {
298  signers = node().listExternalSigners();
299  } catch (const std::runtime_error& e) {
300  QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
301  }
302  if (signers.size() > 1) {
303  QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
304  signers.clear();
305  }
307 
308  m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
309  m_create_wallet_dialog->show();
310 
311  connect(m_create_wallet_dialog, &QObject::destroyed, [this] {
312  m_create_wallet_dialog = nullptr;
313  });
314  connect(m_create_wallet_dialog, &QDialog::rejected, [this] {
315  Q_EMIT finished();
316  });
317  connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
319  askPassphrase();
320  } else {
321  createWallet();
322  }
323  });
324 }
325 
326 OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
327  : WalletControllerActivity(wallet_controller, parent_widget)
328 {
329 }
330 
332 {
333  if (!m_error_message.empty()) {
334  QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated));
335  } else if (!m_warning_message.empty()) {
336  QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
337  }
338 
339  if (m_wallet_model) Q_EMIT opened(m_wallet_model);
340 
341  Q_EMIT finished();
342 }
343 
344 void OpenWalletActivity::open(const std::string& path)
345 {
346  QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);
347 
349  //: Title of window indicating the progress of opening of a wallet.
350  tr("Open Wallet"),
351  /*: Descriptive text of the open wallet progress window which indicates
352  to the user which wallet is currently being opened. */
353  tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
354 
355  QTimer::singleShot(0, worker(), [this, path] {
357 
358  if (wallet) {
360  } else {
362  }
363 
364  QTimer::singleShot(0, this, &OpenWalletActivity::finish);
365  });
366 }
367 
368 LoadWalletsActivity::LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget)
369  : WalletControllerActivity(wallet_controller, parent_widget)
370 {
371 }
372 
373 void LoadWalletsActivity::load(bool show_loading_minimized)
374 {
376  //: Title of progress window which is displayed when wallets are being loaded.
377  tr("Load Wallets"),
378  /*: Descriptive text of the load wallets progress window which indicates to
379  the user that wallets are currently being loaded.*/
380  tr("Loading wallets…"),
381  /*show_minimized=*/show_loading_minimized);
382 
383  QTimer::singleShot(0, worker(), [this] {
384  for (auto& wallet : node().walletLoader().getWallets()) {
386  }
387 
388  QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
389  });
390 }
391 
392 RestoreWalletActivity::RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
393  : WalletControllerActivity(wallet_controller, parent_widget)
394 {
395 }
396 
397 void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name)
398 {
399  QString name = QString::fromStdString(wallet_name);
400 
402  //: Title of progress window which is displayed when wallets are being restored.
403  tr("Restore Wallet"),
404  /*: Descriptive text of the restore wallets progress window which indicates to
405  the user that wallets are currently being restored.*/
406  tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
407 
408  QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
409  auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
410 
411  if (wallet) {
413  } else {
415  }
416 
417  QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
418  });
419 }
420 
422 {
423  if (!m_error_message.empty()) {
424  //: Title of message box which is displayed when the wallet could not be restored.
425  QMessageBox::critical(m_parent_widget, tr("Restore wallet failed"), QString::fromStdString(m_error_message.translated));
426  } else if (!m_warning_message.empty()) {
427  //: Title of message box which is displayed when the wallet is restored with some warning.
428  QMessageBox::warning(m_parent_widget, tr("Restore wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
429  } else {
430  //: Title of message box which is displayed when the wallet is successfully restored.
431  QMessageBox::information(m_parent_widget, tr("Restore wallet message"), QString::fromStdString(Untranslated("Wallet restored successfully \n").translated));
432  }
433 
435 
436  Q_EMIT finished();
437 }
438 
440 {
441  // Warn the user about migration
442  QMessageBox box(m_parent_widget);
443  box.setWindowTitle(tr("Migrate wallet"));
444  box.setText(tr("Are you sure you wish to migrate the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
445  box.setInformativeText(tr("Migrating the wallet will convert this wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n"
446  "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n"
447  "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n"
448  "The migration process will create a backup of the wallet before migrating. This backup file will be named "
449  "<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
450  "an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
451  box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
452  box.setDefaultButton(QMessageBox::Yes);
453  if (box.exec() != QMessageBox::Yes) return;
454 
455  // Get the passphrase if it is encrypted regardless of it is locked or unlocked. We need the passphrase itself.
456  SecureString passphrase;
457  WalletModel::EncryptionStatus enc_status = wallet_model->getEncryptionStatus();
458  if (enc_status == WalletModel::EncryptionStatus::Locked || enc_status == WalletModel::EncryptionStatus::Unlocked) {
460  dlg.setModel(wallet_model);
461  dlg.exec();
462  }
463 
464  // GUI needs to remove the wallet so that it can actually be unloaded by migration
465  const std::string name = wallet_model->wallet().getWalletName();
467 
468  showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(name)));
469 
470  QTimer::singleShot(0, worker(), [this, name, passphrase] {
471  auto res{node().walletLoader().migrateWallet(name, passphrase)};
472 
473  if (res) {
474  m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(res->wallet->getWalletName()));
475  if (res->watchonly_wallet_name) {
476  m_success_message += QChar(' ') + tr("Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->watchonly_wallet_name.value()));
477  }
478  if (res->solvables_wallet_name) {
479  m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(res->solvables_wallet_name.value()));
480  }
481  m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
482  } else {
484  }
485 
486  QTimer::singleShot(0, this, &MigrateWalletActivity::finish);
487  });
488 }
489 
491 {
492  if (!m_error_message.empty()) {
493  QMessageBox::critical(m_parent_widget, tr("Migration failed"), QString::fromStdString(m_error_message.translated));
494  } else {
495  QMessageBox::information(m_parent_widget, tr("Migration Successful"), m_success_message);
496  }
497 
499 
500  Q_EMIT finished();
501 }
node::NodeContext m_node
Definition: bitcoin-gui.cpp:37
int flags
Definition: bitcoin-tx.cpp:533
Multifunctional dialog to ask for passphrases.
void setModel(WalletModel *model)
@ Unlock
Ask passphrase and unlock.
@ Encrypt
Ask passphrase twice and encrypt.
Model for Bitcoin network client.
Definition: clientmodel.h:54
AskPassphraseDialog * m_passphrase_dialog
CreateWalletDialog * m_create_wallet_dialog
CreateWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void created(WalletModel *wallet_model)
Dialog for creating wallets.
bool isMakeBlankWalletChecked() const
QString walletName() const
bool isDisablePrivateKeysChecked() const
void setSigners(const std::vector< std::unique_ptr< interfaces::ExternalSigner >> &signers)
bool isEncryptWalletChecked() const
bool isExternalSignerChecked() const
void load(bool show_loading_minimized)
LoadWalletsActivity(WalletController *wallet_controller, QWidget *parent_widget)
void migrate(WalletModel *wallet_model)
void migrated(WalletModel *wallet_model)
void opened(WalletModel *wallet_model)
OpenWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void open(const std::string &path)
void restore(const fs::path &backup_file, const std::string &wallet_name)
void restored(WalletModel *wallet_model)
RestoreWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
std::vector< bilingual_str > m_warning_message
QObject * worker() const
WalletController *const m_wallet_controller
interfaces::Node & node() const
void showProgressDialog(const QString &title_text, const QString &label_text, bool show_minimized=false)
WalletControllerActivity(WalletController *wallet_controller, QWidget *parent_widget)
QWidget *const m_parent_widget
Controller between interfaces::Node, WalletModel instances and the GUI.
WalletController(ClientModel &client_model, const PlatformStyle *platform_style, QObject *parent)
WalletModel * getOrCreateWallet(std::unique_ptr< interfaces::Wallet > wallet)
ClientModel & m_client_model
void removeAndDeleteWallet(WalletModel *wallet_model)
void walletAdded(WalletModel *wallet_model)
void closeAllWallets(QWidget *parent=nullptr)
std::unique_ptr< interfaces::Handler > m_handler_load_wallet
QThread *const m_activity_thread
std::map< std::string, bool > listWalletDir() const
Returns all wallet names in the wallet dir mapped to whether the wallet is loaded.
void coinsSent(WalletModel *wallet_model, SendCoinsRecipient recipient, QByteArray transaction)
QObject *const m_activity_worker
void walletRemoved(WalletModel *wallet_model)
const PlatformStyle *const m_platform_style
interfaces::Node & m_node
void closeWallet(WalletModel *wallet_model, QWidget *parent=nullptr)
std::vector< WalletModel * > m_wallets
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:48
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
EncryptionStatus getEncryptionStatus() const
void coinsSent(WalletModel *wallet, SendCoinsRecipient recipient, QByteArray transaction)
QString getDisplayName() const
void unload()
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:33
virtual std::vector< std::unique_ptr< ExternalSigner > > listExternalSigners()=0
Return list of external signers (attached devices which can sign transactions).
virtual WalletLoader & walletLoader()=0
Get wallet loader.
virtual std::string getWalletName()=0
Get wallet name.
virtual void remove()=0
virtual util::Result< std::unique_ptr< Wallet > > createWallet(const std::string &name, const SecureString &passphrase, uint64_t wallet_creation_flags, std::vector< bilingual_str > &warnings)=0
Create new wallet.
virtual std::vector< std::string > listWalletDir()=0
Return available wallets in wallet directory.
virtual util::Result< std::unique_ptr< Wallet > > loadWallet(const std::string &name, std::vector< bilingual_str > &warnings)=0
Load existing wallet.
virtual util::Result< WalletMigrationResult > migrateWallet(const std::string &name, const SecureString &passphrase)=0
Migrate a wallet.
virtual util::Result< std::unique_ptr< Wallet > > restoreWallet(const fs::path &backup_file, const std::string &wallet_name, std::vector< bilingual_str > &warnings)=0
Restore backup wallet.
virtual std::unique_ptr< Handler > handleLoadWallet(LoadWalletFn fn)=0
static const int MAX_PASSPHRASE_SIZE
Definition: guiconstants.h:20
Qt::ConnectionType blockingGUIThreadConnection()
Get connection type to call object slot in GUI thread with invokeMethod.
Definition: guiutil.cpp:379
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:250
void PolishProgressDialog(QProgressDialog *dialog)
Definition: guiutil.cpp:895
Definition: messages.h:20
void ThreadRename(const std::string &)
Rename a thread both in terms of an internal (in-memory) name as well as its system thread name.
Definition: threadnames.cpp:57
bilingual_str ErrorString(const Result< T > &result)
Definition: result.h:93
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition: string.h:107
@ WALLET_FLAG_EXTERNAL_SIGNER
Indicates that the wallet needs an external signer.
Definition: walletutil.h:77
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:74
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
Definition: walletutil.h:51
@ WALLET_FLAG_BLANK_WALLET
Flag set when a wallet contains no HD seed and no private keys, scripts, addresses,...
Definition: walletutil.h:71
const char * name
Definition: rest.cpp:49
std::basic_string< char, std::char_traits< char >, secure_allocator< char > > SecureString
Definition: secure.h:58
bool empty() const
Definition: translation.h:29
std::string translated
Definition: translation.h:20
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:48
assert(!tx.IsCoinBase())